很多情况下,我们在Activity的onCreate()中调用View.post()去解决View宽高为0的问题,为什么可以这么做呢?下面我们就带着这个问题去看下源码。
源码分析
1、View.post()
先看下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.
getRunQueue().post(action);
return true;
}
代码很简单,如果attachInfo
不为空,则会执行attachInfo.mHandler.post(action);
,否则执行getRunQueue().post(action);
。这里先看下它们分别做了什么。
- 1、attachInfo.mHandler.post(action);
它其实就是调用Handler的post(),这个handler从哪来的稍后再看。 - 2、getRunQueue().post(action)
getRunQueue()
返回的就是HandlerActionQueue
的对象,然后调用它的post()。
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++;
}
}
从代码中可以看出,其实就是将Runnable和delayMillis封装到HandlerAction
中,并缓存了起来。
这里对View.post()
做一个简单的总结:
如果mAttachInfo
不会空则直接调用attachInfo.mHandler的post方法,否则将Runnbale和delayMillis
封装到HandlerAction中并缓存起来(何时执行缓存的Runnable,后面会讲到)。那么问题来了,mAttachInfo
是什么时候赋值的呢?
2、mAttachInfo
是什么时候赋值的呢?
经搜索发现是在dispatchAttachedToWindow(AttachInfo info, int visibility)
中将info
赋值给View的成员变量mAttachInfo
,在dispatchDetachedFromWindow()
将其置成null。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
//省略代码...
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
onAttachedToWindow();
//省略代码...
}
dispatchAttachedToWindow()
方法中除了给mAttachInfo
进行赋值,还会调用mRunQueue.executeActions(info.mHandler);
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
这个方法就是取出缓存的HandlerAction
,然后调用Handler的postDelayed()去执行缓存的Runnbale
。还记得我们在View.post()
中当mAttachInfo==null
时会将Runnable给缓存起来,就是在这里调用的啦。
那么View的dispatchAttachedToWindow()
又是在哪里调用的呢?
3、View的dispatchAttachedToWindow()的调用
熟悉ViewRootmpl的同学肯定都知道,它是在performTraversals()
中调用的,这里不做过多的分析,只简单说下流程。
我们主要看下
ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mChoreographer.postCallback()
最终会调用postCallbackDelayedInternal()
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
这里只说结论,就不具体分析了:
- 1、首先将
mTraversalRunnable
传给Choreographer
,以当前的时间戳放进一个mCallbackQueues
队列中等待执行,然后调用native方法nativeScheduleVsync()
在底层注册监听下一个屏幕刷新信号的事件。 - 2、当下一个屏幕刷新的信号发出的时候,由于我们对信号做了监听,所以会回调app层的
onVsync
。当onVsync
回调时,会发一个Message到主线程,将后续的工作切换到主线程中工作,即从mCallbackQueues
队列中根据时间戳将之前存入的Runnable取出来执行,则mTraversalRunnable
也会被取出来执行。
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;
}
}
}
执行mTraversalRunnable则会执行doTraversal()
,在doTraversal()
中又会执行performTraversals()
。
private void performTraversals() {
// mView就是DecorView,在setView()时进行的赋值
final View host = mView;
//省略部分代码....
//默认为true
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//省略部分代码....
//调用DecorView的dispatchAttachedToWindow(),DecorView是FrameLayout,则会调用ViewGroup的
//dispatchAttachedToWindow(),ViewGroup的dispatchAttachedToWindow()会遍历child调用child的dispatchAttachedToWindow()
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
} else {
//省略部分代码....
}
if(....){
performMeasure();
}
if(....){
performLayout();
}
if(....){
performDraw();
}
}
- 1、
host.dispatchAttachedToWindow(mAttachInfo, 0)
会调用View的dispatchAttachedToWindow()
并将mAttachInfo
传递给View,mAttachInfo实在ViewRootImpl的构造函数中初始化的。 - 2、接下来会对整个View树进行测量、布局、绘制流程。
有的人可能会有疑问:View的dispatchAttachedToWindow()明明是在测量、布局、绘制之前调用的,为什么View.post()中Runnable执行时能获取到宽高呢?
其实原因我们上面已经说明了:performTraversals()的执行是在监听到下一个屏幕刷新信号时,会发送一个Message到主线程中进行执行的,熟悉Handler的同学都知道,MessageQueue中的消息只有执行完一个,才会取下一个执行。所以这也就保证了在View.post()中Runnable执行时已经完成了测量、布局、绘制。
网友评论