1. 程式人生 > >原始碼詳細解析Activity生命週期onResume中Handler.Post(Runnable)和View.Post(Runnable)的UI效果差異原因

原始碼詳細解析Activity生命週期onResume中Handler.Post(Runnable)和View.Post(Runnable)的UI效果差異原因

一般需求中會出現在Activity啟動中需要獲取Ui控制元件相關大小或者在介面繪製完成之後重新整理資料,我們都知道在UI繪製完成之後,時機最好,不會阻塞主執行緒導致卡頓或者UI控制元件引數獲取失敗。 也許大家使用過或 知道Handler(MainLooper).Post(Runnable)和View.Post(Runnable)都是把Runnable封裝成Message再 push到主線成中looper中MessageQueue中,會發現在Activity的生命週期中執行這兩種方式效果不同,前者不滿足我們的需求,而後者卻能做到,但這是為啥,有沒有深入分析,本文就從Activity啟動流程以及UI重新整理和繪製流程原理以及訊息迴圈機制、同步障礙機制來剖析。

先看demo執行效果,以獲取Ui控制元件大小為例子,如下:

class MyActivity extends Activity{
.....
  @Override
 protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
  myCustomView = findViewById(R.id.custom);
   Log.i("chuan", "onCreate init myCustomView  width="
+ myCustomView.getWidth()); } @Override protected void onResume() { super.onResume(); Log.i("chuan", "Main onResume"); new Handler().post(new Runnable() { @Override public void run() { Log.i("chuan", "onResume Handler post runnable button width="
+ myCustomView.getWidth()); } }); myCustomView.post(new Runnable() { @Override public void run() { Log.i("chuan", "onResume myCustomView post runnable button width=" + myCustomView.getWidth()); } }); } public class MyView extends View { @Override public void layout(int l, int t, int r, int b) { super.layout(l, t, r, b); Log.i("chuan", "myView layout"); } @Override protected void onAttachedToWindow() { Log.i("chuan", "myView onAttachedToWindow with"+getWidth()); try { Object mAttachInfo = ReflectUtils.getDeclaredField(this, View.class, "mAttachInfo"); Log.i("chuan", "myView onAttachedToWindow mAttachInfo=null?" + (mAttachInfo == null)); Object mRunQueue = ReflectUtils.getDeclaredField(this, View.class, "mRunQueue"); Log.i("chuan", "myView onAttachedToWindow mRunQueue=null?" + (mRunQueue == null)); } catch (Exception e) { } super.onAttachedToWindow(); } @Override public boolean post(Runnable action) { try { Object mAttachInfo = ReflectUtils.getDeclaredField(this, View.class, "mAttachInfo"); Log.i("chuan", "myView post mAttachInfo=null?" + (mAttachInfo == null)); } catch (Exception e) { } return super.post(action); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.i("chuan", "myView onDraw"); } }

日誌顯示結果:

4-19 17:24:05.543 27745-27745/com.happyelements.AndroidAnimal I/chuan: myView init
04-19 17:24:05.545 27745-27745/com.happyelements.AndroidAnimal I/chuan: Main onCreate   
04-19 17:24:05.570 27745-27745/com.happyelements.AndroidAnimal I/chuan: onCreate init myCustomView  width=0
04-19 17:24:05.578 27745-27745/com.happyelements.AndroidAnimal I/chuan: Main onResume
04-19 17:24:05.577 27745-27745/com.happyelements.AndroidAnimal I/chuan:
    myView post mAttachInfo=null?true
04-19 17:24:05.584 27745-27745/com.happyelements.AndroidAnimal I/chuan: onResume Handler post runnable button width=0
04-19 17:24:05.594 27745-27745/com.happyelements.AndroidAnimal I/chuan: myView onAttachedToWindow width=0
    myView onAttachedToWindow mAttachInfo=null?false
04-19 17:24:05.630 27745-27745/com.happyelements.AndroidAnimal I/chuan: myView layout
04-19 17:24:05.630 27745-27745/com.happyelements.AndroidAnimal I/chuan: myView onDraw
04-19 17:24:05.631 27745-27745/com.happyelements.AndroidAnimal I/chuan: onResume myCustomView post runnable button width=854 

從日誌中可以看出幾點

  • 在Activity可互動之前的生命週期中UI直接操作是失效的,即使通過handler把Ui操縱任務post到onResume生命週期之後,也依然獲失效,日誌可以看到此時ui介面都沒有繪製。
  • 發現View.post(Runnable)會讓runnable在該View完成了measure、layout、draw之後再執行,這個時候當然就可以獲取到Ui相關引數了。

先看下兩者的原始碼實現:

1、handler.post(Runnable)

Handler.class

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

程式碼簡單可以看到就是把runnable封裝成Message然後加入當前Looper的MessageQueue佇列中。

2、再看下View.post(Runnable)

