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 的寬高。