View.post(runnable) runnable在主线程执行,且能拿到View的宽高等测量绘制信息
view.post投递消息
- 如果attachInfo不为空,则直接由attachInfo的handler来执行runnable 信息
- 否则,放入HandlerActionQueue中,等待attachInfo赋值,然后由attachInfo.handler执行runnable
综上,view.post(runnable)都是通过attachInfo.handler发送消息,由handler分发处理。
attachInfo是在dispatchAttachedToWindow中赋值,在dispatchDetachedFromWindow中移除的。
//View.java
//投递消息
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) { //attachInfo已经赋值,则直接通过attachInfo.handler执行
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action); //否则放入HandlerActionQueue中,等待时机处理
return true;
}
//取消执行runnable对象,和post对应
public boolean removeCallbacks(Runnable action) {
if (action != null) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mHandler.removeCallbacks(action);
attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, action, null);
}
getRunQueue().removeCallbacks(action);
}
return true;
}
AttachInfo mAttachInfo;
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
// Transfer all pending runnables.
if (mRunQueue != null) { //attachInfo.handler执行之前存储的runnable信息
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
void dispatchDetachedFromWindow() {
mAttachInfo = null;
}
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
HandlerActionQueue管理attachInfo未实例化之前的runnable对象
- 将runnable 和 delayTime 包装成一个HandlerAction对象
- 存储管理HandlerAction对象,然后通过传来的handler发消息执行runnable对象
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount; //集合容量
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
//将runnable 和 delayTime 包装成一个HandlerAction对象
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) { //默认初始化数组对象容量为4,
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
//更新集合数据mActions
public void removeCallbacks(Runnable action) {
synchronized (this) {
final int count = mCount;
int j = 0; //j是新的,处理过的的Hnadler集合索引
final HandlerAction[] actions = mActions;
for (int i = 0; i < count; i++) { //i是实际的Hnadler集合索引
if (actions[i].matches(action)) {
continue;
}
if (j != i) { //不匹配,用后面的覆盖前面的(j肯定小于等于i)
actions[j] = actions[i];
}
j++;
}
mCount = j; //集合的容量更新
for (; j < count; j++) { //集合被清除的数据赋为null
actions[j] = null;
}
}
}
//通过handler发送message的形式,在handler所在的线程执行所有action
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;
}
}
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);
}
}
}
attachInfo的实例化和赋值
-
attachInfo是在ViewRootImpl的构造函数中实例化的,mHandler的looper是当前线程(主线程)的looper对象,handler也是在主线程中处理消息的
-
ViewRootImple是在onResume之后,在WindowManagerGlobal中实例化的,并通过setView(view, display)绑定了decorView,viewRootImpl是decorView的parentView
-
setView中通过requestLayout发起布局绘制请求,在下一个16.66ms到来时,执行performTraversals()。
-
在performMeasure之前,通过host.dispatchAttachedToWindow(attachInfo),遍历decorView,将attachInfo绑定到每一个View/ViewGroup身上,才能通过attachInfo.handler发消息执行所有runnable对象
-
runnable对象是在performTraversals()执行完毕后,handler才会继续从messageQueue中取message,在主线程中执行,所以view.post(runnable)能拿到View的宽高等测量绘制信息。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
final View.AttachInfo mAttachInfo;
final ViewRootHandler mHandler = new ViewRootHandler();
//ViewRootImpl实例化时,attachInfo也实例化
public ViewRootImpl(Context context, Display display) {
mFirst = true; // true for the first time the view is added
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout(); //发起布局绘制请求
view.assignParent(this);
}
}
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
if (host == null || !mAdded)
return;
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0); //分发attachInfo给每一个view、ViewGroup
}
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
}
decorView分发attachInfo给每一个View/ViewGroup
//ViewGroup.java
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()));
}
}
总结:
- View.post(Runnable) 内部会自动分两种情况处理,当 View 还没 attachedToWindow 时,会先将这些 Runnable 操作缓存下来;否则就直接通过 mAttachInfo.mHandler 将这些 Runnable 操作 post 到主线程的 MessageQueue 中等待执行。
- 如果 View.post(Runnable) 的 Runnable 操作被缓存下来了,那么这些操作将会在 dispatchAttachedToWindow() 被回调时,通过 mAttachInfo.mHandler.post() 发送到主线程的 MessageQueue 中等待执行。
- mAttachInfo 是 ViewRootImpl 的成员变量,在构造函数中初始化,Activity View 树里所有的子 View 中的 mAttachInfo 都是 ViewRootImpl.mAttachInfo 的引用。
- mAttachInfo.mHandler 也是 ViewRootImpl 中的成员变量,在声明时就初始化了,所以这个 mHandler 绑定的是主线程的 Looper,所以 View.post() 的操作都会发送到主线程中执行,那么也就支持 UI 操作了。
- dispatchAttachedToWindow() 被调用的时机是在 ViewRootImol 的 performTraversals() 中,该方法会进行 View 树的测量、布局、绘制三大流程的操作。
- Handler 消息机制通常情况下是一个 Message 执行完后才去取下一个 Message 来执行(异步 Message 还没接触),所以 View.post(Runnable) 中的 Runnable 操作肯定会在 performMeaure() 之后才执行,所以此时可以获取到 View 的宽高。