View.class


 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        //先是通過attachInfo.mHandler.post來實現,實際上就是用Handler.post和上述一樣
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        //若attachInfo =null時,維護一個mRunQueue 佇列,然後在dispatchAttachedToWindow通過mRunQueue.executeActions(info.mHandler);跟上述方法一樣
        getRunQueue().post(action);
        return true;
    }

      void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
..........省略....
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

.......省略......
    }
   private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

HandlerActionQueue.class

//實際也是通過handler來post到主執行緒
 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.post()方式來加入到主執行緒佇列中,api呼叫一樣為何效果不一樣,下面就從如下幾個知識點來分析:

  1. Activity生命週期啟動流程
  2. Message訊息傳送和執行原理機制
  3. UI繪製重新整理觸發原理機制
  4. MessageQueue同步障礙機制

Activity啟動流程
這個流程不清楚的,可以網上搜,一大堆。但這裡講的是,ApplicationThread收到AMS的scheduleLaunchActivity的Binder訊息之後,原因是binder執行緒,會通過ActivityThread中的mH(Handler)來sendMessage

 private class ApplicationThread extends ApplicationThreadNative {

 @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();
           ....省略....
            sendMessage(H.LAUNCH_ACTIVITY, r);
        }
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }
  }

mH(Handler)會把這個非同步訊息加入到MainLooper中MessageQueue,等到執行時候回撥handleLaunchActivity

 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: 
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

handleLaunchActivity方法會執行很多方法,這個是入口,簡單來說會建立Activity物件,呼叫其啟動生命週期,attach、onCreate、onStart、onResume,以及新增到WindowManager中,重點看下本文中onResume生命週期是如何回撥的。在Activity可見之後,緊接著就是要觸發繪製介面了,會走到handleResumeActivity方法,會performResumeActivity呼叫activity的onResume方法


 public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide) {
        ActivityClientRecord r = mActivities.get(token);

        if (r != null && !r.activity.mFinished) {
           .....................
            try {
                r.activity.onStateNotSaved();
                r.activity.mFragments.noteStateNotSaved();
                //呼叫activity的onResume方法
                r.activity.performResume();
  ...............................
            } catch (Exception e) {
           ........   
            }
        }
        return r;
    }

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        //1、呼叫activity的onResume方法
        ActivityClientRecord r = performResumeActivity(token, clearHide);
           ......
        if (r != null) {
            final Activity a = r.activity;
            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
          .......................
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                //2、decorView先暫時隱藏
                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;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //3、關鍵函式 新增到window觸發ui測量、佈局、繪製
                    wm.addView(decor, l);
                }
            ..............       
                r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                //4、新增decorView之後,設定可見,從而顯示了activity的介面
                    r.activity.makeVisible();
                }
            }
    }

由此可見:從handleResumeActivity執行流程來看onResume呼叫時候,Activity中的UI介面並沒有經過measure、layout、draw等流程,所以直接在onResume或者之前的onCreate中執行ui操縱都是無用的,因為這個時候Ui介面不可見,沒有繪製。那為何通過hander.post(Runnable)讓執行發生在handleLaunchActivity這個Message之後,這個時候流程已經走完了,也是在Ui介面觸發繪製之後,怎麼還是不行呢。

Message訊息傳送和執行原理機制這裡就不闡述了,hander.post(Runnable)讓執行發生在handleLaunchActivity這個Message之後就是因為這個Message迴圈機制原理,可以讓任務通常讓加入的先後順序依次執行,所以handleLaunchActivity這個Message執行之後,就是onResume中的push的Message。

但是為何onResume中hander.post(Runnable)還不能ui操作呢,就猜測handleLaunchActivity之後還沒有同步完成UI繪製,那UI繪製重新整理觸發原理機制是怎麼樣的了,直接分析觸發條件,上午中的wm.addVIew開始:windowManager會通過子類WindowManagerImpl來實現,其內部又通過WindowManagerGlobal的單例項來實現addVIew,原始碼如下
WindowManagerGlobal.class

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      ...............
        ViewRootImpl root;
        View panelParentView = null;
        ......
        //ViewRootImpl整個UI操作實際控制著
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
.................
        // do this last because it fires off messages to start doing things
        try {
        //繫結decorView,並觸發開發繪製
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {

        }
    }

addView動作又轉給ViewRootImpl.setView來實現,具體原始碼如下:
ViewRootImpl.class

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ..........
                //觸發重新整理繪製的關鍵
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                ..........
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //通過binder call新增到Display
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {

                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
           ...............
            //decorView新增父類ViewParent 就是ViewRootImpl
                view.assignParent(this);

            }
        }
    }

