Android View 的绘制流程 - 开篇 MeasureSpec
Android View 的绘制流程 01 - 前置流程
Android View 的绘制流程 02 - performMeasure
Android View 的绘制流程 03 - performLayout
Android View 的绘制流程 04 - performDraw
Android View 的绘制流程总结
之前文集中学习了几个自定义的View, 那么一定还记得三个自定义View的重要流程.
- measure, (测量, 测量每一个 View 及 ViewGroup 的尺寸 )
- layout, (摆放, 根据测量的结果及参数, 在布局上摆放每一个控件)
- draw, (绘制, 摆放好位置后, 就开始绘制, 然后显示在屏幕上)
这个文集也主要是围绕着三个流程来学习. 正式开始.
1. 前置流程
View 的绘制流程最初就是在 ActivityThread.handleLaunchActivity() 中开始.
2.1 ActivityThread.handleLaunchActivity()
在 Activity 启动的过程中, 会调用 ActivityThread.handleLaunchActivity() 方法.
handleLaunchActivity( ) 内部调用以下方法.
- ActivityThread.performLaunchActivity() - 这个方法会调用 Activity 的 onCreate 方法
- ActivityThread.handleResumeActivity() - 这个方法会调用 Activity 的 onResume 方法.(起始)
这个方法虽是入口, 但是我们重点学习不在这里, 所以就不再贴代码上来, 只是简单说一下流程.
1.2 ActivityThread.handleResumeActivity()
ActivityThread 3599 行
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
r = performResumeActivity(token, clearHide, reason);
...
if (...) {
//-------------1----------------
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
...
//------------2----------------
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
...
...
if (...) {
if (...) {
...
//-------------3--------------
wm.addView(decor, l);
}
}
...
}
handleResumeActivity 方法中调用了 performResumeActivity, 我猜测里面可能会调用了 Activity 的 onResume 方法, 在这里不是重点, 不再表述.
重点是下面几行.
第一部分
看到 View decor = r.window.getDecorView();
, 又见到了熟悉的 DecorView, 结合 Android 之 setContentView 流程 这个文集, 立刻能联想到 DecorView 的一系列关系.
- DecorView 在 PhoneWindow 中
- PhoneWindow 是 Window 的唯一派生类
- DecorView 是一个 FrameLayout.
- DecorView 内部有一个布局, 布局内部有一个ID 为 android.id.content 的 ViewGroup.(mContentParent)
- 执行完 setContentView 后, DecorView 中 mContentParent 被改名为 NO_ID,
- mContentParent 中包含了一个 SubDecorView ,
- SubDecorView 中 有一个 ContentFrameLayout, 改名为 android.id.content.
- 我们调用 setContentView ,传入的资源文件, 就在 SubDecorView 的 ContentFrameLayout 控件中.
- ...
怎么样, 有没有想起上面的那些?
所以说在 View 的 绘制流程中, 和这个 DecorView 有着非常密切的关系.
那么现在我们可以知道, PhoneWindow 赋值给了 r.window 属性, DecorView 赋值给了 decor 变量.
第二部分
接着看 ViewManager wm = a.getWindowManager();
a 就是 Activity , Activity 中 getWindowManager() 返回的是 WindowManager 对象 mWindowManager,
public WindowManager getWindowManager() {
return mWindowManager;
}
public interface WindowManager extends ViewManager {
}
可是 WindowManager 只是一个接口, ViewManager 也是一个接口, 那么肯定有实现类, 那么继续在 Activity.java 中搜索, 看是实例化的哪个类.
在 Activity.java 6955 行, 看到 mWindowManager 是在这里被赋值的.
mWindowManager = mWindow.getWindowManager();
.
接着跟进去, 看 mWindowManager 在 Window.java 769 行中被赋值.
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
那么现在就知道了, WindowManagerImpl 就是 ViewManager 子类的子类 . (WindowManagerImpl 继承自 WindowManager, WindowManager 是一个接口, 又继承自 ViewManager),
至此, 我们得知, vm 其实可以看做是 WindowManagerImpl 对象
接着看 WindowManager.LayoutParams l = r.window.getAttributes();
这个比较简单, 代码跟进去发现就是 获取 PhoneWindow 的窗口属性
第三部分
wm.addView(decor, l);
调用 WindowManagerImpl.addView 方法, 传入 decorView 和 PhoneWindow 的窗口属性.
1.3 WindowManagerImpl.addView(View, ViewGroup.LayoutParams)
WindowManagerImpl.java 90 行
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
又调用了 WindowManagerGlobal 的addView 方法, 并且传入DecorView 与 params
1.4 WindowManagerGlobal.addView(View, ViewGroup.LayoutParams, Display, Window )
WindowManagerGlobal.java 278行
//管理所有Activity 的 ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//管理所有 Activity 的 DecorView
private final ArrayList<View> mViews = new ArrayList<View>();
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
synchronized (mLock) {
...
ViewRootImpl root;
...
//初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
//添加 DecorView 到 DecorView 集合
mViews.add(view);
//添加 ViewRootImpl 到集合
mRoots.add(root);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
throw e;
}
}
}
WindowManager 维护着所有 Activity 的 DecorView 和 ViewRootImpl .这里初始化了一个 ViewRootImpl 又使用 ViewRootImpl 调用了 setView, 也传入了 DecorView 和经过转换的 WindowManager.LayoutParams.
1.5 ViewRootImpl.setView(View , WindowManager.LayoutParams , View)
ViewRootImpl.java 632 行
View mView;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//异步刷新View
synchronized (this) {
if (mView == null) {
mView = view;
....
requestLayout();
...
view.assignParent(this);
...
}
}
}
首先把 DecorView 赋值给 ViewRootImpl 类成员变量 mView. 这里需要记住 mView 就是 DecorView.
然后调用了 ViewRootImpl 类 方法 requestLayout()
, 请求对页面进行布局, 对View 完成异步刷新, 在其内部执行 View 的绘制方法., 再去看 requestLayout()
方法之前, 先看一下 view.assignParent(this);
这个方法将 ViewRootImpl 对象 this 作为参数调用了 View的 assignParent()
.
View.java 16834 行
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"+ " it already has a parent");
}
}
- 参数是 ViewParent, 而 ViewRootImpl 实现了 ViewParent 接口, 所以在这里就将 DecorView 和 ViewRootImpl 绑定起来了.
- 每个Activity 的根布局都是 DecorView, 而 DecorView 的 Parent 又是 ViewRootImpl, 所以在子 View 里执行 invalidate() 之类的操作,需要循环找 parent 的时候, 最后都会走到 ViewRootImpl 里.
现在接着看 ViewRootImpl.requestLayout()
方法.
1.6 ViewRootImpl.requestLayout()
ViewRootImpl.java 1153 行
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
请求对页面进行布局, 对View 完成异步刷新, 在其内部执行 View 的绘制方法.
checkThread();
校验当前所在的线程
scheduleTraversals()
, (当我们自定义 View 调用 invalidate 的时候, 其实最后也是调用了这个方法), 这个方法是屏幕刷新的关键. 一起去一探究竟.
1.7 ViewRootImpl.scheduleTraversals()
Traversals /trəˈvərs(ə)l/ 遍历的意思
ViewRootImpl.java 1354 行
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
这里看到调用了 mChoreographer.postCallback
里面传入了三个参数, 第二个参数是一个 Runnable 对象, 继续跟踪到这个 Runnable 中.
记住这个 boolean类型的变量mTraversalScheduled
记住这个方法 postSyncBarrier()
这两个还有下面的 removeSyncBarrier()
将在以后另起一章来讲解 Choreographer 的调用时机
1.8 ViewRootImpl.mTraversalRunnable
ViewRootImpl 6730 行
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
这个 Runnable 在 run 中调用了 doTraversal() 方法, 继续跟踪
1.9 ViewRootImpl.doTraversal()
ViewRootImpl.java 1377 行
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
//最最关键的方法
performTraversals();
...
}
}
boolean 类型变量 mTraversalScheduled
在1.7 中设置为 true, 并且调用了 postSyncBarrier()
,
这里设置为了 false, 调用了 removeSyncBarrier()
.
现在进入最最最关键的方法 performTraversals()
1.10 ViewRootImpl.performTraversals() 从这里开始执行三个流程.
ViewRootImpl.java 1576 行
private void performTraversals() {
...
if (...) {
...
if (...) {
if (...) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
layoutRequested = true;
}
}
} else {
...
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
//摆放
performLayout(lp, mWidth, mHeight);
...
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
...
//绘制
performDraw();
} else {
...
}
...
}
performMeasure()
执行测量
performLayout()
执行摆放
performDraw()
执行绘制
跟踪到这里, 终于看到了这三个方法. 这个方法的逻辑很复杂, 每次都会根据一些状态来判断走哪个流程, 有时候可能只执行某一个, 有时候可能三个都执行或者两个. 但是不管哪个流程, 都会遍历一遍View 树, 因此 View 的绘制是需要遍历很多次 View 树, 如果界面非常复杂, 耗时就会久一点. 当然这三个流程在遍历时, 也不一定都会遍历View 树, ViewGroup 在传递的时候, 还会根据响应的状态判断是否继续向下传递.
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
getRootMeasureSpec
: 根据 window 的布局参数计算出 root view 的 measure.
(可以理解为: 基于 phonewindow 的 layout parms, 算出 DecorView 的 measureSpec )
mWidth 和 mHeight 是 window (也就是 PhoneWindow) 的高度和宽度.
lp 是 window 的 LayoutParams 值. lp.width 和 lp.height 默认都是 MATCH_PARENT,
getRootMeasureSpec()
中又调用了 makeMeasureSpec()
将 高度和宽度值与 MATCH_PARENT 传入. 组合后返回一个采用32位存储的整型值 measureSpec.
根据在 Android View 的绘制流程 - 开篇 MeasureSpec 中所学习的.
现在已经得到了 DecorView 的 MeasureSpec了, 包含的模式与值分别如下.
MeasureSpec | 模式 | 大小 |
---|---|---|
childWidthMeasureSpec | MeasureSpec.EXACTLY | window 的宽度值 |
childHeightMeasureSpec | MeasureSpec.EXACTLY | window 的高度值 |
下一章将会开始学习 performMeasure() 流程.
扩展知识
- 知识点1
为什么我们在 Activity.onCreate 与 onResume 中获取不到 View 宽高 ?
看过上面的流程, 是不是知道了, 打开一个Activity, 当它的 onCreate 和 onResume 执行完后, 才会将它的 DecorView 与新建的一个 ViewRootImpl 绑定起来, 同时开始执行测量, 摆放, 绘制等流程. 执行完测量后, 我们自己 View 中控件才能宽高等属性. 所以, 都还没有进行测量, 甚至连 ViewRootImpl 都没创建,(在1.4中创建的) 怎么会有宽高呢.
还能得到一个信息是, Activity 界面的绘制, 是在 onResume 之后.
- 知识点2
当我们调用 View 的 invalidate() 方法, 执行重绘的时候, 内部也是要层层走到 ViewRootImpl 的 scheduleTraversals 方法里去. 然后这个方法会将遍历绘制 View 树的操作 preformTraversals() 封装到 Runnable 中. 传给 Chorerographer, 以当前的时间戳放进一个 mCallbackQueue 队列中, 然后调用了 native 层方法向底层注册监听下一个屏幕刷新信号事件.