所有涉及的源码基于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自身