分析下面app的错误, 顺便 认识一下 Choreographer 可好?
问题来源:使用WindowManager在Service中addView/removeView,快速做一系列复杂动画交互,销毁的时候出现的问题.

想看 系统源码 可以点击这里👉👉 Android Code Search官网

下面是我们拿来分析的异常日志

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ThreadedRenderer.setFrameCompleteCallback(android.view.ThreadedRenderer$FrameCompleteCallback)' on a null object reference
	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3354)
	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2706)
	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1599)
	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7652)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1034)
	at android.view.Choreographer.doCallbacks(Choreographer.java:839)
	at android.view.Choreographer.doFrame(Choreographer.java:768)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1020)
	at android.os.Handler.handleCallback(Handler.java:873)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loop(Looper.java:224)
	at android.app.ActivityThread.main(ActivityThread.java:7096)
	at java.lang.reflect.Method.invoke(Method.java)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:928)

这里报错的地方是ThreadedRenderer.setFrameCompleteCallback出现空指针问题。

那么为什么这里会出现这个问题呢?先不急,我们来补一补一些知识,为了后面更好的分析问题。

一、WindowManagerImpl在哪里初始化?

有人知道,Activity是如何启动的吗?细节就不在这里说了,介绍的话又是一篇文章,太长了,我们就挑一个简单的入口ActivityThread#handleLaunchActivity 来看一看里面发生了什么:

//android.app.ActivityThread#handleLaunchActivity
public Activity handleLaunchActivity(ActivityClientRecord r,
        PendingTransactionActions pendingActions, Intent customIntent) {
    //初始化获取WMS,并将WMS转换为IWindowManager
    WindowManagerGlobal.initialize();
    final Activity a = performLaunchActivity(r, customIntent);
    return a;

再来看一下performLaunchActivity

//android.app.ActivityThread#performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        if (activity != null) {
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken);
    } catch (Exception e) {
    return activity;

继续看里面的Activity#attach方法

//android.app.Activity#attach
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

这里我们看初始化了一个PhoneWindow,在setWindowManager里面初始化了WindowManagerImpl

//android.view.Window#setWindowManager
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
//android.view.WindowManagerImpl#createLocalWindowManager
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);

原来WindowManagerImpl是在Activity#attatch中初始化的

二、ViewRootImpl在哪里初始化?

我们看一下源码中的注释:

The top of a view hierarchy, implementing the needed protocol between View and the WindowManager. 
This is for the most part an internal implementation detail of WindowManagerGlobal.

普通话大概是下面这个意思:

它是视图层次结构的顶部,实现了View和WindowManager之间的通信协议。
具体实现的细节在WindowManagerGlobal这个类当中。

我们简单看一下下面的类图:

