在日常開發中,不管出於什麼目的,我們可能都會用到Handler來非同步更新UI,有時是為了將一些費時的操作放到非同步執行緒去處理,然後通過Handler將資料更新到UI執行緒,有時是為了在子執行緒裡更新UI,種種原因,反正我們最後都是選擇了直接的Handler+Message組合或者AsyncTask,而瞭解AsyncTask的同學都知道,AsyncTask內部就是通過Handler和Message實現的執行緒間通訊,所以我們還是要好好熟悉一下這位老朋友
為什麼要使用Handler
我們使用Handler更多是為了非同步操作,那麼為什麼非得要非同步操作呢,直接在子執行緒更新UI不行嗎?答案當然是不行的,不然Handler存在的意義是什麼,這一切都是因為UI的控制元件都不是執行緒安全的,如果允許併發訪問,那控制元件的狀態就是未知的了,可能你剛獲取完一個控制元件的狀態,準備進行一些操作,這時候另一個執行緒改變了這個控制元件的狀態,那就很麻煩了,解決這種問題的方法一般有兩種:1.加鎖,在對控制元件進行操作的時候先加鎖,不允許其他人訪問,這樣的問題是會導致UI更新的效率會很差,而且容易堵塞某些執行緒,因為他需要等上一個訪問這個控制元件的執行緒釋放鎖;所以Android選擇的是第二種方法,只允許在一個執行緒內對UI控制元件進行更新,這個執行緒就是主執行緒,這也就是為什麼我們在對UI元件進行更新的時候,必須回到主執行緒去操作。
Handler、MessageQueue和Looper
我用了Handler,不過總是對它一知半解,一直停留在會用的程度,以致於別人在提到MessageQueue和Looper的時候,我竟是一臉懵逼,然後就是無盡的嘲笑,連這些都不知道,你還用什麼Handler,回去搬磚吧!我相信也有很多的Android初學者跟我有同樣的問題,所以在這裡,我想先跟大家詳細介紹一下這幾個概念
Looper
Looper是一個迴圈類,Handler初始化的時候必須依靠Looper,換言之,Handler初始化的那個執行緒,必須有Looper,否則就會報異常資訊,Looper初始化的過程用程式碼表示就是如下所示:
1 2 3 4 5 6 7 8 |
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Handler handler = new Handler(); Looper.loop(); } }).start(); |
我們之所以之前沒有感受到它的存在,是因為在主執行緒,ActivityThread預設會把Looper初始化好,prepare以後,當前執行緒就會變成一個Looper執行緒,增加一個Looper物件,而Looper會維護著一個MessageQueue物件,用來存放Handler傳送的Message。
其實Looper物件的本質是ThreadLocal,是一個執行緒內部的資料儲存類,有興趣的同學可以去搜尋詳細瞭解下
Looper的作用就是從訊息佇列裡取出訊息然後進行處理,沒有訊息處理的時候,它就堵塞在那裡,一有新的訊息進來,它就從訊息佇列中取出,處理。它就是一個任勞任怨的搬運工,它的特點在於它是跟它的執行緒是繫結的,處理訊息的過程也是在Looper所在的執行緒去處理的,這就是為什麼Handler在其他執行緒發的訊息,最後也是在主執行緒處理,因為它只跟Handler初始化的執行緒有關,確切的說是Handler初始化的時候繫結的Looper所在的執行緒有關。這樣非同步的目的就達到了。
如上面程式碼顯示的,Looper主要有兩個方法:prepare()用來初始化當前執行緒的Looper,loop()用來開始Looper的迴圈,其實還有兩個不是很常用的方法:quit()和quitSafely(),這兩個方法的不同在於,quit會立即停止迴圈,而quitSafely會在MessageQueue為空以後才跳出迴圈。
MessageQueue
MessageQueue是一個訊息佇列,用來存放Handler傳送的訊息,主要有兩個操作:新增和讀取,讀取的同時伴隨著訊息從訊息佇列的移除,分別對應的方法就是:enqueueMessage(Message msg, long when)和next(),enqueueMessage方法就是簡單地將一條訊息插入MessageQueue,next方法相對會複雜一點,它是一個死迴圈,返回值是一個Message,它的返回值就是用作Looper處理用的,所以延遲傳送訊息的主要處理步驟就是在next()方法裡,因為next()之前的操作都是記錄message延遲到什麼時間,然後設定給Message.when,next()之後的Looper是不處理延遲時間的,會直接呼叫Handler裡的處理邏輯,在next()內部,當沒有訊息返回時,next()就會堵塞,直到有新的訊息過來再返回,然後又進入堵塞狀態,等待新訊息進來。
Message算是一個比較簡單的類,它本質上是一輛馬車,只是用來裝載資訊,然後在Handler,MessageQueue和Looper之前傳遞,主要有這麼幾個屬性:
- what:int型,最主要的屬性,用於指定訊息的型別,這算是Message唯一一個必須指定的值
- arg0,arg1:兩個int型值,一般用來傳遞一些progress,或者一些簡單的整型
- obj:可以傳遞一些複雜一些的object
- data:Bundle型,這個就不用多解釋了,傳遞較多種資料的時候肯定會用到它
- callback:Runable型,Handler.post(Runnable)內部就是設定的這個屬性,這個一般不會手動設定,但是也會用到,只是我們感覺不到,下面會詳細解釋,用於覆蓋Handler的預設處理邏輯
大致就這些屬性了,詳細瞭解Message的基本結構還是有必要的,傳遞什麼資料,用什麼方法,對以後的實際開發會有一些幫助。
Handler
說了這麼多鋪墊,準備工作算是差不多了,Handler也就要上場了,其實Looper,MessageQueue和Message,除了Message有些接觸以外,其他兩個在實際開發中其實是見不到的,而Handler則是開發者接觸最多的,因為幾乎所有的處理邏輯都是寫在Handler裡的,傳送訊息和處理訊息也都算是Handler接手的,但是我們平常是怎麼建立一個Handler的呢? 無外乎兩種:
- 建立一個自定義的Handler繼承Handler,並重寫handlerMessage()方法。
- 直接使用預設的Handler類,但是在新建Handler物件時,傳入一個Callback物件。
上面說的這兩種方法,我用的比較多的是第二種方法,因為用起來更加靈活一點。
獲得一個Message:obtain()
在傳送一個Message之前 ,我們肯定要先得到一個Message物件,可以直接new一個Message物件出來,但是這種方法並不推薦,更推薦用Message的obtain方法,它類似一個執行緒池,建立了一個Message池,如果有閒置的Message就直接返回,不然就新建一個,用完以後,返回訊息池,這種方法大大減少了當有大量Message物件而產生的垃圾回收問題,而且有obtain方法有多種形式,基本能滿足我們的一些需求:
- obtain() 返回一個空的Message
- obtain(Message msg) copy一個message並返回
- obtain(Handler h) 返回一個設定了target的message,設定了這個屬性以後message就可以使用sendToTarget()方法,其實內部呼叫的就是Handler.sendMessage(this)
- obtain(Handler h, Callback callback) 同上,並設定message的callback屬性
- obtain(Handler h, int what) 同obtain(Handler h),並設定message的what屬性
- obtain(Handler h, int what, Object obj)
- obtain(Handler h ,int what, int arg0, int arg1)
- obtain(Handler h, int what, int arg0, int arg1, Object obj)
這些方法都大同小異,無非就是預設幾個屬性,不過,以後儘量還是要用obtain方法,算不上方便快捷吧,但至少算的上優雅。
傳送一個Message:sendMessage()
一個Message已經準備好了,蓄勢待發,接下來的工作就是把它發射出去,這時候就要交給Handler,呼叫它的sendMessage(Message msg),其實我們也可以不去費心得到一個Message物件直接用Handler.sendEmptyMessage(int what),傳送一個空message,而且Handler還有sendMessageDelay和sendMessageAtTime方法,這些有什麼不同點,可以直接看原始碼
1 2 3 4 |
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } |
1 2 3 4 |
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); } |
1 2 3 4 5 6 7 |
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } |
從這些程式碼中,我們可以看出來,sendEmptyMessage和sendMessage都是呼叫的sendMessageDelay方法,只不過sendEmptyMessage是在方法內部用Message.obtain()方法得到了一個空message,然後sendMessageDelay方法內部又是呼叫的sendMessageAtTime,所以殊途同歸,最後只要看sendMessageAtTime就好了:
1 2 3 4 5 6 7 8 9 10 |
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); } |
沒錯,又回來了,看到了enqueueMessage方法,就是這樣,一條message被插入MessageQueue佇列,接下來的視情節就交給Looper,它會迴圈呼叫MessageQueue的next方法,而next會在合適的時機返回要處理的Message,交給Looper處理。
訊息處理的過程
具體訊息處理的過程又是怎樣的呢?Handler的post方法和sendMessage有什麼不同?都將在這一節揭曉。其實post的Runnable物件,有沒有覺得很熟悉,剛才提到的Message也有一個屬性是Runnable,就是callback,正如我們所料想的那樣,post其實也是傳送的一個message,和sendMessage一樣,只不過它給message設定了屬性CallBack,還是貼最誠實的原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } |
從原始碼中我們可以看到,當message的callback屬性不為空的話,就會呼叫handlerCallback(msg)方法,而這個方法內部則是直接執行的message的callback,訊息處理結束;當message的callback為空時,就是這個message是一個普通的message,會先檢視mCallback是否存在,如果存在的話,嘗試呼叫mCallback的handlerMessage方法,根據返回值決定是否再呼叫Handler的handlerMessage方法,而這個mCallback變數就是我們在新建handler時,傳入的那個Runable物件:
1 2 3 4 5 6 |
Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { return false; } }); |
現在終於理解為什麼在這個地方傳入一個Runable物件也能攔截下訊息處理了,至此,訊息處理的優先順序就出來了:
Message的callback>handler的Callback>Handler的handlerMessage
可能存在的問題:記憶體洩露
在使用Handler的時候,之前一直沒有關心過,直到有一次發生了記憶體洩露才注意到這一點,在Activity銷燬的時候要記得把handler的訊息佇列清空,或者在使用handler時,把handler設成靜態變數或者弱引用。
Message上限
之前我記得有個同事問我Message是否有上限,我當時答不上來,後來去搜尋也沒搜尋到,只查到Message.obtain()的訊息池上限是10個,但是MessageQueue得上限還是不知道,或許是他問錯了,或者我理解錯了,還是這個問題是存在的,只是我還沒找到答案,有知道的看官可以留言指導一下。
The End
雖然現在RxJava和Agera用的比較火,但是關於Handler的一些東西,作為開發者,我們還是有必要知道的,通過看任玉剛大大的書,還有網上的各種資料,最後算是對Handler和Message有了個一知半解,有不對的地方,希望大家批評指正。
熬夜寫部落格,真是一種挑戰,困得要死,想著都要放棄了,明天再寫,但是我知道以我性格一旦推下去,基本上就廢掉了,於是冰箱拿了瓶啤酒,夜裡3點,終於寫完了。