Android绘制整体流程
首次View的绘制流程是在什么时候触发的?
答案在ActivityThread.handleResumeActivity
里触发的。
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// 1.执行Activity的onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
// ...
if (r.window == null && !a.mFinished && willBeVisible) {
// 2.获取Window
r.window = r.activity.getWindow();
// 3.获取DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 4.获取Activity的WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
// ...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 5.调用windowManager的addView方法
wm.addView(decor, l);
} else {
// ...
}
}
}
WindowManager是一个接口,我们查看WindowManagerImpl的addView:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
其内部调用的是WindowManagerGlobal的addView方法:
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);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 6.调用ViewRootImpl的setView方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
从源码中可以看到,onResume 之后,ActivityThread 通过调用 activity 中 windowmanager 的 addView 方法,将 decorView 传入到 ViewRootImpl 的 setView 方法中,通过 setView 来完成 View 的绘制。
ViewRootImpl 是如何绘制 View 的
我们再来看一下 setView 方法
// ViewRootImpl.setView 这里入参是DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// ...
requestLayout();
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 1.线程检查
checkThread();
mLayoutRequested = true;
// 执行绘制任务
scheduleTraversals();
}
}
void scheduleTraversals() {
// 获取内存屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 执行绘制任务
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
经过层层调用,绘制的任务落在了mChoreographer的身上,先看看mTraversalRunnable,从名字可以看出来跟Runnable有关系:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除内存屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 开始绘制--依次调用 performMeasure --performLayout --persformDraw
performTraversals();
// ...
}
}
简单来说 setView 做了三件事
① 检查绘制的线程是不是创建 View 的线程。
② 通过同步屏障保证绘制 View 的任务是最优先的
③ 调用 performTraversals 完成 measure,layout,draw 的绘制
后续 ViewGroup 和 View 的绘制其实是 performTraversals 对整个 ViewTree 的绘制。他们的关系可以用下面这张图表示
既然View 的绘制是在 onResume 之后才开始的,那为什么我在 onCreate 中调用 View.post 方法可以得到 View 的宽高呢
查看view的post代码:
public boolean post(Runnable action) {
// 如果View已经被attachToWindow就直接post到handler中
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// 否则就放到队列中,等到attached之后再执行
getRunQueue().post(action);
return true;
}
View.post 会判断当前 View 是否已经被添加到 window 上。如果添加了则立即执行 runnable,如果没有被添加则先放到一个队列中存储起来,等添加到 window 上时再执行。
而 View 被测量完成后才会 attachToWindow。所以当 post 的 runnable 执行时,View 已经绘制完成了。
MeasureSpec 的理解
View 的大小不仅仅取决于自身的宽高,还取决于父 View 的大小和测量模式。一个 200x200 的父 View 是不可能容纳一个 300x300 的子 View 的,父 View 的 wrap_content 和 match_content 也会影响子 View 的大小。
所以 View 的 measure 函数其实应该有 4 个参数:父 View 的宽,父 View 的高,宽的测量模式,高的测量模式。
那我自定义一个 View 的时候,如果不对 MeasureSpec 做处理。使用这个 View 时宽高传入 wrap_content,结果会怎么样?
查看view的默认的onMeasure():
#View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
#View.getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
所以当我们自定义 View 时,如果没有对 MODE 做处理,设置 wrap_content 和 match_content 结果其实是一样的,View 的宽高都是取父 View 的宽高。
invalidate 和 requestlayout 方法的区别
//ViewRootImpl.invalidate
@UnsupportedAppUsage
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
#ViewRootImpl.requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ViewRootImpl 作为顶级 View 负责 View 的绘制。所以简单来说,requestlayout 和 invaliate 最终都会向上回溯调用到 ViewRootImpl 的 postTranversals 方法来绘制 View。
不同的是 requestlayout 会绘制 View 的 measure,layout 和 draw 过程。invaliate 因为只添加了绘制 draw 的标志位,只会绘制 draw 过程。
实现一下 findViewbyid 的过程
protected T findViewTraversal(@IdRes int id) {
if (id == mID) {
return (T) this;
}
if(mView instanceOf ViewGroup){
int childrenCount = getChildrenCount();
for (int i = 0; i < childrenCount; i++) {
View v = getChildAt(i);
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return (T) v;
}
}
}
}
return null;
}
参考:
View绘制原理