ViewRootImpl源码解读

前言

继前篇文章 Activity启动流程
咱继续来分析ViewRootImpl
前篇文章讲到:handleResumeActivity()方法里面逻辑顺序:

  • 首先会执行performResumeActivity去调用Activity的onStart()、onResume(),返回ActivityClientRecord实例,通过此实例获取Activity
  • 从Activity中获取PhoneWindow,
  • 然后从PhoneWindow中获取到DecorView
  • 接着从Activity中获取WindowManager(实际上是WindowManagerImpl)
  • 再继续WindowManagerImpl调用addView()方法把DecorView添加进去
  • WindowManagerImpld的addView()方法会调用到WindowManagerGlobal的addView()方法,在此方法中会创建ViewRootImpl的实例。
  • 最终会调用ViewRootImpl的setView()方法加载DecorView,执行绘制流程。

1. ViewRootImpl的SetView()方法

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ....
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout(); // 标注:1️⃣
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                ...
                try {
                    ...
                    // 标注:2️⃣
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    ...
                } finally {
                    ...
                }
                ...
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }
            ...
            }
        }
    }

标注1️⃣: requestLayout() 逻辑下面解读
标注:2️⃣:调用mWindowSession.addToDisplay通过Binder ipc调用到WMS,实现对Window的真正的添加,这里的mWindow为 W 对象



2. ViewRootImpl的requestLayout()

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

3. ViewRootImpl的scheduleTraversals()

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 标注1️⃣
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 标注2️⃣
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

标注1️⃣: 同步屏障SyncBarrier, 开启postSyncBarrier()之后,MessageQueue 中的同步消息将不能再继续执行,等下一步调用removeSyncBarrier()之后,同步消息才能继续执行。
标注2️⃣: 设置了同步屏障后,发送一个 CALLBACK_TRAVERSAL 类型消息到 Choreographer 的消息队列。等执行完之后回调TraversalRunnable的run方法


4. ViewRootImpl里TraversalRunnable 的run()方法

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            // 标注1️⃣
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

标注1️⃣: 调用ViewRootImpl的doTraversal()方法


5. ViewRootImpl的doTraversal()方法

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // 标注1️⃣
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            // 标注2️⃣
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

标注1️⃣ 移除同步屏障
标注2️⃣ 调用ViewRootImpl的performTraversals()方法


6. ViewRootImpl的performTraversals()方法

private void performTraversals() {
    ......
    // Implementation of weights from WindowManager.LayoutParams
    // We just grow the dimensions as needed and re-measure if
    // needs be
    int width = host.getMeasuredWidth();
    int height = host.getMeasuredHeight();
    boolean measureAgain = false;

    if (lp.horizontalWeight > 0.0f) {
        width += (int) ((mWidth - width) * lp.horizontalWeight);
        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                MeasureSpec.EXACTLY);
        measureAgain = true;
    }
    if (lp.verticalWeight > 0.0f) {
        height += (int) ((mHeight - height) * lp.verticalWeight);
        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                MeasureSpec.EXACTLY);
        measureAgain = true;
    }

    if (measureAgain) {
        if (DEBUG_LAYOUT) Log.v(mTag,
                "And hey let's measure once more: width=" + width
                + " height=" + height);
        // 标注1️⃣
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    ......
    // 标注2️⃣
    performLayout(lp, mWidth, mHeight);
    ......
    if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    ......
    // 标注3️⃣
    performDraw();
    ......
}

上述标注中的1️⃣2️⃣3️⃣分别会调用View对应的measure()、layout()、draw()这几个方法。


7. ViewRootImpl的performMeasure()方法

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            // 标注1️⃣
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

标注1️⃣: 调用View 的measure()方法


8. ViewRootImpl的performLayout()方法

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ....
        final View host = mView;
        if (host == null) {
            return;
        }
        ....
        try {
            // 标注1️⃣
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
               ....
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                   ...
                    // 标注2️⃣
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                   ...
                }

            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

