Android消息機制源碼解析

案例簡介

        我們做Android開發時經常會遇到一個問題,我們都知道更新UI只能在主線程中更新,如果在子線程也就是非UI線程中更新UI就會報錯,拋出異常:

android.view.ViewRoot.CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

       爲什麼會出現這種問題呢,因爲Android的View組件並不是線程安全的,那麼如果多個線程併發修改UI組件,那麼就會引起線程安全問題,所以Android的解決辦法是禁止非UI線程修改UI組件,在APP第一次啓動時會同時啓動一條主線程也就是UI線程。

      那麼如果需要子線程來修改UI怎麼辦呢,Android提供了handler這個類來實現,handler的運行機制也就是Android的消息機制,handler的運行機制需要底層的MessageQueue和Looper的支撐,handler機制是面試中最經常問到的問題之一,所以可見其重要性,所以我們需要深入理解掌握。

案例解析

      首先我們介紹一下handler機制中各個主要使用到的類。

                                     

                                                                              圖1.Handler機制結構圖

      我們通過一個圖來簡單的先分析一下handler的運行機制

  1. Runnable和Message可以被壓入某個MessageQueue隊列中,注意一般情況下某個類型的MessageQueue只允許保存相同類型的Object,圖中爲了區分Runnable和Message纔將它們混放在同一個MessageQueue中,實際源碼中需要先對Runnable進行相應的轉換
  2. Looper循環地去MQ隊列中去取出一個個Message,然後傳給Handler進行處理,如初循環往復,假如隊列爲空,那麼它會進入休眠。
  3. Handler是真正“處理事情”的地方,它利用自身的處理機制,對傳入的Message進行相應的處理併產生最終結果。

      以上就是Handler的簡單運行機制一句話來概括就是:

Looper不斷獲取MessageQueue中的Message,然後交由Handler來處理。

     接下來我們的一系列分析不論多麼複雜都是基於這句話來展開分析的。

                   

                                                                             圖2.handler機制類圖

       這是整個流程的類圖關係,現在我們來一一分析整個流程中各個使用到的類。

Handler

      Handler主要有兩方面的作用。

  1. 處理Message,這是它作爲“處理者”的本職所在
  2. 將某個Message壓入MessageQueue中

     首先我們來看一下Handler類中的方法。

                                               

                                                                         圖3.handler中的方法分類

       從圖上可以看到最開始的1-5方法都是用來創建返回Message對象的,當然我們也可以自己創建Message對象,這裏比較簡單不做分析。

      接下來我們來分析handler的第一個功能也就是處理Message,第一個功能主要是由兩個方法來完成。

  1. dispatchMessage(Message msg) 這個方法是對Message進行分發
  2. handleMessage(Message msg)   這個方法是對Message進行處理

       之前我們說過Looper從MessageQueue中取出一個Message後,首先調用handler.dispatchMessage進行消息派發,後者則根據具體的策略來將Message分發給相應的責任人。

       我們看一下這個dispatchMessage()方法。

                                            

                                                                 圖4.dispatchMessage方法

      Handler派發Message主要包括以下幾個步驟:

  1. 判斷Message.callback(Runnable runnable)是否爲空。在不爲空的情況下,將優先通過callback來處理,也就是執行callback.run()方法執行任務
  2. 判斷Handler.mCallback是否爲空,在不爲空的情況下,調用mCallback.handleMessage,這個需要自己實現Runnable方法的handleMessage方法
  3. 如果前兩個對象都不存在,才調用Handler.handleMessage,這個纔是我們平時經常用到的創建Handler對象時實現的handleMessage方法

       Message.callback不爲空的情況下,優先通過callback來處理,callback是Runnable類型,然後再判斷handler的mCallBack是否爲空,不爲空就調用mCallback.handleMessage(msg)如果前兩個對象都是是空才調用handler.handleMessage(msg)。由此可見,Handler的擴展子類可以通過重載dispatchMessage或者handleMessage來改變他的默認行爲。

      上面是handler的第一個功能處理以及派發Message事件,那麼它的另一個功能將Message壓入MessageQueue中。我們先看一下相應的功能函數聲明如下;

