Android消息機制
引言
從開發的角度說,Handler是Android消息機制的上層接口,使得在開發的過程中只需要和Handler交互即可。
Handler的使用過程很簡單,通過它可以將一個任務切換到Handler所在的線程中進行.Handler一般被用于更新UI。因為Android不允許在子線程中更新UI,所以子線程需要通過Handler發送信息到主線程更新UI。
Handler不僅可以用于在其他的線程處理的過程中更新UI,還可以處理其他的一些線程間的通信。
概述
簡介
Android的消息機制實際上指的是Handler的消息機制,而Handler的運行需要通過底層的MessageQueue和Looper的支撐。MessageQueue即消息隊列,實際上由單鏈表而不是隊列儲存消息列表。Looper即循環,也可以被理解為消息循環,它不能去處理消息,而Looper就填補了這個功能,Looper通過運行一個死循環,從MessageQueue中獲取信息,如果有則處理信息,否則就一直阻塞等待。Looper中還有一個特殊的概念,ThreadLocal,ThreadLocal用于在每個線程中儲存數據。Handler創建的時候會默認使用當前線程的Looper對象來構造消息循環系統,而Android通過ThreadLocal可以在不同的線程中互不干擾的獲取數據的特點,通過ThreadLocal輕松地獲取每個線程的Looper。
需要注意的是,線程默認是沒有Looper的,如果需要使用Handler則必須為線程創建Looper。而Android中的UI線程,即ActivityThread,在被創建的時候會初始化Looper,所以不需要再創建Looper就可以使用Handler。
使用Handler的原因
Android的消息機制中,Handler和MessageQueue、Looper實際上是一個整體,而在開發的過程中,開發者只是比較多的接觸Handler而已。而Handler的主要作用是將一個任務切換到它Handler所在的線程執行,Android提供這樣的一個功能是它規定UI訪問只能在主線程中執行,如果在子線程中訪問UI,那么程序就會拋出異常,究其原因,是因為ViewRootImpl通過checkThread方法對UI操作做了驗證。
因為Android的這個限制,所以必須在主線程中訪問UI,但是Android又不建議在主線程中進行耗時的操作,否則會導致程序出現ANR。假如我們需要進行耗時工作,如從網絡中獲取數據并更新UI,這個時候必須新建一個子線程來完成網絡請求操作,獲取完畢后需要在子線程中更新UI,但子線程中無法直接更新UI,所以這個時候必須通過Handler來將UI的工作切換到主線程中,解決了子線程中無法訪問UI的矛盾。這就是為什么Android提供Handler。
不允許在子線程中更新UI的原因
Android系統不允許在子線程中更新UI的原因是,Android中的UI并不是線程安全的,而如果在多線程中并發訪問可能會導致UI控件處于不可預期的狀態。Android系統也不對UI控件的訪問提供鎖機制,原因有二,首先添加鎖機制會讓UI訪問的邏輯變得復雜,其次鎖機制會因為其會阻塞某些線程的執行而降低UI訪問的效率。而鑒于這兩個問題,最簡單高效的方法就是通過單線程模型來處理UI操作,對開發者來說不不至于太復雜,只需通過Handler來切換UI訪問的線程即可。
Looper的構造
Handler創建的時候會采用當前線程的Looper來構造內部的消息循環系統,如果當前的線程沒有Looper,那么就會拋出RuntimeException異常。
解決這個問題只需要為當前的線程創建Looper即可,或者在一個有Looper的線程中創建Handler。
而當Handler創建完畢后,這個時候其內部的Looper以及MessageQueue就可以和Handler一起協同工作了,然后通過Handler的post方法將一個Runnbale投遞到Handler內部的Looper進行處理,而其實post方法的內部也是通過send方法完成的。
以下是send方法的工作流程。
當Handler的send方法被調用時,它會調用MessageQueue的enqueueMessage方法將這個消息放入隊列中,然后Looper發現有新消息到來時,就會處理這個消息。最終消息中的Runnable或者Handler的方法就會被調用。注意Handler默認創建的Looper是運行在創建Handler的線程中的,這樣一來Handler中的業務邏輯就被切換到所在的線程中處理了。
下圖是Handler的工作過程圖。
Android的消息機制分析
ThreadLocal
ThreadLocal是一個線程內部的數據儲存類,通過它可以在線程的內部存儲信息。并在指定的線程中可以獲取到儲存的數據,對于其他線程來說則無法獲取到。
對于Hanlder來說,它需要獲取當前線程的Looper對象,很顯然Looper的作用域就是線程并且不同的線程具有不同的Looper,這個時候通過ThreadLocal就可以輕松地實現Looper在線程中的獲取。
消息隊列的工作原理
消息隊列即Android中的MessageQueue,MessageQueue主要包含兩個操作,插入和讀取,而讀取操作本身會伴隨著刪除操作,插入和獨缺對應的方法分別為enqueueMessage和next,其中enqueueMessage的作用是往消息隊列里面插入一條信息,而next的作用是從消息隊列中插入一條信息,而next的作用是從消息隊列中取出一條信息并且將其從消息隊列中移除。而MessageQueue的內部實現是通過一個單鏈表的數據結構來維護消息列表,單鏈表在插入和刪除上復雜度為O(1)。
enqueueMessage的內部實現實際上就是單鏈表的插入操作,將Message插入到隊列中去,其優先級和delay字段有關。
next的內部實現包含著一個無限循環的方法,如果內部的消息隊列中沒有信息或者存在信息但是還在delay中,那么將一直阻塞,而當有新消息到來時,next將返回這條信息,并從單鏈表中移除。
Looper的工作原理
Looper在Android的消息機制中扮演著消息循環的角色,具體來說就是它不停地從MessageQueue中查看是否有新消息,如果有新消息則立刻進行處理,說否則就一直阻塞。Looper的構造方法如下,在構造方法中它會創建一個MeesageQueue消息隊列,然后將當前線程的對象保存起來。
private Looper(boolean quitAllowed) {
mQueue = new MesssageQueue(quitAllowed);
mThread = Thread.currentThread();
}
而Handler的工作需要Looper,在沒有Looper的線程創建Handler就會拋出異常,而為一個沒有Looper的線程創建Looper只需要通過調用Looper.prepare()即可,接著通過Looper.loop()開啟消息循環,代碼如下。
new Thread("Thread#2") {
@Override
public void run() {
Looper.prepare();
Hanlder handler = new Handler() {
// some code
}
Looper.loop():
}
}
Looper除了prepare方法外,還提供了prepareMainLooper方法,這個方法主要是給主線程也就是ActivityThread創建Looper使用的,其本質也是通過prepare方法來實現的。由于主線程的Looper方法比較特殊,所以Looper提供了一個getMainLooper方法,通過它可以在任何地方獲取到主線程的Looper。
Looper也是可以退出的,Looper提供了quit和quitSafely來退出一個Looper,兩者的在于,quit會直接退出Looper,而quitSafely只是設定一個退出標記,然后把消息隊列的已有印象處理完畢才完全地退出。Looper退出后,通過Handler發送的消息會失敗,這個時候Handler的send方法會返回false。
在子線程中,如果手動為其創建了Looper,那么在所有事情都完成以后應該調用quit來終止消息循環,否則這個子線程則一直處于等待的狀態。而當子線程推出Looper后,這個線程會立刻停止,因此建議當子線程不需要再運行的時候終止Looper。
Loop.loop()
Looper最重要的是loop方法,只有調用了loop方法,消息循環系統才真正的開始。 Looper的loop方法的工作過程也比較好理解,loop是一個死循環,唯一跳出循環的方式是MessageQueue的next方法返回了null。而當Loooper的quit方法被調用時,Looper就會調用MessageQueue的quit或者quitSafely通知消息隊列退出,當消息隊列被標記為退出狀態時,它的next方法就會返回null。也就是說,Looper必須退出,否則Looper方法將無限循環下去,loop方法會MessageQueue的next方法來獲取新消息,而next是一個阻塞的操作,當沒有消息的時候,next方法會一直阻塞,這同時也導致loop方法一直阻塞。如果MessageQueue的next方法返回了新消息,Looper就會處理這條消息"msg.target.dispatchmessage(msg)
,這里的msg.target
是發送這條消息的Handler對象,這樣handler發送的消息最終又交給了它的dispatchMessage方法處理。但是這里不同的是,Handler的dispatchMessage方法是在創建Hanlder所使用的Looper中執行的,這就成功地將代碼邏輯切換到指定的線程中執行了。
Handler的工作原理
Handler的工作主要包含信息的發送和接收過程。消息的發送可以通過post的一系列方法以及send的一系列方法來實現,post的一系列方法最終是通過send的一系列方法來實現的。
Handler內部源碼中發送消息的過程僅僅是向消息隊列里面插入了一條信息,MessageQueue的next方法就會返回這條消息給Looper,Looper收到消息后就開始處理了,最終消息由Looper交由Handler處理,即Handler的dispatchMessage方法會被調用,這時Handler就進入了處理消息的階段。
Handler處理消息的過程
首先,先檢查Message的callback是否為null,不為null則通過handleCallback來處理消息。Message的callback是一個Runnable對象,實際上就是通過Handler的post方法所傳遞的Runnable參數。handleCallback的邏輯也是很簡單的,代碼如下
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
privare static void handlerCallback(Message msg) {
msg.callback.run();
}
其次,檢查mCallback是否為null,不為null就調用mCallback的handlerMessage方法來處理消息。Callback是個接口,代碼定義如下。
public interface Callback {
public boolean handleMessage(Message msg);
}
而通過Callback可以采用如下方式創建Handler對象:Handler handler = new Hanler(callback)
。使用Callback的意義在于不用創建一個Hanlder的實例而不用派生Handler的子類,并且可以在Callback的handleMessage通過返回false時,通過Handler的handleMessage方法來處理。
日常開發中,也可以通過派生Handler子類的方式并重寫handleMessage方法來處理具體的消息,而Callback通過另一種方式也可以實現。
而最后,通過調用Handler的handlerMessage方法來處理消息。
Handler處理消息的過程如圖下。
指定Looper
Handler還有一個特殊的構造方法,可以指定一個Looper來構造Handler,代碼如下。
public Hanlder(Looper looper) {
this(looper. null, false)
}
而Handler的默認構造方法是public Handler()
,這個構造方法會調用以下代碼的構造方法。
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看出假如當前線程沒有Looper的話,就會拋出異常。
主線程的消息循環
Android的主線程就是Activity,主線程的入口方法是main,在main方法中系統會通過Looper.prepareMainLooper()來創建主線程的Looper以及MessageQueue,并通過Looper.loop()來開啟消息循環。
主線程的消息循環開始了以后,AcitivyThread還需要一個Handler來和消息隊列進行交互,這個Hanlder就是ActivityThread.H,它內部定義了一組消息類型,主要包含四大組件的啟動和停止等過程。
AcitivityThread通過ApplicationThread和AMS進行進程間通信,AMS以進程間通信方式完成AcitivityThread的請求后回調ApplicationThread中的Binder方法,然后ApplicationThrad會向H發送消息,H收到消息后會將ApplicationThread中的邏輯切換到ActivityThread中執行,即切換到主線程中去執行,這個過程就是主線程的消息循環過程。