所有涉及的源码基于Android 10, api 29
Window添加
Window是抽象的概念,实际显示的内容是View, ViewManager
是管理View的基本接口, WindowManger
继承了ViewManager
接口,WindowManagerImpl
是WindowManger
的实现类, 内部的实际工作交给WindowManagerGlobal
处理。
WindowManagerGlobal
在addView
时, 创建了一个ViewRootImpl
对象并调用其setView
方法,setView
主要包含图上的两个过程:requestLayout
和addToDisplay
。其中addToDisplay
是IPC调用,通过Session
最终调用WMS的方法。resquestLayout
的相关代码如下:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 消息屏障, 只处理异步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// mTraversalRunnable的定义
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除消息屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 进入三大流程
performTraversals();
}
}
复制代码
通过源码可知最终会执行performTraversals
进入View的三大流程。
setContentView
最终调用PhoneWindow
的setContentView
public void setContentView(View view, ViewGroup.LayoutParams params) {
// mContentParent就是放置我们定义的XML视图的位置
if (mContentParent == null) {
// 创建DecorView, 并初始化mContentParent
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// Activity切换动画
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
// 省略
}
// 省略缩减的代码
private void installDecor() {
if (mDecor == null) {
// 创建DecorView
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 初始化mContentParent
mContentParent = generateLayout(mDecor);
}
}
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup generateLayout(DecorView decor) {
// 省略
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 省略
return contentParent
}
// 从DecorView中查找id为com.android.internal.R.id.content的ViewGroup
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
复制代码
由源码可以看出setContentView
只是创建出了DecorView
并将我们定义的视图放到id为com.android.internal.R.id.content
的ViewGroup中, 还没有通过WindowManager
来真正添加视图到窗口上。
Activity onCreate和onResume执行的时机
onCreate
在ActivityTheard#performLaunchActivity
方法中利用Instrumentation#callActivityOnCreate
调用Activity
的onCreate
方法onResume
的调用和onCreate
基本一样, 在performResumeActivity
方法中, 最终也是通过Instrumentation
调用了onResume
方法
DecorView何时添加
在ActivityTheard
的handleResumeActivity
方法中, 首先调用了performResumeActivity
方法,之后再用WindowManager
的addView
方法将DecorView
添加到Window
中。由上面Window添加的过程可以看出最终通过ViewRootImpl
完成setView操作, 并触发View的三大流程以及和WMS的IPC通信。
View#post为什么可以获取到视图的宽高
- 为什么onCreate和onResume获取不到
通常我们编写代码时在Activity的
onCreate
或者onResume
方法中无法获取到View的宽高, 而通过View#post
则可以获取到。说明onCreate
和onResume
方法的执行在View测量之前。从上面onCreate
和onResume
的执行时机以及DecorView
被添加的时机可以直到View的测量发生在onCreate和onResume之后, 因此在这些方法中无法获取View的宽高。 - 为什么View#post可以获取到
public boolean post(Runnable action) { // 获取AttachInfo final AttachInfo attachInfo = mAttachInfo; // AttachInfo不为null, 之间通过Handler放到MessageQueue中 if (attachInfo != null) { return attachInfo.mHandler.post(action); } // 消息暂时存到HandlerActionQueue中 getRunQueue().post(action); return true; } // 简略代码 void dispatchAttachedToWindow(AttachInfo info, int visibility) { // mAttachInfo赋值 mAttachInfo = info; if (mRunQueue != null) { // 将暂存的消息发送到MessageQueue等待执行 mRunQueue.executeActions(info.mHandler); mRunQueue = null; } } public void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions; for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); } mActions = null; mCount = 0; } } 复制代码
从
View#post
的源码可以看到mAttachInfo
为null的时候会暂存消息, 搜索源码发现mAttachInfo
在dispatchAttachedToWindow
方法中被复制, 而该方法在performTraversals
中被调用,因此当该方法被调用时,View的三大流程已经开始了,等到Looper轮询到我们post的消息的时候,View的宽高以及被测量了。
View三大流程
ViewRootImpl
的performTraversals
方法中分别调用了performMeasure
、performLayout
、performDraw
方法而进入View的三大流程
Measure
performMeasure
直接调用View#measure
, 最终会回调具体ViewGroup实现的onMeasure
方法, 对于ViewGroup需要先对其所属的子View进行测量。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 子View的LayoutParam参数
final LayoutParams lp = child.getLayoutParams();
// 根据父View的MeasureSpec和View自己的LayoutParam的到View自己的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 调用子View的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// View#onMeasure的默认实现, 有具体的View或者ViewGroup负责重写
// 参数的两个MeasureSpec都是上面 getChildMeasureSpec 方法计算到的View自己的 MeasureSpec
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
复制代码
注意:onMeasure
方法中的MeasureSpec参数是当前View自身的MeasureSpec
-
MeasureSpec
View类的一个静态内部类, 用一个32的int数据来表示View的测量模式, 以及对应的测量数值。
高2位表示测量模式, 低30表示具体数值。
测量模式有UNSPECIFIED
,EXACTLY
,AT_MOST
三种 -
MeasureSpec的计算
上面提到通过
getChildMeasureSpec
来计算View的MeasureSpec
// childDimension:LayoutParams中的width或者height, 就是XML中写的 public static int getChildMeasureSpec(int spec, int padding, int childDimension) { // 父View对应的Mode和Size int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); // 去掉padding int resultSize = 0; int resultMode = 0; // ViewGroup.LayoutParams中MATCH_PARENT定义为-1, WRAP_CONTENT定义为-2 switch (specMode) { // Parent has imposed an exact size on us 父View的Mode是EXACTLY case MeasureSpec.EXACTLY: if (childDimension >= 0) { // XML中写的具体dp数值 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. 和父View一样大 resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. 最大不超过父View resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us 父View指定了一个最大值 case MeasureSpec.AT_MOST: if (childDimension >= 0) { // // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } 复制代码
Layout
performLayout
直接调用View#layout
方法, 最终调用了具体ViewGroup的onLayout
进行布局操作, 计算中子View在布局中的具体位置后, 再调用具体View的layout
来设置View的left、top、right、bottom相关参数。
Draw
performDraw
方法,最终会调用具体View的onDraw
方法绘制View自身