今天带着一个问题来看Android View的绘制流程
View的绘制入口在哪?
很多时候,在进入到一个页面的时候,会需要动态的获取到布局中某一个view的宽度或者高度,但是我们发现如果直接在onCreate方法或者onResume方法中通过这种方式去取高度值得到的是0
int measuredHeight = mTextView.getMeasuredHeight();
而调用post方法才可以得到正确的值
mTextView.post(new Runnable() {
@Override
public void run() {
int measuredHeight1 = mTextView.getMeasuredHeight();
System.out.println("post measuredHeight:"+measuredHeight1);
}
});
所以回到我们的第一个问题,view的绘制入口在哪里,只有view绘制完成经过测量才能得到宽高值,是否在onCreate和onResume方法中,view还没有完成绘制测量呢?
在Activity的启动流程中已经了解到,最终要启动Activity并且开始执行Activity生命周期的位置是在ActivityThread的handleLaunchActivity中,我们直接来到这里看,对Activity启动流程不熟悉可以看另一篇https://www.jianshu.com/p/bd5208574430
handleLaunchActivity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
......
//这里会回调onCreate方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
//这里会回调onResume方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out paused, because it
// needs to be visible but isn't in the foreground. We accomplish this by going
// through the normal startup (because activities expect to go through onResume()
// the first time they run, before their window is displayed), and then pausing it.
// However, in this case we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just retain the current
// state it has.
performPauseActivityIfNeeded(r, reason);
// We need to keep around the original state, in case we need to be created again.
// But we only do this for pre-Honeycomb apps, which always save their state when
// pausing, so we can not have them save their state when restarting from a paused
// state. For HC and later, we want to (and can) let the state be saved as the
// normal part of stopping the activity.
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
//如果启动Activity失败,交给ActivityManager处理
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
performLaunchActivity
ClassLoader加载出Activity后执行了callActivityOnCreate,会回调onCreate方法
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
......
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
return activity;
}
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
//调用activity中的performCreate,onCreate方法就在里面
activity.performCreate(icicle);
postPerformCreate(activity);
}
可以看到,activity创建出来后就开始回调onCreate,而onCreate中做了什么呢,调用了setContentView方法加载我们的布局文件,setContentView源码分析https://www.jianshu.com/p/2f87ebe77f4e
从源码中可以看到,setContentView做了以下的操作:
1.new了一个DecorView
2.加载了一个id为android.R.id.content的布局并把它加入DecorView
3.我们设置的布局文件被加载到id为android.R.id.content的ViewGroup中
也就是说,setContentView后只是加载了布局,但是还没有进行测量绘制,那么在onResum方法调用时是否完成了测量绘制呢?
performLaunchActivity之后系统开始执行handleResumeActivity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
....
//回调onResum方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
......
//获取到WindowManager,将包含了我们我们自己的布局的
//DecorView加入到WindowManager中(ViewManager是一个
//接口,WindowManager实现了它)
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 (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
//如果Activity已经可见了,就将DecorView加入Window
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
......
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
......
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();
View decor = r.window.getDecorView();
//window可见之后,开始真正的绘制view了,这个方法很关键
wm.updateViewLayout(decor, l);
}
}
......
r.onlyLocalRequest = false;
// Tell the activity manager we have resumed.
if (reallyResume) {
try {
ActivityManager.getService().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
}
也就是说在onCreate和onResume方法之心的时候,还没有完成View的测量,所以在这两个方法中无法得到实际的高度,具体绘制的代码先不看,我们看看为什么post方法中可以得到高度
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
//把Runnable加入了队列
getRunQueue().post(action);
return true;
}
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
//看得出这是一个数组实现的队列,加入队列后也没有执行
//那么是什么时候执行的
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
public static <T> T[] append(T[] array, int currentSize, T element) {
assert currentSize <= array.length;
if (currentSize + 1 > array.length) {
T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
array = newArray;
}
array[currentSize] = element;
return array;
}
dispatchAttachToWindow,这个队列是在这个方法中执行的,后边我们会找到这个方法执行的时机,然后就会知道为什么这个方法中执行就可以得到高度了
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
......
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
......
}
我们再回到上边ViewManager的addView和updateViewLayout方法,ViewManager和WindowManager都是接口,找到它的实现类WindowManagerImpl(可以通过PhoneWindow找到WindowManager的实现类是他)
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
看一下WindowManagerGlobal这个类中做了什么,这里创建了一个ViewRootImpl,并把DecorView加入了这个View中
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
......
root = new ViewRootImpl(view.getContext(), display);
......
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
}
//这个方法是给view设置参数的,所以关键还是在addView方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
requestLayout();
}
}
这里又来到了曾经在invalidate方法中看到的源码,刷新view
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
//执行这个Runnable
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void 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;
}
}
}
private void performTraversals() {
....省略大量代码
// Ask host how big it wants to be 回调到measure了
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
....
//回调到layout了
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
......
//回调draw了
performDraw();
}
总结一下:
1.onCreate和onResume方法调用时还没有进行view的测量
2.onResume方法调用之后将包含着当前布局文件的DecorView交给了WindowManager(实现类WindowManagerImpl)处理
3.WindowManager中将DecorView设置到ViewRootImpl中,在ViewRootImpl中调用requestLayout开始刷新View,执行measure layout 和 draw方法
4.post方法中能得到高度是因为post将runnable加入队列,在view attachToWindow时后才去获取高度,此时view已经完成了测量