setView完成了上述幾個重要步驟,其中requestLayout的實現是如何觸發重新整理繪製的:

 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            //安排重新整理請求
            scheduleTraversals();
        }
}
 final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
 final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }  

 void scheduleTraversals() {
         //一個重新整理週期只執行一次即可,遮蔽其他的重新整理請求
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //設定同步障礙Message
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //螢幕重新整理訊號VSYNC 監聽回撥把mTraversalRunnable(執行doTraversal()) push到主執行緒了且是個非同步Message會優先得到執行 ,具體看下Choreographer的實現
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步障礙Message
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //真正執行decorView的繪製
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

  private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        .............
        Rect frame = mWinFrame;
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            .......
            mAttachInfo.mUse32BitDrawingCache = true;
            mAttachInfo.mHasWindowFocus = false;
            mAttachInfo.mWindowVisibility = viewVisibility;
            mAttachInfo.mRecomputeGlobalAttributes = false;
           //performTraversals 第一次呼叫時候decorView dispatch mAttachInfo變數
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);

            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(TAG,
                        "View " + host + " resized to: " + frame);
                mFullRedrawNeeded = true;
                mLayoutRequested = true;
                windowSizeMayChange = true;
            }
        }
       ...................
       //會根據狀態判斷是否執行,對mVIew(decorView)執行view的測量、佈局、繪製
       perforMeasure()
      perforLayout()
      perforDraw()
mFirst=false;
}

從上述程式碼可以發現在addView之後同步執行到requestLayout,再到scheduleTraversals中設定了同步障礙訊息,這個簡單闡述,看下原始碼實現:
MessageQueue.class

private int postSyncBarrier(long when) {
       synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

//根據token移動這個Message
 public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

MessageQueue同步障礙機制: 可以發現就是把一條Message,注意這個Message是沒有設定target的,整個訊息迴圈唯一一處不設定回撥的target(hander),因為這個即使標誌了同步障礙訊息,也是不需要handler來pushMessage到佇列中,直接手動迴圈移動連結串列插入到合適time的Message之後的即可。然後是如何識別這個障礙訊息的呢,在Looper的loop迴圈獲取MessageQueue.next()函式獲取下一個的message,是如何實現的,

MessageQueue.class

Message next() {

        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //查詢是否有下一個訊息,沒有就阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {

                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //關鍵地方,首先識別 msg.target == null情況就是同步障礙訊息,如果該訊息是同步障礙訊息的話,就會迴圈查詢下一個訊息是否是isAsynchronous狀態,非同步Message,專門給重新整理UI訊息使用的
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                //如果查到非同步訊息或者沒有設定同步障礙的訊息,直接返回執行
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
             ........省略.................
          nextPollTimeoutMillis = 0;
        }
    }

可以看到scheduleTraversals中設定了同步障礙訊息,就是相當於在MessageQueue中插入了一個Message,並且是在onResume之後插入的,所以在onResume中handler.post(Runnable)之後,這個訊息會在同步障礙Message之前,會先被執行,這個時候依然沒有重新整理繪製介面,待查詢到同步障礙Message時候,會等待下個非同步Message(重新整理Message)出現。所以在onResume中handler.post(Runnable)是Ui操作失效的。

那麼為何View.post(Runnable)就可以了,再回過頭來看下其原始碼:
View.class

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;
    }

由於在onResume中執行,這個時候ViewRootImpl還沒有初始化(addView時),而mAttachInfo是在ViewRootImpl建構函式中初始化的,過此時mAttachInfo=null,從上文知道 getRunQueue()維護了一個mRunQueue 佇列,然後在dispatchAttachedToWindow通過mRunQueue.executeActions(info.mHandler);那這個方法dispatchAttachedToWindow什麼會被呼叫,回顧上文中ViewRootImpl第一次收到Vsync同步重新整理訊號之後會執行performTraversals,這個函式內部做了個判斷當時第一次mFirst時候會呼叫host.dispatchAttachedToWindow(mAttachInfo, 0);把全域性mAttachInfo下發給所有子View,其原始碼如下:
View.class

 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
        //向下分發info,其實現在ViewGroup中
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once
     .........
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
        ........

    }

可以看到這個函式同時執行了 mRunQueue.executeActions(info.mHandler);從上文可知就是通過hander把mRunQueue中任務全部push到主執行緒中。由此可以知道在performTraversals(Message)中push Message到主線中,肯定會這個performTraversals(Message)之後再執行,並且在doTraversals中移除了同步障礙訊息(Message),故會依次執行。所以onResume中View.post的Message就會在performTraversals之後執行,而performTraversals就是完成了View整個測量、佈局和繪製。當View的mAttachInfo !=null時也說明肯定完成過UI繪製。

感謝看完,看似簡單的東西,其實內部原理沒有分析清楚容易糊塗,同時研究原始碼會學到很多相關的知識點,例如要看懂本文就需要了解上午中提到的4個知識點。
1. Activity生命週期啟動流程
2. Message訊息傳送和執行原理機制
3. UI繪製重新整理觸發原理機制
4. MessageQueue同步障礙機制**