源码版本Android 6.0
请参阅:http://androidxref.com/6.0.1_r10
本文目的是分析从Activity启动到走完绘制流程并显示在界面上的过程,在源码展示阶段为了使跟踪代码逻辑更清晰会省略掉一部分非主干的代码,具体详细代码请翻阅源码。
上一篇文章Android UI绘制流程分析(一)Activity.setContentView与DecorView之间的关系我们分析了从Activity启动开始到调用Activity的setContentView方法时DecorView的页面层级,接下来我们继续往下分析DecorView和Window的关系以及如何调用到绘制的流程。
此时Activity的onCreate生命周期方法已经结束,我们回到ActivityThread的handleResumeActivity方法也就是上一篇的分割线位置继续往下走:
#class in ActivityThread
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
...
// ActivityThread中有一个mActivities的集合,这个集合里面包含了所要启动的所有ActivityClientRecord数据,每一个对应一个Activity
// 在上一篇的performLaunchActivity方法中会将所要启动的Activity对应的ActivityClientRecord保存在mActivities集合中
// 从上述集合中获取当前所要启动的ActivityClientRecord
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
...
// 当第一次启动该Activity时r.window == null,并且以下条件都会成立
if (r.window == null && !a.mFinished && willBeVisible) {
// 给record设置window实体
r.window = r.activity.getWindow();
// 获取该Activity对应的PhoneWindow中的decorView,此时decor就是上一篇文章中分析创建的decoreView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 这里最终会回调到上一篇中performLaunchActivity中的WindowManagerGlobal.initialize();是同一个对象
ViewManager wm = a.getWindowManager();
// 获取window的布局参数
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;
// 将DecorView与Window进行关联,最终调用到WindowManagerGlobal的addView方法
wm.addView(decor, l);
}
...
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
}
}
}
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume)主要做这几件事:
- 调用performResumeActivity方法获取ActivityClientRecord以及回调Activity的onResume生命周期方法
- 将DecorView与Window进行关联
我们来看下wm.addView(decor, l);是如何回调到WindowManagerGlobal中的:
#class in WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
synchronized (mLock) {
,..
// 创建一个ViewRootImpl,致辞
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 将根视图(也就是decorView、ViewRootImpl、二者所对应的布局参数都缓存起来)
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
// 将decorView设置给ViewRootImpl,也可以理解为ViewRootImpl其实是DecorView逻辑上的“父布局”,虽然ViewRootImpl并不是一个View
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
这时候主要做了3件事:
- 创建ViewRootImpl对象(从这里我们又可以得出一个结论:Activity - PhoneWindow - WindowManager - ViewRootImpl是一一对应的)
- 将DecorView以及ViewRootImpl缓存在WindowManagerGlobal本地
- 调用ViewRootImpl.setView将DecorView与ViewRootImpl进行关联,其实是会将DecorView设置给ViewRootImpl中的mView这个本地成员变量,也就是我们把ViewRootImpl理解成逻辑上的“父布局”,一个ViewRootImpl持有一个DecorView,在setView内部会开始进行ui测绘
至此再回顾一下Activity的布局层级我们可以这么理解:
root.setView(view, wparams, panelParentView);将DecorView设置给了ViewRootImpl:
#class in ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
// 从上面注释可以看出在将DecorView设置进WindowManager之前会在这里进行整个DecorView的第一次测绘
requestLayout();
...
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 进行UI线程校验,在子线程设置ui抛出的CalledFromWrongThreadException就是再这个方法内部抛出
checkThread();
// 设置Layout状态,后续会用到这个标记
mLayoutRequested = true;
// 执行测绘流程
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
void scheduleTraversals() {
// 如果当前有有正在进行的测绘任务,则不进行本次测绘
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 发送一个同步屏障以确保UI测绘的任务能优先得到执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通过编舞者调度发送一个UI测绘的消息,该消息是mTraversalRunnable的Runnable对象且是一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障,确保其他同步消息不会被一直阻塞
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
// 测绘流程入口,内部会执行performMeasure、performLayout、performDraw开始真正的测量、布局、绘制流程
performTraversals();
}
}
}
从ViewRootImpl的setView方法开始一路线性的跟踪下去我们可以总结出系统做了几件事:
- 在将DecorView设置给WindowManager之前先调用了requestLayout对 UI进行测绘之后再将DecorView设置给WindowManager
- 在requestLayout中做了先做了一下线程校验确保UI操作是在主线程中进行,之后再调用scheduleTraversals方法进行测绘
- 在scheduleTraversals中判断如果当前有正在进行测绘的任务就不执行本次测绘,如果没有则先发送一个同步屏障消息来确保UI测绘的消息能得到优先执行,然后通过编舞者发送一个UI测绘的异步消息。此处的同步屏障如果不清楚可以参考我的另一篇文章:Android中的Handler,对于编舞者后续再发文详解,对编舞者不清楚并不影响对后续测绘流程的理解,暂时可以直接将它理解为通过它来发异步消息执行测绘流程即可。
- 最终代码调用道performTraversals进行真正的测绘流程
先分析到这里,接下来用三篇文章来分别分析UI测绘的三个流程:测量,布局,绘制