首先来看下这些疑问:
- handler post和view post有什么区别?
- 在Activity生命周期onCreate中直接获取view的大小为0,这时通常使用view.post(runnable)方式来获取就能成功。为什么通过这种方式就可以哪?
- 在App启动速度一文中提到,为了不影响Activity启动速度,可以把一些耗时的操作通过view.post(runnable)放在第一帧绘制完成后进行。怎么确定这样就是在第一帧绘制完成后执行的哪?
handler post会把Runnable封装成Message,然后走消息机制那些流程。如果在获取view大小时通过handler post可以吗?来看下view post的实现。
源码分析
view post(runnable)
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*/
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.
getRunQueue().post(action);
return true;
}
复制代码
注释可以看到这里的Runnable和Hanlder post中的一样被加入到消息队列中,并且将会在UI线程中执行。具体到代码时跟view的mAttachInfo有关:
- 不为空时,runnable会通过mHandler对象发送到UI线程的MessageQueue中,这样的话就跟普通handler post没什么区别了。(为什么是ui线程可以通过mHandler的注释得到验证)
- 为空时,将runnable放入RunQueue中。 那么post到RunQueue里的runnable什么时候执行呢,又是为何当View还没attach到window的时候,需要post到RunQueue中。
mAttachInfo 什么时候初始化的哪?RunQueue又是什么哪?
mAttachInfo的初始化
此对象的初始化在 View的dispatchAttachedToWindow()里
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
// 在此会执行处理RunQueue里的runnable,稍后再介绍
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
...
onAttachedToWindow();
}
复制代码
可以看到只有当view attch到Window后,才会给mAttachInfo赋值,并且接下来会直接执行 getRunQueue().post(action) 。接下来我们看下RunQueue到底是什么以及executeActions()的实现。
RunQueue
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
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 void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
//最终也是发送到UI线程中
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
private static class HandlerAction {
final Runnable action;
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null || action != null && action.equals(otherAction);
}
}
}
复制代码
在这里可以看到,之前mAttachInfo为空时,调用 "getRunQueue().post(action)" 会将 runnable 保存在 HandlerActionQueue对象里的mActions数组中。然后在dispatchAttachedToWindow()里,mAttachInfo初始化后会调用 executeActions()方法,最终将这些runnable发送到ui线程消息队列中。
通过上面的分析我们了解到view post大概处理流程,但还是完全解决文章开头2、3问题。这可以溯源到 dispatchAttachedToWindow() 调用时机。
dispatchAttachedToWindow
此方法的调用会追溯到 ViewRootImpl.performTraversals()方法
public ViewRootImpl(Context context, Display display) {
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
}
private void performTraversals() {
final View host = mView;
...
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
} else {
...
}
mFirst = false;
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
复制代码
整个Activity布局View的 measure/layout/draw 都是在 performTraversals()里调用的,可以看到AttachInfo 是在此开始传递的,而且是第一次遍历执行的。这里的host就是DecorView,我们看下DecorView(ViewGroup)的dispatchAttachedToWindow的实现,如下:
// ViewGroup中的实现
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
super.dispatchAttachedToWindow(info, visibility);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility()));
}
}
复制代码
能够看到ViewGroup先调用自己的 dispatchAttachedToWindow()方法,再调用每个child的dispatchAttachedToWindow()方法,这样整个view树中就初始化了各自的mAttachInfo。但是注意一下细节,在performTraversals()中,dispatchAttachedToWindow()是在performMeasure()之前,这样的话,依旧没法保证post(runnable)时获取到尺寸啊。我们继续往上看performTraversals的调用,最终定位到 doTraversal()
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
复制代码
可以看到performTraversals()是由handler发送TraversalRunnable消息执行的,view post(runable)也是由handler发送消息执行的。消息的执行顺序跟他们加入消息队列的顺序有关,由于TraversalRunnable先加入的,所以performTraversals在view post的Runnable之前执行,也就是measure/layout/draw流程先执行。也就是说等到执行view post的Runnable时,view已经被measure过,此时可以获取view的大小。
直接在Activity的onCreate/onResume无法获取view的尺寸,也就是说performTraversals还未执行,那performTraversals是在Activity哪个阶段调用的哪?
AttachedToWindow 与 Activity的生命周期
要查看 performTraversals()的调用,就离不开ViewRootImpl对象的创建。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
//ViewRootImpl 创建
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//在 setView 里最终会调用performTraversals()
root.setView(view, wparams, panelParentView);
}
//addView 会被 WindowManagerImpl类中的addView调用
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
//WindowManagerImpl的addView 是被ActivityThread类中的 handleResumeActivity调用的
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
//首先调用performResumeActivity()处理Activity的resume状态,在这里面会调用onResume
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
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;
...
wm.addView(decor, l);
}
}
复制代码
通过上面调用链的分析,我们最终可以确定在onResume()后,会调用performTraversals()处理布局的measure/layout/draw并调用各view的dispatchAttachedToWindow。所以在onResume时并不能获取view的尺寸。
上面代码分析都是基本api28 分析的,其实在api 24(android 7.0)以下view post实现是有些许区别的。
view post低版本实现
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
//不同处
ViewRootImpl.getRunQueue().post(action);
return true;
}
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new RunQueue();
sRunQueues.set(rq);
return rq;
}
}
复制代码
可以看到,7.0以下的版本,不同的线程会获取到不同的RunQueue对象。这样的话,如果是在子线程调用的view post,Runnable是往子线程的RunQueue里存的,而performTraversals是在主线程中被执行的,getRunQueue()取的是主线程对应的队列,这是两个不同的队列,因此Runnable不会被执行。
view/activity的生命周期关系
上面已经通过源码分析了view 和 activity的一些关系,下面代来看下各个回调。在MyActivity中布局添加一个CustomView,然后在各个回调中添加log,会有如下调用顺序:
E/MyActivity: onCreate:
E/CustomView: onFinishInflate:
E/MyActivity: onResume:
E/MyActivity: onAttachedToWindow:
E/CustomView: onAttachedToWindow:
E/CustomView: onMeasure:
E/CustomView: onMeasure:
E/CustomView: onMeasure:
E/CustomView: onSizeChanged:
E/CustomView: onLayout:
E/CustomView: onDraw:
E/MainActivity: onWindowFocusChanged: true
E/CustomView: onWindowFocusChanged: true
//接下来退出Activity
E/MainActivity: onWindowFocusChanged: false
E/CustomView: onWindowFocusChanged: false
E/MyActivity: onDestroy:
E/CustomView: onDetachedFromWindow:
E/MyActivity: onDetachedFromWindow:
复制代码
经过上述分析,文章开头的问题已清晰~