标注1️⃣ 与 2️⃣ 都是调用View的layout()方法


9. ViewRootImpl的performDraw()方法

private void performDraw() {
    if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
        return;
    } else if (mView == null) {
        return;
    }
    ...

    try {
        // 标注1️⃣
        boolean canUseAsync = draw(fullRedrawNeeded);
        if (usingAsyncReport && !canUseAsync) {
            mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
            usingAsyncReport = false;
        }
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ...
}

// 标注1️⃣ 调用的方法
private boolean draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    ...
    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;

        // Offset dirty rect for surface insets.
        dirty.offset(surfaceInsets.left, surfaceInsets.right);
    }
    ...
    // 标注2️⃣
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
            scalingRequired, dirty, surfaceInsets)) {
        return false;
    }
    ...
}

 // 标注2️⃣ 调用的方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

    // Draw with software renderer.
    final Canvas canvas;

    // We already have the offset of surfaceInsets in xoff, yoff and dirty region,
    // therefore we need to add it back when moving the dirty region.
    int dirtyXOffset = xoff;
    int dirtyYOffset = yoff;
    if (surfaceInsets != null) {
        dirtyXOffset += surfaceInsets.left;
        dirtyYOffset += surfaceInsets.top;
    }

    try {
        dirty.offset(-dirtyXOffset, -dirtyYOffset);
        final int left = dirty.left;
        final int top = dirty.top;
        final int right = dirty.right;
        final int bottom = dirty.bottom;

        canvas = mSurface.lockCanvas(dirty);

        // TODO: Do this in native
        canvas.setDensity(mDensity);
    } catch (Surface.OutOfResourcesException e) {
        handleOutOfResourcesException(e);
        return false;
    } catch (IllegalArgumentException e) {
        ...
        mLayoutRequested = true;    // ask wm for a new surface next time.
        return false;
    } finally {
        dirty.offset(dirtyXOffset, dirtyYOffset);  // Reset to the original value.
    }

    try {
        ...
        canvas.translate(-xoff, -yoff);
        if (mTranslator != null) {
            mTranslator.translateCanvas(canvas);
        }
        canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
        // 标注3️⃣
        mView.draw(canvas);

        drawAccessibilityFocusedDrawableIfNeeded(canvas);
    } finally {
        ...
    }
    return true;
}

标注1️⃣: 调用View 的draw()方法

总结ViewRootImpl调用流程:

  • step1: 最先调用setView()方法,传入View
  • step2: 调用requestLayout() ,检查创建ViewRootImpl时的线程和当前的线程是否一致。执行下一步scheduleTraversals()
  • step3: 调用scheduleTraversals(),开启同步屏障,之后发送一个 CALLBACK_TRAVERSAL 类型消息到 Choreographer 的消息队列。等执行完之后回调TraversalRunnable的run方法。
  • step4: 在TraversalRunnable的run方法里面调用doTraversal()方法。
  • step5: 在doTraversal()方法中,移除同步屏障,接着调用performTraversals()方法。
  • step6: 在performTraversals()方法中按顺序调用performMeasure(),performLayout(),performDraw()这几个方法,它们又分别调用View的measure() ,layout() ,draw()方法。

常问问题:

1.什么时候获取 View 的测量宽高?

private void performTraversals() {
    ......
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(lp, mWidth, mHeight);
    ......
    if (triggerGlobalLayoutListener) {
       mAttachInfo.mRecomputeGlobalAttributes = false;
        //标注1️⃣
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    ......
    performDraw();
    ......
}

答:根据 ViewRootImpl#performTraversals的调用逻辑。标注1️⃣的dispatchOnGlobalLayout()方法 在 performMeasure - performLayout 之后调用,通过该回调可以获取到测量的宽高。

App 层可以通过注册 OnGlobalLayoutListener 方式获取,代码如下:

view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
       // view.getMeasuredWidth()/view.getMeasuredHeight()
       ......
    }
});