Post系列:

                                                       

                                                                                 圖5.post方法

Send系列:

                                                  

                                                                                圖6.send方法

        post以及send兩個系列的共同點是它們都負責將某個消息壓入MessageQueue中,區別在於後者處理的函數參數直接是Message,而post則需要先把其他系列的“零散”信息轉換成Message再調用send系列函數來執行下一步。

                

                                                                             圖7.消息發送過程

        我們挑選第一個Post函數來分析源碼,其中邏輯流程如圖所示。

                                     

                                                                                圖8.post源碼分析

       首先將Runnable對象使用getPostMessage(r)將其封裝成一個Message,然後通過調用對應的send函數把他壓入到MessageQueue中;

                                           

                                                                 圖9.getPostMessage源碼分析

       可以看到getPostMessage(r)方法內部先是通過Message.obtain()方法生成一個Message,然後將Runnable賦值給Message的callback變量。

      當準備好Message之後,程序調用sendMessageDelayed來執行下一步操作,這個函數可以設定延遲多長時間後再將消息發送,其內部又通過當前時間加上延時時長計算出具體是在哪個時間點發送消息sendMessageAtTime()。

       sendMessageAtTime函數的源碼如下:

                               

                                                                       圖10.sendMessageAtTime源碼

       我們看到handler內部對應有一個MessageQueue這個也就是handler對應的MessageQueue,爲什麼這個MessageQueue隊列是handler對應的,以及這個MessageQueue是怎麼生成的,後面我們講完handler機制的幾大類之後單獨整理。之後就會調用enqueueMessage()方法將Message壓入到MessageQueue隊列中。

       這樣我們就整理出了一條由Runnable組成的Message通過handler成功壓入到了MessageQueue中,不僅僅這樣最後looper中取出Message然後再利用Message綁定的handler來分發dispatchMessage(Message msg)以及處理Message的handleMessage(Message msg)。

