源码版本为 Android 10(Api 29),不同Android版本可能有一些差别
View 的绘制从哪里开始
在《Activity常见问题》的 Activity 在 onResume 之后才显示的原因是什么? 部分中我们知道了View是在 onResume() 回调之后才显示出来的,显示过程主要是通过 WindowManagerImpl#addView() -> WindowManagerGlobal#addView() -> ViewRootImpl#setView() 这个过程,我们再次看一下 ViewRootImpl#setView() 的代码(核心代码):
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 调用 requestLayout() 方法,进行布局(包括measue、layout、draw)
requestLayout();
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 通过调用 Session 的 addToDisplay() 方法
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
}
}
有这样一行 requestLayout() ,表示请求布局,我们界面的绘制也是从这一行代码开始的,接下来,我们就来看一下跟踪一下这段代码(ViewRootImpl#requestLayout()):
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
调用 ViewRootImpl#scheduleTraversals() 方法:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在 scheduleTraversals() 方法中通过 mChoreographer.postCallback() 方法发送一个要执行的实现了 Runnable 的 TraversalRunnable 的对象 mTraversalRunnable:
1. mChoreographer.postCallback() 方法内部就是通过Handler机制
2. TraversalRunnable 为 ViewRootImpl 的内部类
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
调用 ViewRootImpl#doTraversal() 方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
调用 ViewRootImpl#performTraversals() 方法,该方法中关于绘制的代码
private void performTraversals(){
……
// 方法测量组件的大小
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
……
// 方法用于子组件的定位(放在窗口的什么地方)
performLayout(lp,desiredWindowWidth,desiredWindowHeight);
……
// 绘制组件内容
performDraw();
……
}
而在performMeasure()、performLayout()和performDraw()方法的调用过程可以用下面的图来表示:
从图中可以看出系统的View类已经写好了measure()、layout()和draw()方法:
1. 在系统View类中measure()方法用了final修饰,不能被重写(我觉得这应该是google不想让开发者更改measure()方法里面的逻辑而设计的,但是开发者有时又有需求需要自己测量,所以提供了onMeasure()方法可以重写);
代码
2. 在系统View类中的layout()方法在调用onLayout()方法前调用了setFrame()方法,这个方法作用是判断View的位置是否发生改变,如果没有发生改变,就不调用onLayout()方法,主要是为了提高性能,在View类中onLayout()方法是空实现,这是因为view没有子类,而当在自定义的控件如果是直接继承ViewGroup时就必须重写onLayout()方法;
代码
3. 在系统View类中的draw()方法,开发者一般不会重写,因为当我们如果重写draw()时,就需要按照系统定义好的步骤一步一步的画,否则会显示不出来,相对来说比较麻烦。而如果我们实现onDraw()方法,我们只要关注我们画的内容即可(画出来的内容就是显示到界面的内容);
代码
4. 当开发者在自定义控件时