2.Activity怎么在onCreate()里获取view的宽高?
有三种方法:
第1️⃣种:通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了,这时候就可以获取view的宽高了
注意:代码只会执行一次

public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
 
      mTextView = (TextView) findViewById(R.id.main_clock_time);
      Log.e(TAG, "mtextview width is : "+mTextView.getWidth()+", height is : "+mTextView.getHeight() );
      mTextView.post(new Runnable() {
          @Override
          public void run() {
            Log.e(TAG, "mtextview width is : "+mTextView.getWidth()+", height is : "+mTextView.getHeight() );
          }
      });
 }

第2️⃣种:通过ViewTreeObserver的OnGlobalLayoutListener接口,当View树的状态或者View树内部的View的可见性发生改变的时候,onGlobalLayout方法将被回调,此时便可以获取View的宽高。
注意:伴随着View树的状态改变等,onGlobalLayout会被多次调用。

@Override
protected void onCreate(Bundle savedInstanceState) {
    mTextView = (TextView) findViewById(R.id.main_clock_time);
    ViewTreeObserver observer = mTextView.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            int width = mTextView.getWidth();
            int height = mTextView.getHeight();
        }
    });
}

第3️⃣种:重写Activity/View 的onWindowFocusChangedView方法获取view宽高。
注意:onWindowFocusChanged会被调用多次,当Activity的窗口得到焦点和失去焦点的时候都会被调用一次。

public class MainActivity extends AppCompatActivity  {
    TextView mTextView;
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
            int width = mTextView.getWidth();
            int height = mTextView.getHeight();
    }
}

小总结:

  1. 调用getMeasuredWidth() 获取宽度,必须要等measure()方法走完才能获取到值。
  2. 调用getWidth()必须要layout()完之后才能获取到值。
  3. onMeasure() 和layout()是怎么调用的呢,简单捋一下view的绘制流程,每一个view的绘制流程大致包括,measure() ,layout() ,draw() ,view的绘制是从ViewRootImpl的performtraversal() 方法开始的,这个方法里面依次调用了 performMeasure(),performLayout(), performDraw() ,对应View里面的方法是measure() ,layout() ,draw()
    注意:
    1️⃣ getWidth()在layout()走完之后才有值
    2️⃣ getMeasuredWidth()在measure()走完之后才有值

3.在子线程中可以更新 UI 吗?
在 Activity#onResume 之前,可以在子线程中更新 UI。
checkThread 线程检查是在 ViewRootImpl 的方法中进行的 (如 invalidate 和 requestLayout)
而通过溯源代码,ViewRootImpl 是在 ActivityThread#handleResumeActivity 创建的。在 Activity#onResume 之前 ViewRootImpl 还没创建,所以也不会检查线程和绘制 UI。

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    ......
}

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a" +  
                "view hierarchy can touch its views.");
    }
}

4.MeasureSpec 自定义 View 的方式有哪几种?

  1. EXACTLY:精确模式(父 View 指定了子 View 的精确大小)
    对应我们在布局文件中设置宽高时给一个具体值或者match_parent;当前的尺寸就是当前View应该取的尺寸.
    对应关系:match_parent--->EXACTLY。
    match_parent就是要利用父View给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。
    固定尺寸(100dp)--->EXACTLY.
    用户自己制定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主。
    父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
  2. AT_MOST:最大值模式(父 View 指定了子 View 的最大值,子 View 最大到达这个最大值)
    对应设置宽高时给一个wrap_content; 当前尺寸是当前View能取的最大尺寸
    对应关系:warp_parent---> AT_MOST
    我们想要将大小设置为包裹我们的View内容,那么尺寸大小就是父View给我作为参考的尺寸,至于不超过这个尺寸就可以啦。具体尺寸就根据我们的需求去设定。
  3. UNSPECIFIED:未指明模式(父 View 没有对子 View 做限制)
    这种测量模式多用在ScrollView中,或者系统内部调用;当前的尺寸就是当前View应该取的尺寸;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容