MessageQueue

       上面handler將Message壓入到MessageQueue隊列中,然後由Looper不停的從其中獲取Message調用handler的方法對Message進行處理。因爲MessageQueue是一個隊列所以我們下面就簡單的介紹一下MessageQueue。

       我們先看一下Message中主要的幾個方法。

  1. 新建隊列

      新建隊列由MessageQueue的構造函數以及nativeInit方法組成

                                                     

                                                                圖11.MessageQueue構造函數

                                                 

                                                                      圖12.nativeInit函數

        調用Android底層C++的native方法創建出一個NativeMessageQueue對象,然後直接複製給MessageQueue的成員變量mPtr,這個mPtr其實就是存放MessageQueue的引用地址,通過這種方式將Java層的對象與Native層的對象關聯在了

  1. 元素入隊

       這裏元素入隊是調用enqueueMessage()方法,我們源碼看一下整個過程。

                                                                      圖13.enqueueMessage源碼分析

        首先會將msg的執行等待時間when賦值,然後將當前表頭元素mMessage賦值給元素p,接着利用if(p==null || when==0 || when<p.when)判斷如果當前隊列表頭元素爲空或者要插入元素的等待時間爲0或者插入元素的等待時間小於表頭元素,那麼就執行插入操作,將該元素插入到隊列表頭。那麼如果if判斷爲false那麼就執行插入到隊列中某個位置的操作,具體是利用for循環找出第一個等待時間小於當前p元素的位置,然後跳出for循環執行插入到該p元素之前的位置的操作。

  1. 元素出隊

       元素出隊的方法是next(),我們還是看一下源碼。

                         

                                                                            圖14.next方法源碼分析

        MessageQueue是一個單向鏈表,Message對象有個next字段保存列表中的下一個,MessageQueue中的mMessages保存鏈表的第一個元素。

       我們先看註釋一,循環體內首先調用nativepollOnce(),這是一個native方法,實際作用就是通過Native層的MessageQueue阻塞nextPollTimeoutMills毫秒的時間。

  1. 如果nextPollTimeoutMills=-1,一直阻塞不會超時
  2. 若果nextPollTimeoutMills=0,不會阻塞,立即返回
  3. 如果nextPollTimeoutMills>0,最長則是nextPollTimeoutMills毫秒(超時),如果期間有程序喚醒會立即返回暫時知道這些就可以繼續往下分析

       然後我們看到就是一個無限for循環來查找一下元素,我們看到註釋二和註釋三,如果msg.traget=null,則進入do-while循環通過msg.isAsynchronous()找出第一個異步消息,我們可以看到正常加入隊列enqueueMessage時都是會設置target等於當前handler的但是我們我們調用postSyncBarrier()就會在MessageQueue中插入一個Message,這個Message的作用是插入一個消息屏障,這個屏障之後的所有同步消息都不會執行,即使時間已經到了也不會執行,具體應用場景可以查看ViewRootImpl中的scheduleTraversals方法,這個在繪製繪製部分提到過,它在繪圖之前會插入一個消息屏障只有繪製之後纔會調用removeSyncBarrier來移除。

       然後我們繼續看,註釋四中會判斷msg是不是空如果不爲空,判斷執行時間是否到達,如果沒有到達那麼就會設置阻塞時間nextPollTimeoutMills,然後再次進入for循環,這個時候就會調用nativepollOnce()阻塞隊列,如果到達了時間,那麼進入註釋五,因爲prevMsg不爲空所以這裏肯定是異步消息,將msg的前一個元素的next設置爲msg.next將msg出隊

       如果prevMsg說明是當前不是異步消息,那麼將當前隊列所指的位置mMessage設置爲下一個元素,將當前msg出隊,之後就是註釋七中msg的上一個鏈表元素已經被移除,再將msg的下一個鏈表元素移除,然後將該msg返回出去,最後我們看一下注釋八和註釋九,如果msg爲空,那麼就阻塞隊列,如果mQuitting爲true,那麼就退出銷燬隊列。

