View 的绘制流程 layout,measure,draw,追溯到源头,就是 Activity 创建时 View 的绘制。
从源码来看,大概会涉及到 Activity 的 attach, create, resume 这三个过程
Activity 的 attach 过程
启动 Activity 时,ActivityThread 的 performLaunchActivity 方法中先调用了 activity.attach 方法,attach 中创建了 mWindow 和 mWindowManager
mWindow = new PhoneWindow(this); // 创建了 window
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 这里创建了 windowManager 对象
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mWindow.setWindowManager 方法中创建了 mWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
WindowManagerImpl 类中有个变量
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
到此就是 attch 方法创建的 window 相关的对象。
create 过程
onCreate 中 setContentView 调用了 PhoneWindow.setContentView, 这里创建了 DecoreView(继承自 FrameLayout) 对象,作为 PhoneWindow 的成员变量,并且给 DecoreView 添加了子 View, 根据不同的 window feature 添加不同的布局,但每个布局中都会有一个 id 为 content 的 View, 然后再把 activity 的 xml 文件添加到 content 中
整个 setContentView 添加的 View 就是这样子,到现在为止总结来说就是 Window 中有一个 DecoreView 对象,View 的层级图如下
resume 过程
onCreate 完成后,会接着调用 ActivityThread 的 handleResumeActivity 方法,主要看这几行代码
if (r.window == null && !a.mFinished && willBeVisible) {
// r.window 在前面没有赋值,会走到这里的逻辑
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager(); // 这里的 wm 就是 attach 时的 mWindowManager, 是 WindowManagerImpl 对象
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l); // ① 这里执行了 WindowManagerImpl.addView
}
}
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager(); // 这里的 wm 就是 attach 时的 mWindowManager, 是 WindowManagerImpl 对象
View decor = r.window.getDecorView(); // decore 就是 create 时的 DecoreView
wm.updateViewLayout(decor, l); // ② 这里这行了 WindowManagerImpl.updateViewLayout
}
}
重点在上述代码中的注释 ①,②,WindowManagerImpl内部都是通过 mGlobal 来实现的,mGlobal 在 attach 过程中介绍过,是 WindowManagerGlobal 对象。
下面就先看 WindowManagerGlobal.addView 方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display); // 这里创建了 ViewRootImpl
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root); // 把 ViewRootImpl 加进来
mParams.add(wparams);
...
root.setView(view, wparams, panelParentView);
}
接着看 WindowManagerGlobal.uodateViewLayout 方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index); // 这里得到的是 addView 时创建的 ViewRootImpl 对象
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false); // ③ 这里又是重点
}
}
到这里,注释 ① ② 都分析完了,上面代码中有个注释 ③,这是又一个重点,跟进代码里来看会发现到最后调用了一个方法 scheduleTraversals(),这个方法应该很熟悉了,很多文章都有写过绘制流程的入口就是这个方法。
接下来就是 scheduleTraversals 方法中会执行 mTraversalRunnable, TraversalRunnable 的 run 方法执行了 doTraversal, doTraversal 又调用了 performTraversals,然后这个方法就是一步一步绘制的流程了,会执行 layout,measure,draw 方法。
到这里启动一个 Activity 界面的绘制就很清晰了。
到这我们可以思考一个常见的问题,就是非 UI 线程更改 View 报的错 “Only the original thread that created a view hierarchy can touch its views”,在 onCreate 中创建子线程 setText 不报错的原因。
这句描述是在 ViewRootImpl.checkThread 报的,但 ViewRootImpl 对象是在 resume 时候创建的,所以 onCreate 中创建子线程修改 UI 时可能 ViewRootImpl 对象还没创建,所以就没有报错。