前两天写的博客Android--setContentView的绘制流程和Android--LayoutInflater的渲染过程
主要讲了Activity窗口的绘制层级,以及XML文件是如何对其中的节点View进行添加的。但是其中有很多细节我们都没有将:比如DecorView是如何添加到PhoneWindow中的;LayoutInflater只是解析了XML,并且将View添加到了父View中,但是View是如何真正渲染的,我们也没有细说说,今天我们就详细数一下这些问题
一、DecorView添加到PhoneWindow
我们都知道,当Activity执行到onResume这个生命周期的方法的时候,界面才会展示出来。所以呢,DecorView是在onResume方法执行的时候添加到PhoneWindow中的。具体怎么执行的,我们先从ActivityThread的handleResumeActivity方法中入手,因为onResume方法的入口是在这个方法中:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
.....
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
.....
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
....
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} ....
....
}
第4行:从performResumeActivity方法进入,这里就是onResume方法的真正入口。onResume方法不是重点,我就不进入查找了
第6~12行:在这里,我们可以找到很多眼熟的代码,比如window就是PhoneWindow;decor就是我们说的DecorView。wm是WindowManager,WindowManager我们是第一次见到,它其实是一个接口,具体实现类是WindowManagerImpl。我就不一行一行的带着大家去找了,我直接贴出最终的依据:Window.java中可以看见如下代码,而这里的this,其实就是PhoneWindow
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
第19行:将DecorView对象添加到了WindowManager中,这里就是我们想要的地方,我们先看看WindowManagerImpl中的addView方法
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
这里有几个关键对象:
mGlobal:很明显,和WindowManager有关系。而且,我们看见WindowMangaerGlobal是全局的单例对象,而且我们在其源码中可以发现一下代码
@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
所以,我们推测WindowManagerGlobal的作用就是管理整个进程的窗口信息
mParentWindow:我们刚刚说了,是PhoneWindow
params:是PhoneWindow的LayoutParams
我们进入WindowManagerGlobal的addView方法中查看:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
....
synchronized (mLock) {
.....
int index = findViewLocked(view, false);
....
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
代码太多,浓缩了一下
第8行:这里主要就是判断了一下view已经添加到了WindowManager中,防止重复添加
第11行:ViewRootImpl这个类不知道大家是否熟悉,在小节的后面,我会说它的作用
第20行:我们进入ViewRootImpl的setView方法中查看
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
....
requestLayout();
.....
view.assignParent(this);
......
}
}
}
浓缩的都是精华!!
查看requestLayout方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
查看scheduleTraversals方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
第5行:postCallback的第二个参数mTraversalRunnable是一个Runnable接口,所以我们查看它的run方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
run方法中,只有一个方法调用,我们查看doTraversal
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals方法我们下一节说,我们先总结一下上面说的内容
说到这里,我相信大家应该有很多问题,因为我看到这里的时候,流程虽然很流畅,但是又很多概念似乎很模糊,很懵懂。我就从我自己的角度上来提出问题,并且一一解答吧
1.PhoneWindow,ViewRootImpl,DecorView它们的作用和关系到底是什么?
PhoneWindow:从Android--setContentView的绘制流程上来看,PhoneWindow应该是Activity中展示View层级的一部分。其实并不是这样,因为我们在PhoneWindow找不到任何渲染的代码,仅仅是包含了DecorView,而DecorView才是真正的View对象,真正的View树根节点(FrameLayout)。其实PhoneWIndow更多的作用是连接Activity与View之间的交互,是一个桥梁的作用,比如说setContentView的具体执行,设置标题内容以及颜色,状态栏导航栏颜色获取等等。
ViewRootImpl:PhoneWindows虽然执行了setContentView但是并没有对DecorView进行层级的渲染,也就是去调用measure,layout,draw,那这些动作在哪里执行的呢?就是在ViewRootImpl中执行的,也就是我们将要说的performTraversals方法。当然,也不仅限于此,还包括事件的分发
DecorView:DecorView应该是最好理解的,View树的根节点,所有的绘制都是从这里开始的
2.DecorView到底是什么时候添加到PhoneWindow中的?
如果上面的概念理解了,这个问题我们就知道了。DecorView其实并没有真正的添加到PhoneWindow中,因为PhoneWindow本身也不是View,只是和PhoneWindow做了绑定,而且这个绑定过程在setContentView中就已经执行了。上面所说的流程,其实是创建了ViewRootImpl,然后执行了绘制过程,所以DecorView才会展示出来
二、View的绘制过程
其实到这里,绘制反而不是什么问题了,因为我们知道,绘制就是调用View的onMeasure,onLayout,onDraw方法了。而在上面我们所说的performTraversals中,存在performMeasure,performLayout,performDraw三个方法与之相对应。具体的调用过程我就不去捋了,我就说一下measure
measure
在measure中,需要注意的是MeasureSpec。MeasureSpec是一个32位的整形值,MeasureSpec的前两位表示测量模式,后30位表示测量的大小,从而保存了两个信息。
size表示了高度或宽度,单位是像素;而mode分为了三种:MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY,MeasureSpec.AT_MOST。MeasureSpec.UNSPECIFIED基本不用,常用的只有EXACTLY和AT_MOST。
EXACTLY:精准模式,表示这个View的宽高是一个确定值
AT_MOST:最大模式,表示这个View的宽高能达到的最大值
那么,onMeasure
中的两个参数是如何得到的呢?
事实上,一般来讲,当一个 ViewGroup 测量自身的时候,会先遍历它的子 View,并调用子 View 的measure
方法进行测量,而measure
方法又会调用onMeasure
回调。
也就是说,View的onMeasure
回调方法中的两个参数是父容器计算并传递过来的。
下表反应了正常情况下父容器是如何得到一个子 View 的 MeasureSpec 的。其中横轴表示父容器的 MeasureSpec,纵轴表示了该 View 在布局文件中设置的宽或高,表中为该 View 的 MeasureSpec 的 mode 与 size。
子View在xml中 \ 父MeasureSpec | EXACTLY | AT_MOST |
---|---|---|
确定的数值 | mode: EXACTLY size: 布局中设置的大小 | mode: EXACTLY size: 布局中设置的大小 |
wrap_content | mode: AT_MOST size: 父容器specSize | mode: AT_MOST size: 父容器specSize |
match_parent | mode: EXACTLY size: 父容器specSize | mode: AT_MOST size: 父容器specSize |
也就是说:
- 当布局中宽或高填写的固定值,那么对应的 mode 为 EXACTLY ,size 为填写的值;
- 当布局中的宽或高填写的
wrap_content
,那么对应的 mode 为 AT_MOST,size 为父容器的 size; - 当布局中的宽或高填写的
match_parent
,那么顾名思义,mode 和 size 都与父容器相同。