View.post()方法使用场景
- 子线程中更新ui。
- onCreate()中调用获取view宽高。
下面看看源码
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
这里有两个流程:
- AttachInfo不为null时,
attachInfo.mHandler.post(action)->Handler.post()
- AttachInfo为null时调用
getRunQueue().post(action)
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
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和延迟时间封装到HandlerAction
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);
}
}
GrowingArrayUtils.append(mActions, mCount, handlerAction)。保存handlerAction到数组mActions,mActions是HandlerActionQueue的成员变量。
HandlerActionQueue.executeActions()很明显操作了HandlerAction。
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;
}
}
最终调用handler.postDelayed()
当然这解释不了最开始的问题:
- View.post()为何能在子线程中更新ui。
- View.post()为何能在onCreate()中获取宽高。
下面看executeActions()调用处。
View.dispatchAttachedToWindow()
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
ViewRootImpl.performTraversals()
final View host = mView;
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
getRunQueue().executeActions(mAttachInfo.mHandler)
...
//绘制流程
performMeasure(...);
performLayout(...);
performDraw();
performTraversals()是ui绘制入口,这里调用View.dispatchAttachedToWindow()传入了AttachInfo。所以AttachInfo不为null意味着view树已经绘制完毕,前面attachInfo.mHandler.post(action)自然也就能获取view的宽高。
但是这里有个问题:dispatchAttachedToWindow()明显在绘制之前调用,View.post()如何保证在绘制后执行?performTraversals()本身也是运行在主线程Looper事件循环中,executeActions()调用handler.post()发送消息执行传人的Runnable,事件队列会先执行完performTraversals()才执行下一条消息。
mAttachInfo在ViewRootImpl构造方法中初始化
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
这里先不说ViewRootImpl,只需知此处mHandler是主线程中初始化的,所以子线程中View.post()可以操作ui。
总结:view树还未绘制完毕,View.post()传入的Runnable会保存在HandlerAction数组中。待view树绘制完毕,ViewRootImpl调用view.dispatchAttachedToWindow()->HandlerActionQueue.executeActions()->Handler.post()发送消息,执行Runnable;view树绘制完毕,直接执行Handler.post()。