Looper

      Looper有點類似於發動機,正是因爲它的推動,handler甚至整個程序才成爲活源之水,不斷地處理新的消息。

     Looper是用來使線程中的消息循環起來的,默認情況下當我們創建一個新的線程的時候,這個線程裏面是沒有消息隊列MessageQueue的,而且一個線程只有一個MessageQueue,爲了能夠讓線程能夠綁定唯一的一個消息隊列,我們需要藉助於Looper,我們先看一下一個例子來看看Looper是怎樣讓一個線程綁定唯一的一個消息隊列。

                                   

                                                                          圖15.Looper使用例子

        我們看到在子線程中創建handler時會採用當前線程的Looper來構建內部的消息循環系統,如果當前線程沒有Looper,就會報錯。

                   

                                                                          圖16.Looper報錯源碼

        我們修改一下代碼這樣就可以正確運行了。

                                        

                                                               圖17.修改後的Looper運行實例

     這段代碼我們可以分爲三個步驟:

  1. Looper的準備工作(prepare):
  2. 創建處理消息的handler:
  3. Looper開始運作(loop):

     下面我們來分析一下這個三個步驟,首先我們看到Looper的構造函數是private,那麼就不能通過直接new Looper()的方法來創建對象,我們先來看Looper()構造函數。

                                                

                                                                             圖18.Looper構造函數

        可以看到Looper構造函數中生成了一個MessageQueue隊列的對象,這裏就是創建MessageQueue隊列的地方,然後我們再看看Looper.prepare()方法。

                                                        

                                                                         圖19.prepare()方法源碼

       在看看prepare()方法。

                        

                                                                            圖20.prepare()方法源碼

        這裏我們看到一個變量sThreadLocal,我們看到這裏使用到new Looper(quitAllowed)生成了一個Looper對象並且這裏調用Looper的構造函數也同時生成了一個MessageQueue對象,我們看一下sThreadLocal.set(new Looper(quitAllowed))的set()方法。

                                               

                                                                            圖21.set()源碼分析

       這裏是通過getMap(t)方法得到ThreadLocalMap,我們進入getMap(t)方法查看。

                                                                

                                                                              圖22.getMap()源碼分析

       可以看到這個方法是返回當前線程中的threadLocals這個成員變量,然後調用這個Map的set方法將Looper存儲到這個ThreadLocalMap中。

       這裏用到了一個ThreadLocalMap來存儲Looper,這個類我們介紹一下,因爲它的設計比較特殊。

       ThreadLocal是一個創建線程局部變量的類,通常情況下,我們創建的變量是可以在任何一個線程訪問並修改的,因爲同一個進程中線程是共享資源的,而使用ThreadLocalMap創建的變量只能被當前線程訪問,其他線程則無法訪問和修改。

       而使用ThreadLocal大致邏輯就是,首先在每個Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值爲當前ThreadLocal變量,value是變量副本也就是我們傳入的Looper值。

      可以看到在Looper中有一個成員變量sThreadLocals,這個值是一個ThreadLocal,然後調用這個變量的set方法,set方法內部中其實是先獲取當前線程,然後獲取當前線程中的成員變量ThreadLocal.ThreadLocalMap,然後存入Looper其實就是存入到當前ThreadLocalMap中的,鍵值對爲當前的ThreadLocal和Looper,我們這裏需要分清楚的是Looper中的成員變量是ThreadLocal,Thread中的成員變量是ThreadLocalMap,ThreadLocal是作爲ThreadLocalMap的鍵值存儲的。

      接下來看步驟三Looper.loop(),這個是Looper類最重要的方法之一,只有調用了該方法消息循環系統纔會真正起作用,我們從源碼角度來看一下。

           

                                                                                    圖23.loop()源碼分析

       上面代碼是省略過之後的代碼,包含的是loop()主要的邏輯,簡單整理一下過程就是先是獲取到Looper,爲空就是代表沒有調用過prepare()方法直接報錯,如果不爲空那麼就獲取Looper中的MessageQueue消息隊列,之後就是使用一個無限for循環  不停的從MessageQueue隊列中獲取Message,如果獲取的Message爲空那麼就直接退出循環,不爲空就調用Message中的handler處理Message消息。

案例總結

       以上我們就講整個Handler機制涉及到的類全部詳細的介紹了一遍,現在我們再次完整的梳理一下整個流程,我們用一張圖來總結一下這個過程。

                                                                       圖24.handler機制整體結構圖

         首先在創建handler之前我們需要準備Looper也就是Looper.prepare()方法這個方法會調用Looper的sThreadLocals.set(new Looper())方法將Looper存儲到當前線程中的ThreadLocalMap中,以當前Looper的ThreadLocal爲鍵new出來的Looper爲值存儲起來,這裏需要指出在new Looper()的時候回創建出與當前線程綁定的MessageQueue存儲到當前Looper中,準備Looper的工作做完之後就是需要new出Handler對象,在handler的構造函數中會調用Looper.myLooper()方法這個方法會從ThreadLocal中獲取到當前線程的Looper,得到這個Looper之後就可以直接獲取到這個Looper中的MessageQueue綁定到Handler中。

         然後就是實現Handler的handleMessage()方法處理Message消息,之後就是調用Looper.loop()方法來開啓無限循環的獲取MessageQueue中的消息,這個時候我們消息機制所需要的所有的類都已經生成了。

        當我們在一個線程中給另一個線程發送消息時我們是通過拿到我們所需要發送消息的線程的Handler對象,然後調用這個handler對象的postxxx()或者sendxx()系列方法,將Message消息通過MessageQueue的enqueueMessage方法壓入到我們所需要發送消息的線程中的MessageQueue消息隊列,當前handler中有一個Looper.loop()的無限循環獲取MessageQueue中的消息,獲取到之後就是通過當前Message.target.dispatchMessage(msg)處理這個msg消息。

   上面就是完整的handler機制的代碼邏輯的實現。      

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章