前言
轉載請聲明,轉自【https://www.cnblogs.com/andy-songwei/p/12021867.html】,謝謝!
提起View.post(),相信不少童鞋一點都不陌生,它用得最多的有兩個功能,使用簡便而且實用:
1)在子線程中更新UI。從子線程中切換到主線程更新UI,不需要額外new一個Handler實例來實現。
2)獲取View的寬高等屬性值。在Activity的onCreate()、onStart()、onResume()等方法中調用View.getWidth()等方法時會返回0,而通過post方法卻可以解決這個問題。
本文將由從源碼角度分析其原理,在閱讀文本之前,希望讀者是對Handler的Looper問題有一定了解的,如果不了解請先閱讀【朝花夕拾】Handler篇。源碼基本基於API-28(即Android 9),只有ActivityThread中Activity啟動到調用handleResumeActivity()這一小部分是參考的API-26(即Android 8.0,API-28上這部分代碼邏輯有些變化,還沒來得及詳細研究,但基本機制應該不會變)。
本文的主要內容如下:
一、在子線程中更新UI
1、在子線程中更新UI使用示例
一般我們通過使用View.post()實現在子線程中更新UI的示例大致如下:
1 private Button mStartBtn; 2 @Override 3 protected void onCreate(Bundle savedInstanceState) { 4 super.onCreate(savedInstanceState); 5 setContentView(R.layout.activity_intent_service); 6 mStartBtn = findViewById(R.id.start); 7 new Thread(new Runnable() { 8 @Override 9 public void run() { 10 mStartBtn.post(new Runnable() { 11 @Override 12 public void run() { 13 //處理一些耗時操作 14 mStartBtn.setText("end"); 15 } 16 }); 17 } 18 }).start(); 19 }
第7行開啟了一個線程,第10行通過調用post方法,使得在第14行實現了修改自身UI界面的顯示(當然,平時使用中不一定只能在onCreate中,這里僅舉例而已)。
2、post源碼分析
在上述例子中,mStartBtn是如何實現在子線程中通過post來更新UI的呢?我們進入post源碼看看。
//=================View.java==========--
1 /** 2 * <p>Causes the Runnable to be added to the message queue. 3 * The runnable will be run on the user interface thread.</p> 4 * ...... 5 */ 6 public boolean post(Runnable action) { 7 final AttachInfo attachInfo = mAttachInfo; 8 if (attachInfo != null) { 9 return attachInfo.mHandler.post(action); //① 10 } 11 // Postpone the runnable until we know on which thread it needs to run. 12 // Assume that the runnable will be successfully placed after attach. 13 getRunQueue().post(action); //② 14 return true; 15 }
第1~5行的注釋說,該方法將Runnable添加到消息隊列中,該Runnable將在UI線程運行。這就是該方法的作用,添加成功了就會返回true。
上述源碼的執行邏輯,關鍵點在mAttachInfo是否為null,這會導致兩種邏輯:
1)mAttachInfo != null,走代碼①的邏輯。
2)mAttachInfo == null,走代碼②的邏輯。
當前View尚未attach到Window時,整個View體系還沒有加載完,mAttachInfo就會為null,表現在Activity中,就是onResume()方法還沒有執行完。反之,mAttachInfo就不會為null。這部分內容會在下一篇文章中詳細講解,這里先知道這個結論。
(1)mAttachInfo != null的情況
對於第一種情況,當看到代碼①時,應該會竊喜一下,因為看到了老熟人Handler,這就是Handler.post(Runnable)方法,我們再熟悉不過了。這里的Runnable會在哪個線程執行,取決於該Handler實例化時使用的哪個線程的Looper。我們繼續跟蹤mHandler是在哪里實例化的。
1 //=============View.AttachInfo=============== 2 /** 3 * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This 4 * handler can be used to pump events in the UI events queue. 5 */ 6 final Handler mHandler; 7 AttachInfo(IWindowSession session, IWindow window, Display display, 8 ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer, 9 Context context) { 10 ...... 11 mViewRootImpl = viewRootImpl; 12 mHandler = handler; 13 ...... 14 }
我們發現mHandler是在實例化AttachInfo時傳入的,該實例就是前面post方法第7行的mAttachInfo。在View類中只有一處給它賦值的地方:
//==============View.java==========
1 void dispatchAttachedToWindow(AttachInfo info, int visibility) { 2 mAttachInfo = info; 3 ...... 4 }
現在的問題就變成了要追蹤dispatchAttachedToWindow方法在哪里調用的,即從哪里把AttachInfo傳進來的。這里我們先停住,看看第二種情況。
(2)mAttachInfo == null的情況
post源碼中第11、12行,對代碼②有說明:推遲Runnable,直到我們知道需要它在哪個線程中運行。代碼②處,看看getRunQueue()的源碼:
1 //=============View.java============ 2 /** 3 * Queue of pending runnables. Used to postpone calls to post() until this 4 * view is attached and has a handler. 5 */ 6 private HandlerActionQueue mRunQueue; 7 /** 8 * Returns the queue of runnable for this view. 9 * ...... 10 */ 11 private HandlerActionQueue getRunQueue() { 12 if (mRunQueue == null) { 13 mRunQueue = new HandlerActionQueue(); 14 } 15 return mRunQueue; 16 }
getRunQueue()是一個單例模式,返回HandlerActionQueue實例mRunQueue。mRunQueue,顧名思義,表示該view的HandlerAction隊列,下面會講到,HandlerAction就是對Runnable的封裝,所以實際就是一個Runnable的隊列。注釋中也提到,它用於推遲post的調用,直到該view被附着到Window並且擁有了一個handler。
HandlerActionQueue的關鍵代碼如下:
1 //============HandlerActionQueue ======== 2 /** 3 * Class used to enqueue pending work from Views when no Handler is attached. 4 * ...... 5 */ 6 public class HandlerActionQueue { 7 private HandlerAction[] mActions; 8 private int mCount; 9 10 public void post(Runnable action) { 11 postDelayed(action, 0); 12 } 13 14 public void postDelayed(Runnable action, long delayMillis) { 15 final HandlerAction handlerAction = new HandlerAction(action, delayMillis); 16 17 synchronized (this) { 18 if (mActions == null) { 19 mActions = new HandlerAction[4]; 20 } 21 mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); 22 mCount++; 23 } 24 } 25 ...... 26 public void executeActions(Handler handler) { 27 synchronized (this) { 28 final HandlerAction[] actions = mActions; 29 for (int i = 0, count = mCount; i < count; i++) { 30 final HandlerAction handlerAction = actions[i]; 31 handler.postDelayed(handlerAction.action, handlerAction.delay); 32 } 33 34 mActions = null; 35 mCount = 0; 36 } 37 } 38 ...... 39 private static class HandlerAction { 40 final Runnable action; 41 final long delay; 42 43 public HandlerAction(Runnable action, long delay) { 44 this.action = action; 45 this.delay = delay; 46 } 47 ...... 48 } 49 }
正如注釋中所說,該類用於在當前view沒有handler附屬時,將來自View的掛起的作業(就是Runnable)加入到隊列中。
當開始執行post()時,實際進入到了第14行的postDelay()中了。第15行中,將Runnable封裝成了HandlerAction,在39行可以看到HandlerAction實際上就是對Runnable的封裝。第21行作用就是將封裝后的Runnable加入到了數組中,具體實現我們不深究了,知道其作用就行,而這個數組就是我們所說的隊列。這個類中post調用邏輯還是比較簡單的,就不啰嗦了。
代碼②處執行的結果就是將post的參數Runnable action添加到View的全局變量mRunQueue中了,這樣就將Runnable任務存儲下來了。那么這些Runnable在什么時候開始執行呢?我們在View類中搜索一下會發現,mRunQueue的真正使用只有一處:
1 //===========View.java============ 2 void dispatchAttachedToWindow(AttachInfo info, int visibility) { 3 ...... 4 // Transfer all pending runnables. 5 if (mRunQueue != null) { 6 mRunQueue.executeActions(info.mHandler); 7 mRunQueue = null; 8 } 9 ...... 10 onAttachedToWindow(); 11 ...... 12 }
這里我們又到dispatchAttachedToWindow()方法了,第一種情況也是到了這個方法就停下來了。我們看看第6行,傳遞的參數也是形參AttachInfo info的mHandler。進入到HandlerActionQueue類的executeActions可以看到,這個方法的作用就是通過傳進來的Handler,來post掉mRunQueue中存儲的所有Runnable,該方法中的邏輯就不多說了,比較簡單。這些Runnable最終在哪個線程運行,就看這個Handler了。
到這里為止,兩種情況就殊途同歸了,最后落腳點都集中到了dispatchAttachedToWindow方法的AttachInfo參數的mHandler屬性了。所以現在的任務就是找到哪里調用了這個方法,mHandler到底是使用的哪個線程的Looper。
3、dispatchAttachedToWindow方法的調用
要搞清這個方法的調用問題,對於部分童鞋來說可能會稍微有點復雜,所以這里單獨用一小節來分析。當然,不想深入研究的童鞋,直接記住本節最后的結論也是可以的,不影響對post機制的理解。
這里需要對框架部分的代碼進行全局搜索,所以需要准備一套系統框架部分的源碼,以及源碼閱讀工具。筆者這里用的是Source Insight來查找的(不會使用童鞋可以學習一下,使用非常廣的源碼閱讀工具,推薦閱讀:【工利其器】必會工具之(一)Source Insight篇)。沒有源碼的童鞋,也可以直接在線查找,直接通過網站的形式來閱讀源碼(不知道如何操作的,推薦閱讀:【安卓本卓】Android系統源碼篇之(一)源碼獲取、源碼目錄結構及源碼閱讀工具簡介第四點,AndroidXRef,使用非常廣)。
全局搜索后的結果如下:

對於這個結果,我們可以首先排除“Boot-image-profile.txt”和“RecyclerView.java”兩個文件(原因不需多說吧...如果真的不知道,那就說明還完全沒有到閱讀這篇文章的時候),跟這個方法調用相關的類就縮小到View,ViewGroup和ViewRootImpl類中。在View.java中與該方法相關的只有如下兩處,顯然可以排除掉View.java。

ViewRootImpl類中的調用如下:
1 //=============ViewRootImpl.java=========== 2 final View.AttachInfo mAttachInfo; 3 ...... 4 public ViewRootImpl(Context context, Display display) { 5 ...... 6 mAttachInfo = new View.AttachInfo(mWindowSession, 7 mWindow, display, this, mHandler, this, context); 8 ...... 9 } 10 11 private void performTraversals() { 12 ...... 13 host.dispatchAttachedToWindow(mAttachInfo, 0); 14 ...... 15 } 16 ...... 17 final ViewRootHandler mHandler = new ViewRootHandler(); 18 ......
追蹤dispatchAttachedToWindow方法的調用,目的是為了找到AttachInfo的實例化,從而找到mHandler的實例化,這段代碼中正好就實現了AttachInfo的實例化,看起來有戲,我們先放這里,繼續下看ViewGroup類中的調用。



在ViewGroup類中,這個方法出現稍微多一點,但是稍微觀察可以發現,根本沒有找到AttachInfo實例化的地方,要么直接使用的View類中的mAttachInfo(因為ViewGroup是View的子類),要么就是圖一中通過傳參得到。而圖一的方法,也是重寫的View的方法,所以這個AttachInfo info實際也是來自View。這樣一來我們也就排除了ViewGroup類中的調用了,原始的調用不在這里面。
通過排除法,最后可以斷定,最原始的調用其實就在ViewRootImpl類中。如果研究過View的繪制流程,那么就會清楚View體系的繪制流程measure,layout,draw就是從ViewRootImpl類的performTraversals開始的,然后就是對DecorView下面的View樹遞歸繪制的(如果對View的繪制流程不明白的,推薦閱讀我的文章:【朝花夕拾】Android自定義View篇之(一)View繪制流程)。這里的dispatchAttachedToWindow方法也正好從這里開始,遞歸遍歷實現各個子View的attach,中途在層層傳遞AttachInfo這個對象。當然,我們在前面介紹View.post源碼時,就看到過如下的注釋:
1 /** 2 * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This 3 * handler can be used to pump events in the UI events queue. 4 */ 5 final Handler mHandler;
這里已經很明確說到了這個mHandler是ViewRootImpl提供的,我們也可以根據這個線索,來確定我們的推斷是正確的。有的人可能會吐槽了,源碼都直接給出了這個說明,那為什么還要花這么多精力追蹤dispatchAttachedToWindow的調用呢,不是浪費時間嗎?答案是:我們是在研究源碼及原理,僅僅限於別人的結論是不夠的,這是一個成長過程。對於不想研究本節過程的童鞋,記住結論即可。
結論:View中dispatchAttachedToWindow的最初調用,在ViewRootImpl類中;重要參數AttachInfo的實例化,也是在ViewRootImpl類中;所有問題的核心mHandler,也來自ViewRootImpl類中。
4、mHandler所在線程問題分析
通過上一節的分析,現在的核心問題就轉化為mHandler的Looper在哪個線程的問題了。在第三節中已經看到mHandler實例化是在ViewRootImpl類實例的時候完成的,且ViewRootHandler類中也沒有指定其Looper。所以,我們現在需要搞清楚,ViewRootImpl是在哪里實例化的,那么就清楚了mHandler所在線程問題。
現在追蹤ViewRootImpl時會發現,只有如下一個地方直接實例化了。
1 //==========WindowManagerGlobal========= 2 public void addView(View view, ViewGroup.LayoutParams params, 3 Display display, Window parentWindow) { 4 ...... 5 ViewRootImpl root; 6 ...... 7 root = new ViewRootImpl(view.getContext(), display); 8 ...... 9 }
到這里,我們就很難繼續追蹤了,因為調用addView的地方太多了,很難全局搜索,我們先在這里停一會。其實到這個addView方法時,我們會看到里面有很多對View view參數的操作,而addView顧名思義,也是在修改UI。而對UI的修改,只能發生主線程中,否則會報錯,這是一個常識問題,所以我們完全可以明確,addView這個方法,就是運行在主線程的。我想,這樣去理解,應該是完全沒有問題的。但是筆者總感覺還差點什么,總覺得這里有點猜測的味道,所以還想一探究竟,看看這個addView方法是否真的就運行在主線程。當然,如果不願意繼續深入探究的童鞋,記住本節最后的結論也沒有問題。
既然現在倒着推導比較困難,那就正着來推,這就需要我們有一定的知識儲備了,需要知道Android的主線程,Activity的啟動流程,以及Window添加view的相關知識。
我們平時所說的主線程,實際上指的就是ActivityThread這個類,它里面有一個main()函數:
//==========ActivityThread===========
1 public static void main(String[] args) { 2 ...... 3 ActivityThread thread = new ActivityThread(); 4 ...... 5 }
看到這里,想必非常親切了,Java中程序啟動的入口函數,到這里就已經進入到Android的主線程了(對於Android的主線程是否就是UI線程這個問題,業內總有些爭議,但官方文檔很多地方的表述為主線程也就是UI線程,既然如此,我們也沒有必要糾結了,把這兩者等同,完全沒有問題)。在main中,實例了一個ActivityThread(),該類中有如下的代碼:
1 //========ActivityThread.java========= 2 ...... 3 final H mH = new H(); 4 ...... 5 private class H extends Handler { 6 public static final int LAUNCH_ACTIVITY = 100; 7 public static final int RESUME_ACTIVITY = 107; 8 public static final int RELAUNCH_ACTIVITY = 126; 9 ...... 10 public void handleMessage(Message msg) { 11 switch (msg.what) { 12 case LAUNCH_ACTIVITY: 13 ...... 14 case RESUME_ACTIVITY: 15 handleResumeActivity(...) 16 ...... 17 case RELAUNCH_ACTIVITY: 18 ...... 19 } 20 ...... 21 final void handleResumeActivity(...) { 22 ...... 23 ViewManager wm = a.getWindowManager(); 24 ...... 25 wm.addView(decor, l); 26 ...... 27 }
其中定義了一個Handler H,現在毫無疑問,mH使用的是主線程的Looper了。如果清楚Activity的啟動流程,就會知道不同場景啟動一個Acitivty時,都會進入到ActivityThread,通過mH來sendMessage,從而直接或間接地在handleMessage回調方法中調用handleResumeActivity(...),顯然,這個方法就運行在主線程中了。
handleResumeActivity(...)的第25行會添加DecorView,即開始添加整個View體系了,我們平時所說的View的繪制流程,就是從這里開始的。這里我們就需要了解ViewManager、WindowManager、WindowManagerImpl和WindowManagerGlobal類之間的關系了,如下所示:

這里用到了系統源碼中常用的一種設計模式——橋接模式,調用WindowManagerImpl中的方法時,實際上是由WindowManagerGlobal對應方法來實現的。所以第25行實際執行的就是WindowManagerGlobal的addView方法,我們需要追蹤的ViewRootImpl實例化就是在這個方法中完成的,前面的源碼顯示了這一點。
結論:這里的關鍵mHandler使用的Looper確實是來自於主線程。
5、mHandler所用Looper線程問題分析狀態圖
上一節分析mHandler所用Looper所在線程問題,其實就是伴隨着啟動Activity並繪制整個View的過程,可以得到如下簡略流程圖:

圖1.5.1 View繪制狀態圖
通過這里的dispatchAttachedToWindow方法,就將mHandler傳遞到了View.post()這個流程中,從而實現了從子線程中切換到主線程更新UI的功能。
6、小結
到這里,使用View.post方法實現在子線程中更新UI的源碼分析就結束了。我們可以看到,實際上底層還是通過Handler從子線程切換到主線程,來實現UI的更新,而整個分析流程其實主要是在做一件事,確定核心Handler使用的是主線程的Looper。
二、使用View.post()獲取View的寬高
看到這個標題的時候,您可能會很納悶,平時工作中不是可以直接通過view.getWidth()(getHeight也一樣,后面不贅述)就能獲取view的寬高嗎,通過View.post()來實現豈不是多此一舉?
1、通過post獲取寬高的示例演示
對於上述疑惑,咱們先來一個例子:
1 private Button mStartBtn; 2 @Override 3 protected void onCreate(Bundle savedInstanceState) { 4 super.onCreate(savedInstanceState); 5 setContentView(R.layout.activity_intent_service); 6 mStartBtn = findViewById(R.id.start); 7 Log.d(TAG, "width-1=" + mStartBtn.getWidth()); 8 mStartBtn.post(new Runnable() { //Runnable ① 9 @Override 10 public void run() { 11 Log.d(TAG, "width-3=" + mStartBtn.getWidth()); 12 } 13 }); 14 mStartBtn.setOnClickListener(new View.OnClickListener() { 15 @Override 16 public void onClick(View v) { 17 Log.d(TAG, "width-4=" + mStartBtn.getWidth()); 18 } 19 }); 20 } 21 @Override 22 protected void onResume() { 23 super.onResume(); 24 Log.d(TAG, "width-2=" + mStartBtn.getWidth()); 25 }
運行后得到如下log:
1 12-10 16:16:49.059 18918-18918/com.example.demos D/postDemo: width-1=0
2 12-10 16:16:49.065 18918-18918/com.example.demos D/postDemo: width-2=0
3 12-10 16:16:49.104 18918-18918/com.example.demos D/postDemo: width-3=264
4 12-10 16:16:53.074 18918-18918/com.example.demos D/postDemo: width-4=264
看到這份log,對於部分童鞋來說,是不是無法淡定了?第7行和第24行為什么得到的值是0呢?后面我們會詳細分析原因,這里咱們先知道會有這個現象,這就是為什么要使用view.post()來獲取view寬高的原因了。第8~13行演示了該方法的使用示例,使用起來也是很簡單的。
2、view繪制的時機問題
通過上面的示例,我們可以看到在onCreate()和onResume()中直接調用view.getWidth()返回值都是0,但是到了view.post()中以及按鈕的onClick()事件中卻可以得到正確值。通過這兩組值可以推斷,view是還沒有繪制的,也還沒有attach到Window中。前面也分析過post源碼,這個post()里面的Runnable是推遲到View attach到Window后才會執行,所以就能得到准確值了。onClick的時機就更晚了,自然也能夠得到正常的值。
所以,出現上述log中的現象,是和view的繪制時機有着密切聯系的。下面重點分析Activity啟動以及View的繪制時機源碼。
1 //=======ActivityThread.java========= 2 final void handleResumeActivity(...) { 3 ...... 4 //這里會執行onStart/onRestart、onResume方法 5 r = performResumeActivity(token, clearHide, reason); 6 ...... 7 //開始添加並繪制view 8 ViewManager wm = a.getWindowManager(); 9 wm.addView(decor, l); 10 ...... 11 }
第5行看字面意思大概也能猜出是和Activity的onResume生命周期方法有關系,實際上這里面會執行onStart/onRestart、onResume這些生命周期方法,下面會詳細分析。第8、9行前面我們講過它的作用,添加DecorView並開始整個View體系的繪制。我們繼續看看第5行具體做了什么:
1 //========ActivityThread.java========= 2 public final ActivityClientRecord performResumeActivity(...){ 3 ...... 4 r.activity.performResume(); 5 ...... 6 } 7 8 //=========Activity.java======== 9 final void performResume() { 10 performRestart(); 11 ...... 12 mInstrumentation.callActivityOnResume(this); 13 ...... 14 } 15 16 final void performRestart() { 17 ...... 18 mInstrumentation.callActivityOnRestart(this); 19 ...... 20 performStart(); 21 ...... 22 } 23 24 final void performStart() { 25 ...... 26 mInstrumentation.callActivityOnStart(this); 27 ...... 28 }
r.activity是一個Activity實例,在Launch Activity時創建。這一塊代碼比較容易理解,實際上就是依次執行了Instrumentation實例的callActivityOnStart \ callActivityOnRestart 、callActivityOnResume方法。
1 //==========Instrumentation.java======== 2 public void callActivityOnStart(Activity activity) { 3 activity.onStart(); 4 } 5 ...... 6 public void callActivityOnRestart(Activity activity) { 7 activity.onRestart(); 8 } 9 ...... 10 public void callActivityOnResume(Activity activity) { 11 activity.onResume(); 12 ...... 13 }
這三個方法最終調用的就是對應的如下方法:
1 //===========Activity.java======== 2 protected void onStart() { 3 ...... 4 } 5 6 protected void onReStart() { 7 ...... 8 } 9 10 protected void onResume() { 11 ...... 12 }
這就很熟悉了,我們自定義Activity時需要重寫的生命周期方法(我們知道onStart 和onRestart方法只會執行一個,這里沒有具體分析什么條件下執行哪一個,這不是本文的重點)都是重寫的這些方法。整個流程大致可以表示為如下的狀態圖:

現在就很明白了,雖然我們平時是在onCreate()方法中調用setView()來加載xml中的布局,但真正將整個view樹添加到Window中是在onResume之后的,添加整個view的過程包含了view的繪制流程。這樣我們就明白了第一點中,為什么在onCreate()和onResume()方法中直接調用getWidth()得到的值為0了。
3、dispatchAttachedToWindow()發生在view繪制前,如何能獲取真實的寬高?
前面在研究dispatchAttachedToWindow()方法執行時機的時候,我們已經明確了它是在ViewRootImpl類中的performTraversals()方法中調用的。但是細心的童鞋會發現如下的情況:
1 //=========ViewRootImpl========代碼2.3.1 2 private void performTraversals() { 3 ...... 4 host.dispatchAttachedToWindow(mAttachInfo, 0); 5 ...... 6 performMeasure(...); 7 ...... 8 performLayout(...); 9 ...... 10 performDraw(); 11 ...... 12 }
dispatchAttachedToWindow()方法居然發生在View的繪制流程執行之前!不知道這有沒有驚到您的下巴呢?我們前面在講post源碼時就講過,如果view還沒有attach到Window,post里面的Runnable會推遲到dispatchAttachedToWindow()方法執行時才會再執行。既然view都還沒有繪制,那Runnable中又如何能夠獲取到真實的寬高呢?
這里先看一下performTraversals()方法的調用流程:
1 //========ViewRootImpl=========代碼2.3.2 2 Choreographer mChoreographer; 3 4 public ViewRootImpl(...){ 5 ...... 6 mChoreographer = Choreographer.getInstance(); 7 ...... 8 } 9 10 void scheduleTraversals() { 11 ...... 12 mChoreographer.postCallback( 13 Choreographer.CALLBACK_TRAVERSAL, 14 mTraversalRunnable, null); 15 ...... 16 } 17 18 final class TraversalRunnable implements Runnable { //Runnable ② 19 @Override 20 public void run() { 21 doTraversal(); 22 } 23 } 24 25 final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); 26 27 void doTraversal() { 28 ...... 29 performTraversals(); 30 ...... 31 }
在前文圖1.5.1 View繪制狀態圖中顯示了從activity啟動到performTraversals()方法執行的大致流程,部分流程的對應代碼如上。進入第12行看看其執行邏輯:
1 //=========Choreographer=======代碼2.3.3 2 private final FrameHandler mHandler; 3 4 private Choreographer(Looper looper, int vsyncSource) { 5 mLooper = looper; 6 mHandler = new FrameHandler(looper); 7 ...... 8 } 9 10 private final class FrameHandler extends Handler { 11 public FrameHandler(Looper looper) { 12 super(looper); 13 } 14 15 @Override 16 public void handleMessage(Message msg) { 17 ...... 18 } 19 } 20 21 public void postCallback(int callbackType, Runnable action, Object token) { 22 postCallbackDelayed(callbackType, action, token, 0); 23 } 24 25 public void postCallbackDelayed(int callbackType, 26 Runnable action, Object token, long delayMillis) { 27 ...... 28 postCallbackDelayedInternal(callbackType, action, token, delayMillis); 29 } 30 31 private void postCallbackDelayedInternal(int callbackType, 32 Object action, Object token, long delayMillis) { 33 ...... 34 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); 35 ...... 36 mHandler.sendMessageAtTime(msg, dueTime); 37 }
可見代碼2.3.2處實際就是封裝的一個Handler來執行一個Runnable任務的,而且從代碼2.3.3來看,這個封裝的FrameHandler mHandler所使用的looper來自於第4行的構造函數。那么這里就需要分析本次使用的Choreographer實例傳入的looper了。
代碼2.3.2中第6行,繼續追蹤:
1 //==========Choreographer=========代碼2.3.4 2 3 // Thread local storage for the choreographer. 4 private static final ThreadLocal<Choreographer> sThreadInstance = 5 new ThreadLocal<Choreographer>() { 6 @Override 7 protected Choreographer initialValue() { 8 Looper looper = Looper.myLooper(); 9 if (looper == null) { 10 throw new IllegalStateException("The current thread must have a looper!") 11 } 12 Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); 13 if (looper == Looper.getMainLooper()) { 14 mMainInstance = choreographer; 15 } 16 return choreographer; 17 } 18 }; 19 20 /** 21 * Gets the choreographer for the calling thread. Must be called from 22 * a thread that already has a {@link android.os.Looper} associated with it. 23 * 24 * @return The choreographer for this thread. 25 * @throws IllegalStateException if the thread does not have a looper. 26 */ 27 public static Choreographer getInstance() { 28 return sThreadInstance.get(); 29 }
這里用到了ThreadLocal,本文不展開講它的實現機制,只需要知道本段代碼中,它會為每一個線程存儲一個對應線程下的Choreographer實例。本次調用Choreographer.getInstance()是在主線程中(前面第一節中已經分析過ViewRootImpl是在主線程中實例化的)完成的,所以此次獲取的是主線程下的Choreographer實例,在代碼2.3.4中傳入的looper就是主線程的looper,所以代碼2.3.3中的Handler使用的就是主線程的looper。
分析到這里,就明白了,整個performTraversals()方法,是作為Runnable②的一部分被封裝成Message,被加入主線程的MessageQueue中的。當執行到代碼2.3.1第3行dispatchAttachedToWindow()時,它會再向主線程的MessageQueue中添加一個封裝了本節開頭實例中Runnable①的Message。由於Looper.loop()取MessageQueue中的Message執行時是有順序的,所以Runnable②會先執行完畢,然后才會執行Runnable①,也就是說實際執行中,Runnable①中的view.getWidth()是發生在performMeasure() — performLayout() — performDraw()之后的。

此時,疑惑就解開了,這里我們總結一下本小結的結論:performTraversals()方法中,dispatchAttachedToWindow()所產生的Runnable①,是在view繪制流程結束后才執行的。
4、小結
其實本節主要就是要搞明白一個難點,dispatchAttachedToWindow執行Runnable與View的繪制執行的時間順序問題,最后落腳點還是到了Handler上面。
結語
本文大部分的篇幅都是在分析Handler問題,由此可見Handler在整個流程中的重要地位。整個分析過程還穿插了ActivityThread、Activity啟動、WMS添加view、View的繪制流程等相關知識點,讀者可以根據自己掌握的情況選擇性地閱讀。當然,源碼中有很多知識點是環環相扣的,各種知識點都需要平時多積累,希望讀者們遇到問題不要輕易放過,這就是一個打怪升級的過程。
【朝花夕拾】Android自定義View篇之(一)View繪制流程
【安卓本卓】Android系統源碼篇之(一)源碼獲取、源碼目錄結構及源碼閱讀工具簡介
由於筆者經驗和水平有限,如有描述不當或不准確的地方,請多多指教,謝謝!
