Android進階;Handler訊息機制詳解

安卓開發高階技術分享發表於2019-01-18

我們知道,Android裡面的執行緒做了這樣的分工:主執行緒不做耗時操作,子執行緒不更新UI。


這是因為Android系統是個單執行緒模型,系統給App分配的程式裡,只有一個主執行緒,主執行緒是App程式的核心,所有的元件都在主執行緒裡實現、排程和管理,元件的繪製、互動操作和生命週期回撥都是主執行緒處理的,這個執行緒如果出現阻塞、或多執行緒死鎖、或CPU飢餓,導致事件處理時間過長,甚至沒有機會得到處理,系統就會處理為ANR,強制關閉App。


所以主執行緒不能做耗時操作,耗時操作需要我們在子執行緒裡處理。同時,Android不允許子執行緒重新整理UI,因為Android無法保證多個執行緒同步操作,這樣子執行緒和主執行緒之間就需要做協調,比如子執行緒從網路伺服器拿來資料要展示到UI,就需要告知主執行緒去向UI裡重新整理資料,這就需要Handler訊息機制了。

主執行緒、Looper和訊息佇列

Handler訊息機制是圍繞一個訊息佇列展開的,系統啟動App時,App程式會啟動ActivityThread主執行緒,主執行緒啟動時,會建立一個Looper來迴圈處理訊息,ActivityThread的main函式主要就是讓這個Looper做死迴圈,如果訊息迴圈停止了,App也就停止了:

//ActivityThread原始碼
Looper.loop();
//主執行緒會一直loop,如果出了looper,就直接拋異常,關閉app
throw new RuntimeException("Main thread loop unexpectedly exited");

Looper自己會去建立和管理一個訊息佇列,然後就開始不斷地處理佇列裡的訊息,子執行緒向訊息佇列裡發訊息,就可以和主執行緒通訊了。
訊息佇列為空時,主執行緒會休眠,釋放CPU資源。(主執行緒大部分時間都在休眠)

 

 

 

但是,Android為了確保主執行緒安全,Looper的訊息佇列是ThreadLocal的,禁止其他執行緒訪問,所以子執行緒不能直接向訊息佇列裡發訊息。

 

 


這樣,就需要在主執行緒裡放一個Handler,Handler在建立時,會引用所線上程的Looper,進而獲得Looper中的訊息佇列,這樣,我們就可以用Handler來幫忙處理訊息了。

 


這樣一來,處理訊息的主力就是Handler了。

Message

為了配合Handler,Message裡面有這樣幾個主要屬性:

  1. what:int值,存放訊息ID(Send方式)
  2. callback:Runnable物件(Post方式,所以如果訊息的callback非空,就直接處理runnable)
  3. target:Handler物件,指向Handler(訊息還是丟給Handler處理)
  4. data: Bundle物件,存放業務資料
  5. next:Message物件,存放佇列中的下一個Message(鏈式)

這幾個主要的屬性在Handler訊息機制中非常重要,Handler要使用這幾個屬性才能正常運轉。

Handler

Handler有兩個作用:

  1. 發訊息,子執行緒通過Handler,向訊息佇列發訊息
    發訊息有兩種方式,send一個訊息ID(msg.what),或者post一個Runnable(msg.callback),這兩種方式最終都是包裝為一個Message,通過一個sendMessageAtTime函式,把訊息推進佇列裡。
  2. 處理訊息,Looper通過Handler,逐個處理佇列中的訊息
    Looper在逐個處理訊息時,拿到每個msg後,會去執行msg.target.dispatchMessage(msg),這裡面的target其實就是Handler,這裡會做一個判斷,如果msg.callback不為空,說明訊息裡有Runnable(post進來的),就呼叫msg.callback.run,整個過程其實就是在Handler裡呼叫Runnable的run函式;
 mHandler.post(new Runnable() {
       @Override
       public void run() {
       }
 });