classDiagram
ViewManager <|-- WindowManager
WindowManager <|-- WindowManagerImpl
class ViewManager{
+addView(View view, ViewGroup.LayoutParams params)
+updateViewLayout(View view, ViewGroup.LayoutParams params)
+removeView(View view)
class WindowManager{
+getDefaultDisplay()
+removeViewImmediate(View view)
class WindowManagerImpl{
-WindowManagerGlobal:mGlobal
<<interface>> ViewManager
<<interface>> WindowManager

看到这里了,内心疑问三连,这些玩意就算看了源码,过段时间也会忘,那么为什么要看源码?

当然是为了上面的错误啦,出错了找不到原因就先看源码里面到底做了什么事情,再来结合自己的项目来分析。

我们在目录一,已经分析过WindowManagerImpl在哪初始化的了,我们再来看一下addView方法

//android.view.WindowManagerImpl
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    //WindowManagerGlobal
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());

再次默默的打开了WindowManagerGlobal源码

//android.view.WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        //初始化ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);
        //设置传入视图的LayoutParams
        view.setLayoutParams(wparams);
        try {
            //ViewRootImpl#setView设置视图
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {

原来ViewRootImpl是在WindowManagerGlobaladdView方法中初始化的

我们简单看一眼ViewRootImpl构造函数里面初始化了什么

//android.view.ViewRootImpl
public ViewRootImpl(Context context, Display display, IWindowSession session,
        boolean useSfChoreographer) {
    //初始化View.AttachInfo,用于描述View树与Window的依附关系
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);
    //初始化可见性信息,后面主要用于View#dispatchSystemUiVisibilityChanged(int)
    mCompatibleVisibilityInfo = new SystemUiVisibilityInfo();
    //用于无障碍服务的辅助功能
    mAccessibilityManager = AccessibilityManager.getInstance(context);
    mAccessibilityManager.addAccessibilityStateChangeListener(
            mAccessibilityInteractionConnectionManager, mHandler);
    //高对比度文本
    mHighContrastTextManager = new HighContrastTextManager();
    mAccessibilityManager.addHighTextContrastStateChangeListener(
            mHighContrastTextManager, mHandler);
    //处理特殊按键的类,如:音量+-静音、媒体上一首|下一首|暂停起播等等
    mFallbackEventHandler = new PhoneFallbackEventHandler(context);
    //从ThreadLocal中返回一个Choreographer
    mChoreographer = useSfChoreographer
            ? Choreographer.getSfInstance() : Choreographer.getInstance();
   //显示管理服务
   mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

三、Choreographer是什么东西?

协调动画、输入和绘图的时间,每个Looper线程都有自己的Choreographer, Choreographer通过接收显示系统的时间脉冲(如垂直同步信号), 来完成下一帧的渲染工作;

我们在目录二看了ViewRootImpl是在哪初始化的,初始化ViewRootImpl的时候同时初始化了Choreographer,这两个类是有关联的。

1.Choregrapher四个常用方法

ViewRootImpl类中,发现使用了Choregrapher的四个方法:

//android.view.Choreographer
//监听VSYNC信号,下一次VSYNC信号到来时,执行action
void postCallback(int callbackType, Runnable action, Object token)
void removeCallbacks(int callbackType, Runnable action, Object token)
// 你可以把你自己的callback添加到Choreographer中
// 在下一个Frame被渲染的时候就会回调你的callback,执行你定义的doFrame操作
// 可以获取到帧率等信息
void postFrameCallback(FrameCallback callback)
void removeFrameCallback(FrameCallback callback)

查看之后发现上面两个post方法最终都会执行到Choreographer#postCallbackDelayedInternal 里面

//android.view.Choreographer
//这个action可能是FrameCallback也可能是Runnable
private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //复用一个callback,重新设置数据之后,添加到CallbackQueue[]中
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
            //回调时间到了,则请求一个VSYNC信号
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            //异步消息,消息队列优先处理异步消息
            msg.setAsynchronous(true);
            //触发MSG_DO_SCHEDULE_CALLBACK
            mHandler.sendMessageAtTime(msg, dueTime);

2.scheduleTraversals

//android.view.ViewRootImpl#scheduleTraversals
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //开启消息同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //监听VSYNC信号,下一次VSYNC信号到来时,执行mTraversalRunnable
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

如何触发scheduleTraversals调用的呢?
我们看一下View在调用invalidate之后触发的调用链:

graph TD
View#invalidate --> ViewGroup#invalidateChild --> ViewGroup#onDescendantInvalidated --递归调用--> ViewGroup#onDescendantInvalidated --> ViewRootImpl#onDescendantInvalidated --> ViewRootImpl#invalidate --> ViewRootImpl#scheduleTraversals --开启消息同步屏障--> Choreographer#postCallback --> Choreographer#postCallbackDelayedInternal --> Choreographer#scheduleFrameLocked --> 请求VSYNC垂直同步信号 --> Choreographer.FrameDisplayEventReceiver#run --> Choreographer#doFrame --> Choreographer#doCallbacks --> Choreographer.CallbackRecord#run --> ViewRootImpl.TraversalRunnable#run --> ViewRootImpl#doTraversal --移除消息同步屏障--> ViewRootImpl#performTraversals -.-> |省略部分方法|View#draw

3.scheduleFrameLocked

我们可以从上面的调用链看到是从哪里执行到scheduleFrameLocked方法的

//android.view.Choreographer
private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        mFrameScheduled = true;
        if (USE_VSYNC) {
            //判断当前运行的Looper是否为FrameHandler对应的Looper
            if (isRunningOnLooperThreadLocked()) {
                scheduleVsyncLocked();
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                //异步消息
                msg.setAsynchronous(true);
                //发送到消息队列头部,立即执行
                mHandler.sendMessageAtFrontOfQueue(msg);
        } else {
            final long nextFrameTime = Math.max(
                    mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            //定时触发消息
            mHandler.sendMessageAtTime(msg, nextFrameTime);

我们看一下上面的scheduleVsyncLocked:请求VSYNC垂直同步信号

//android.view.Choreographer
private void scheduleVsyncLocked() {
   //FrameDisplayEventReceiver
   //请求VSYNC垂直同步信号
   mDisplayEventReceiver.scheduleVsync();
//android.view.DisplayEventReceiver
public void scheduleVsync() {
   nativeScheduleVsync(mReceiverPtr);

android_view_DisplayEventReceiver.cpp

//frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
    sp<NativeDisplayEventReceiver> receiver =
            reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
    status_t status = receiver->scheduleVsync();

DisplayEventDispatcher.cpp

//frameworks/native/libs/gui/DisplayEventDispatcher.cpp
status_t DisplayEventDispatcher::scheduleVsync() {
    if (!mWaitingForVsync) {
        //请求下一个vsync信号
        status_t status = mReceiver.requestNextVsync();
    return OK;

刚刚上面看到scheduleVsyncLocked里面使用了FrameDisplayEventReceiver 发起VSYNC请求

//android.view.Choreographer.FrameDisplayEventReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    //这个onVsync如何回调回来的?
    //请打开:DisplayEventReceiver#dispatchVsync方法,这个方法中会回调onVsync
    @Override
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
        //该消息的callback为当前对象FrameDisplayEventReceiver
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        //此处mHandler为FrameHandler
        //通过FrameHandler向主线程Looper发送了一个自带callback的消息
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    //当主线程的Looper执行到上面的消息时候,会触发run方法调用
    @Override
    public void run() {
        mHavePendingVsync = false;
        //见目录:4.doFrame
        doFrame(mTimestampNanos, mFrame);
//android.view.DisplayEventReceiver#dispatchVsync
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
   //回调onVsync
   onVsync(timestampNanos, physicalDisplayId, frame);
//dispatchVsync方法,见:android_view_DisplayEventReceiver.cpp

scheduleFrameLocked逻辑如下:
(1). USE_VSYNC,默认是true
如果Looper是FrameHandler对应的Looper,则执行下一个VSYNC信号;
否则发送异步消息,最终都会执行到scheduleVsyncLocked()
(2). 非USE_VSYNC
定时向FrameHandler发送类型为MSG_DO_FRAME的异步消息,
执行doFrame从而刷新一帧数据;

4.doFrame

//android.view.Choreographer#doFrame
//frameTimeNanos是VSYNC信号回调时的时间
void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // no work to do
        long intendedFrameTimeNanos = frameTimeNanos;
        //当前时间
        startNanos = System.nanoTime();
        //当前时间与VSYNC信号来时的时间的差值
        final long jitterNanos = startNanos - frameTimeNanos;
        //掉帧检查
        if (jitterNanos >= mFrameIntervalNanos) { //mFrameIntervalNanos:每一帧的时长
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            //判断是否掉帧了,如果掉帧数量超过阈值,就输出log警告
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            //计算帧间隔偏移量
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            //修正VSYNC信号回调的时间,将其对齐到最新到达的垂直同步信号
            frameTimeNanos = startNanos - lastFrameOffset;
        if (frameTimeNanos < mLastFrameTimeNanos) {
            //由于跳帧的问题,继续请求下一个VSYNC信号
            scheduleVsyncLocked();
            return;
        if (mFPSDivisor > 1) {
            long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
            //时间间隔小于指定的时间,继续请求下一个VSYNC信号
            if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                scheduleVsyncLocked();
                return;
        //设置当前帧信息
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        //更新状态,上层不会收到后续的onVsync()回调
        mFrameScheduled = false;
        //将上一帧绘制时刻更新为最新垂直同步信号时间
        mLastFrameTimeNanos = frameTimeNanos;
    try {
        //统计doFrame耗时开始
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        //确保VSYNC更新期间的所有访问都同步到VSYNC的时间戳
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
        //优先执行输入事件,如Touch事件
        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
        //执行动画
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
        //View树遍历,执行layout/draw
        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
        //结束标识(当前帧时间上报,修正最后一帧的时间戳)
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        //统计doFrame耗时结束
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

doFrame逻辑如下:
(1). 掉帧检查
如果掉帧数量超过阈值,触发Log日志警告开发者,修正VSYNC信号回调的时间;
如果跳帧或者时间间隔小于执行时间,都需要请求下一个VSYNC信号;
(2).渲染当前帧
绘制每一帧,依次执行下面的doCallbacks

CALLBACK_INPUT
CALLBACK_ANIMATION
CALLBACK_INSETS_ANIMATION
CALLBACK_TRAVERSAL
CALLBACK_COMMIT

5.doCallbacks

//android.view.Choreographer#doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        final long now = System.nanoTime();
        //获取CallbackRecord内部的dueTime小于当前时间戳的callbacks
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        mCallbacksRunning = true;
        if (callbackType == Choreographer.CALLBACK_COMMIT) {
            final long jitterNanos = now - frameTimeNanos;
            Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
            //如果帧延迟超过2帧,需要更新帧时间,保证下一帧时间永远要大于前一帧
            if (jitterNanos >= 2 * mFrameIntervalNanos) {
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
                        + mFrameIntervalNanos;
                //修正VSYNC信号回调的时间
                frameTimeNanos = now - lastFrameOffset;
                //将上一帧绘制时刻更新为最新垂直同步信号时间
                mLastFrameTimeNanos = frameTimeNanos;
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            //执行CallbackRecord内部的run方法
            c.run(frameTimeNanos);
    } finally {
        //回收CallbackRecord
        synchronized (mLock) {
            mCallbacksRunning = false;
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

doCallbacks逻辑如下:
(1). 获取CallbackRecord内部的dueTime小于当前时间戳的callbacks
(2). 如果帧延迟超过2帧,需要更新帧时间,保证下一帧时间永远要大于前一帧
(3). 遍历CallbackRecord链表,执行run方法
(4). 回收CallbackRecord链表

6.CallbackRecord#run

//android.view.Choreographer.CallbackRecord#run
public void run(long frameTimeNanos) {
    if (token == FRAME_CALLBACK_TOKEN) {
        //Choreographer#postFrameCallback会执行到这里
        ((FrameCallback)action).doFrame(frameTimeNanos);
    } else {
        //CallbackRecord是View动画或绘制就会执行到这里
        ((Runnable)action).run();

CallbackRecord#run逻辑如下:
(1). 如果是Choreographer#postFrameCallback的调用会执行到第一个分支里面去
(2). 如果是Choreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, null)调用就会执行到第二个分支,执行里面的run方法之后,会触发ViewRootImpl#doTraversal方法的调用

四、堆栈信息异常分析

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ThreadedRenderer.setFrameCompleteCallback(android.view.ThreadedRenderer$FrameCompleteCallback)' on a null object reference
	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3354)
        .....

在这个方法里面执行到ThreadedRenderer.setFrameCompleteCallback出现空指针问题。

我们有下面两个疑问点,需要查看代码进行进一步的分析

1.ThreadedRenderer在哪初始化?

//android.view.ViewRootImpl#setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
            if (mSurfaceHolder == null) {
               //启用硬件加速
               enableHardwareAcceleration(attrs);

我们看到setView的时候在surfaceHolder为null的时候会调用enableHardwareAcceleration

//android.view.ViewRootImpl#enableHardwareAcceleration
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
    //init flag
    mAttachInfo.mHardwareAccelerated = false;
    mAttachInfo.mHardwareAccelerationRequested = false;
    //处于兼容模式的时候不要启用硬件加速
    if (mTranslator != null) return;
    // 是否支持启用硬件加速
    final boolean hardwareAccelerated =
            (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
    if (hardwareAccelerated) {
        if (!ThreadedRenderer.isAvailable()) {
            //源码里面:只写了一句isAvailable => return true
            return;
        if (!ThreadedRenderer.sRendererDisabled
                || (ThreadedRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
            if (mAttachInfo.mThreadedRenderer != null) {
                //先销毁HardwareRenderer
                mAttachInfo.mThreadedRenderer.destroy();
            //使用 OpenGL 创建ThreadRenderer
            mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                    attrs.getTitle().toString());
            mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut);
            //更新深色模式
            updateForceDarkMode();
            if (mAttachInfo.mThreadedRenderer != null) {
                //表示此时已经依附到硬件加速窗口了
                mAttachInfo.mHardwareAccelerated =
                        mAttachInfo.mHardwareAccelerationRequested = true;

enableHardwareAcceleration逻辑如下:
(1). 如果处于兼容模式则不启用硬件加速
(2). 非兼容模式,判断是否支持硬件加速,如果支持启用,ThreadedRenderer.isAvailable()不可用,会拦截执行
(3). 先销毁mAttachInfo里面的mThreadedRenderer,再使用OpenGL创建并初始化ThreadRenderer

2.ThreadedRenderer何时为null?

我们上面分析到ThreadedRenderer在setView里面的enableHardwareAcceleration方法中初始化的,那么有初始化启用的地方,一定要对应禁用销毁的地方

//android.view.ViewRootImpl#dispatchDetachedFromWindow
void dispatchDetachedFromWindow() {
    destroyHardwareRenderer();
//android.view.ViewRootImpl#destroyHardwareRenderer
private void destroyHardwareRenderer() {
    ThreadedRenderer hardwareRenderer = mAttachInfo.mThreadedRenderer;
    if (hardwareRenderer != null) {
        if (mView != null) {
            //销毁此视图与硬件关联的资源
            hardwareRenderer.destroyHardwareResources(mView);
        //销毁HardwareRenderer
        hardwareRenderer.destroy();
        hardwareRenderer.setRequested(false);
        //重置为null
        mAttachInfo.mThreadedRenderer = null;
        mAttachInfo.mHardwareAccelerated = false;

destroyHardwareRenderer逻辑如下:
(1). hardwareRenderer不为null
如果mView不为null,则需要:销毁此视图与硬件关联的资源
(2). 销毁HardwareRenderer
(3). 重置mThreadedRenderer

3.ViewRootImpl#performDraw

//android.view.ViewRootImpl#performDraw
private void performDraw() {
    //屏幕已经关闭 && 没有下一次绘制的报告
    if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
        return;
    } else if (mView == null) {
        //ViewRootImpl#setView失败或者触发了ViewRootImpl#dispatchDetachedFromWindow
        //mView才会为null
        return;
    //是否需要重绘
    final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
    mFullRedrawNeeded = false;
    boolean usingAsyncReport = false;
    boolean reportNextDraw = mReportNextDraw; 
    if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
       ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver.captureFrameCommitCallbacks();
       final boolean needFrameCompleteCallback = mNextDrawUseBLASTSyncTransaction ||
                        (commitCallbacks != null && commitCallbacks.size() > 0) ||
                        mReportNextDraw;
        usingAsyncReport = mReportNextDraw;
        if (needFrameCompleteCallback) {
           //报错疑点1️⃣,此处上面已经判断了mThreadedRenderer!=null
           mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(...);
   try {
       //尝试硬件加速绘制,不符合条件触发软件绘制,有滑动动画需要进入下一轮绘制
       //只有调用ThreadedRenderer.draw方法执行硬件渲染绘制canUseAsync才会为true
       boolean canUseAsync = draw(fullRedrawNeeded);
       if (usingAsyncReport && !canUseAsync) {
           //报错疑点2️⃣,目测是此处出现了空指针问题
           mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
           usingAsyncReport = false;
           finishBLASTSync(true /* apply */);
   } finally {

我们看一下可疑点2️⃣上面的draw(fullRedrawNeeded)

//android.view.ViewRootImpl#draw
private boolean draw(boolean fullRedrawNeeded) {
    //在哪里初始化的surface的呢?请看:ViewRootImpl#relayoutWindow 此方法
    Surface surface = mSurface;
    if (!surface.isValid()) {
        //surface无效则直接返回
        return false;
    //处理滑动区域或者焦点区域。
    scrollToRectOrFocus(null, false);
    //如果发生了滑动
    if (mAttachInfo.mViewScrollChanged) {
        mAttachInfo.mViewScrollChanged = false;
        //则触发回调
        mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
    //判断是否需要滑动动画
    boolean animating = mScroller != null && mScroller.computeScrollOffset();
    final int curScrollY;
    if (animating) {
        curScrollY = mScroller.getCurrY();
    } else {
        curScrollY = mScrollY;
    if (mCurScrollY != curScrollY) {
        mCurScrollY = curScrollY;
        fullRedrawNeeded = true;
        if (mView instanceof RootViewSurfaceTaker) {
            //进行Y轴上的动画执行
            ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
    final Rect dirty = mDirty;
    if (mSurfaceHolder != null) {
        // 不绘制,清空dirty区域
        dirty.setEmpty();
        if (animating && mScroller != null) {
            //停止滚动动画
            mScroller.abortAnimation();
        return false;
    //需要重绘,更新脏数据区域
    if (fullRedrawNeeded) {
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    //通知监听器,绘制即将开始
    mAttachInfo.mTreeObserver.dispatchOnDraw();
    int xOffset = -mCanvasOffsetX;
    int yOffset = -mCanvasOffsetY + curScrollY;
    final WindowManager.LayoutParams params = mWindowAttributes;
    final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
    if (surfaceInsets != null) {
        xOffset -= surfaceInsets.left;
        yOffset -= surfaceInsets.top;
        //更新偏移量
        dirty.offset(surfaceInsets.left, surfaceInsets.right);
    //开始绘制的时间
    mAttachInfo.mDrawingTime =
            mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
    boolean useAsyncReport = false;
    //脏数据区域非空 || 需要执行动画 || 辅助服务发生了焦点变化
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
            mInvalidateRootRequested = false;
            mIsAnimating = false;
            if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
                mHardwareYOffset = yOffset;
                mHardwareXOffset = xOffset;
                invalidateRoot = true;
            if (invalidateRoot) {
                //表示需要更新 DrawCallbacks 绘制的内容,由下一次调用 draw() 来完成
                mAttachInfo.mThreadedRenderer.invalidateRoot();
            //清空脏数据区域
            dirty.setEmpty();
            final boolean updated = updateContentDrawBounds();
            if (mReportNextDraw) {
                //恢复挂起的渲染请求,将在下一个vsync信号处产生一个新帧
                mAttachInfo.mThreadedRenderer.setStopped(false);
            if (updated) {
                //执行WindowCallbacks#onRequestDraw,请求窗口绘制下一帧
                requestDrawWindow();
            useAsyncReport = true;
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else {
            if (mAttachInfo.mThreadedRenderer != null &&
                    !mAttachInfo.mThreadedRenderer.isEnabled() &&
                    mAttachInfo.mThreadedRenderer.isRequested() &&
                    mSurface.isValid()) {
                try {
                    //重新初始化
                    mAttachInfo.mThreadedRenderer.initializeIfNeeded(
                            mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                } catch (OutOfResourcesException e) {
                    handleOutOfResourcesException(e);
                    return false;
                mFullRedrawNeeded = true;
                //进入下一轮的绘制
                scheduleTraversals();
                return false;
            //如果上面条件不匹配,会触发软件绘制
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                    scalingRequired, dirty, surfaceInsets)) {
                return false;
    //滑动动画
    if (animating) {
        mFullRedrawNeeded = true;
        //进入下一轮的绘制
        scheduleTraversals();
    return useAsyncReport;

这个draw(boolean fullRedrawNeeded) 方法内部大致执行了如下流程:


点击查看大图

这个时候能不能分析出来上面那个空指针可能是什么原因导致的呢?
我们可以大胆的猜测一下:
1.draw(boolean fullRedrawNeeded) 返回false的情况,可能会导致异常崩溃

boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
    //这里确实有点诡异,下面的代码还有判断非空,只有这里没有判断非空的情况
    //官方在这里加个:判断非空很难吗?
    mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
    usingAsyncReport = false;
    finishBLASTSync(true /* apply */);

2.performDraw() 什么条件下触发调用的?

//mAttachInfo.mTreeObserver.dispatchOnPreDraw():
//通知观察者绘制过程开始了,
//如果某一个观察者OnPreDrawListener.onPreDraw返回true,
//则绘制过程会被取消掉或者重新开始调度
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
//绘制没有被取消或者视图可见的时候才会触发performDraw
if (!cancelDraw) {
    performDraw();

五、猜想验证

刚刚上面分析了2个可疑点,虽然上面的错误只是某某用户操作某个功能出现的一种错误,但是不得不引起我们的重视,为什么会出现ThreadRenderer为null了,我们还有要绘制的任务再继续执行?

打个比方,我们给即将销毁的View设置setVisibility为GONE,大家可以猜想一下,可能会出现什么问题?
假设我们removeView的代码如下:

fun WindowManager.removeLayout(layout: View?) {
    if (null != layout) {
        //假设此处写了这样的一段代码,可以进行压力测试一下
        layout.post{
            layout.visibility = View.GONE
        try {
            removeView(layout)
        }catch (e:Exception){

我们可以直接打开ViewRootImpl,看一下里面的performTraversals方法

private void performTraversals() {
    .....
    final int viewVisibility = getHostVisibility();
    final boolean viewVisibilityChanged = !mFirst
            && (mViewVisibility != viewVisibility || mNewSurfaceNeeded
            || mAppVisibilityChanged);
    mAppVisibilityChanged = false;
    .....
    if (viewVisibilityChanged) {
        .....
        //我们发现如果可见性发生变更会执行到此处,同时触发destroyHardwareResources()调用
        if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
            endDragResizing();
            destroyHardwareResources();
   .....

如果执行到方法最后一行,这个时候刚好mThreadRenderer被设置为null,那么还是会出现空指针

void destroyHardwareResources() {
    final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
    if (renderer != null) {
        if (Looper.myLooper() != mAttachInfo.mHandler.getLooper()) {
            mAttachInfo.mHandler.postAtFrontOfQueue(this::destroyHardwareResources);
            return;
        renderer.destroyHardwareResources(mView);
        //如果这个时候mThreadedRenderer刚好被设置为null,这里还是会出现空指针的
        renderer.destroy();

所以最好的做法是,在我们执行视图销毁之前:不要出现触发刷新视图的动作,包括设置可见性等。

有些不规范的写法,比如:某某自定义View,
在onDetachedFromWindow里面判断了当前View的layerType,然后调用了setLayerType(LAYER_TYPE_NONE),这种的是不应该在这里调用的,需要删除,一般动画开始设置LAYER_TYPE_HARDWARE,动画结束或者取消再去设置LAYER_TYPE_NONE

因为在做一些复杂的场景的时候,如果无法做不到控制,到处刷新,会造成一些莫名其妙的错误。

希望这篇文章的分析,能对大家有那么一点点的帮助!

  • 千里之行,始于发心
  • ChatGPT保姆级教程,一分钟学会使用ChatGPT!
  • 放弃 console.log 吧!用 Debugger 你能读懂各种源码
  • 非大厂的我们,要如何去搞前端基建?
  •