如果msg.callback為空,說明沒有Runnable(send進來的),就呼叫handlemessage,也就是執行Handler的handlemessage函式;

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

這樣,Handler訊息機制就能正常運轉了。
Handler訊息機制多見於主執行緒UI重新整理,不過這並不是它的主業,它其實是用來實現Android執行緒間通訊的,所以只要是跨執行緒的通訊,都需要用到Handler,我們可以自己在子執行緒裡寫Looper。
如果我們要在子執行緒裡使用Looper,可以使用HandlerThread,它會幫我們維護一個Looper;如果我們直接用Thread,就需要自己在run函式裡做Looper的prepare和loop。

HanderThread

HanderThread就是handler+thread+looper,它有這麼幾個特點:
1.本質上是一個Thread
2.已經準備好了Looper,可以做Looper迴圈
3.本身是工作執行緒,其looper不在UI主執行緒,可以在handleMessage方法中執行非同步任務
4.雖然不能併發處理多工(因為是一個執行緒,不是執行緒池),但是效能損耗小
5.因為已經有Looper,所以可以線上程裡建立和使用Handler

其他機制

Android的其他非同步處理機制,其實都與Handler機制有關:

  • View.post(Runnable)/View.postDelayed(Runnable, long),這個機制一般也是走主執行緒的Handler處理,不過,如果View所在的檢視樹沒有attach到window,就會自己維護一個RunQueue,在檢視樹performTraversals時,把RunQueue裡的訊息全部取出來,讓檢視樹集中處理掉。
  • Activity.runOnUiThread(Runnable),這個函式其實是也是走的Handler機制,如果當前執行緒就是UI執行緒,則立即執行;如果當前執行緒不是UI執行緒,就把Runnable發給UI執行緒的訊息佇列,排隊執行。runOnUiThread的原始碼大概是這樣的:
if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
  • AsyncTask,AsyncTask實際上是個封裝了執行緒池和handler的輕量級非同步框架,它雖然用一個執行緒池做非同步處理,但是處理結果總要提交回主執行緒,實際上就是使用一個Handler(AsyncTask有一個InternalHandler物件)來向主執行緒發訊息(MESSAGE_POST_RESULT),並在主執行緒處理訊息(handleMessage),這就要求把Handler放在主執行緒裡,以便繫結主執行緒的Looper和訊息佇列,所以AsyncTask需要在主執行緒裡建立和呼叫。
    有時候,AsyncTask持有的Activity物件可能會被銷燬重建(如螢幕旋轉),這就會導致AsyncTask指向的Activity物件無效,新的Activity無法接收AsyncTask的onPostExecute(),出現結果丟失的現象。

Handler的記憶體洩露

我們使用Handler時,如果用內部類或匿名內部類的方式去使用,就會遇到內部類持有外部物件導致的記憶體洩露(非靜態內部類,在編譯後的建構函式中會引用所在的外部類),解決方式有兩種:

  1. 使用靜態內部Handler類(+弱引用)。
  2. 在元件的onDestroy中,呼叫handler.removecallback()方法。

同樣的,由於AsyncTask封裝了handler,也會有同樣的記憶體洩露問題,解決方法也是使用靜態內部類+弱引用,或在onDestroy時及時呼叫asyncTask.cancel()方法,否則就會記憶體洩露,甚至導致崩潰(AsyncTask找不到要處理的View物件)。

更詳細深入的handler通訊機制手寫視訊資料;

1.Handler框架總覽
2.4個關鍵類的一致性問題
3.Android 跨執行緒通訊的深層原理
4.生產者-消費者模式應用
5.ThreadLocal原始碼解析
6.Handler框架手寫實現
7.如何在子執行緒更新UI?

可以加下面的Android技術進階群免費獲取。

附錄;

附錄一;Android高階技術大綱

附錄二;Android進階實戰技術視訊

 

獲取方式;

加Android進階群;701740775。即可前往免費領取。麻煩備註一下csdn領取資料

相關文章