提到消息機制,想必大家都不陌生吧,在日常開發中不可避免要涉及到這方面的内容。從開發的角度來說,Handler是Android的消息機制的上層接口,這使得在開發過程中隻需要和Handler互動即可。Handler的使用過程很簡單,通過它可以輕松地将一個任務切換到Handler所在的線程中去執行。由于Android的開發規範的限制,我們并不能在子線程中通路UI控件,否則就會觸發程式異常,這個時候通過Handler就可以将更新的UI的操作切換到主線程中執行,是以從本質上來來說,Handler并不是專門用于更新UI的,它隻是常被開發者用來更新UI。
Android中的消息機制主要指Handler的運作機制,Handler的運作需要底層的MessageQueue和Looper的支撐。MessageQueue翻譯過來就是消息隊列,它内部存儲了一組消息,以隊列的形式對外提供插入和删除的過程,雖然叫做消息隊列,但是它内部存儲結構并不是真正的隊列,而是采用單連結清單的資料結構來存儲消息清單,Looper翻譯過來就是循環,這裡可以了解為消息循環。由于MessageQueue隻是一個消息的存儲單元,它不能去處理消息,而Looper填補了這個功能,Looper會無限循環的形式去查找是否有新的消息,如果有的話就處理消息,否則就中一直等待。Looper中還有一個特殊的概念,那就是ThreadLocal,Threadlocal并不是線程,它的作用是可以在每個線程中存儲資料。
我們知道,Handler建立的時候會采用目前線程的Looper來構造消息循環系統,那麼Handler内部如何擷取到目前線程的Looper呢,這就要使用ThreadLocal了,ThreadLocal可以在不同的線程中互不幹擾地存儲并提供資料,通過ThreadLocal可以輕松擷取每個線程的Looper。需要注意的是,線程是預設沒有Looper的,如果需要使用Handler就必須為線程建立Looper,我們經常提到的主線程,也叫UI線程,它就是ActivityThread,ActivityThread被建立時就會初始化Looper,這也是在主線程中預設可以使用Handler的原因。
我們知道Handler的主要作用是将一個任務切換到某個指定的線程中去執行,那麼Android為什麼要提供這個功能呢,這是因為Android規定通路UI隻能在主線程中進行,如果子線程中通路UI,那麼程式就會抛出異常。
這是ViewRootImpl的checkThread方法,從這段代碼就可以看出,如果不在目前線程,就會抛出異常。同時呢,Android不建議在主線程中進行耗時操作, 否則會導緻程式無法響應,即ANR。那麼系統為什麼允許在子線程中通路UI呢,這是因為Android中的UI控件并不是線程安全,它同時也延伸了Java系統中預設程序的話會産生預設的單線程習慣,當使用者點選、滑動等事件操作時,UI線程是負責分發的,統一管理會更高效點,采取單線程來處理UI操作,對于開發者來說也不是很麻煩,隻是需要通過Handler切換下UI通路的執行線程即可。
簡單描述下Handler的工作原理,Handler建立完畢後,這個時候内部的Looper以及MessageQueue就可以和Handler一起協同工作,然後通過Handler的post方法将一個Runnable投遞到Handler内部的Looper中去處理,也可以通過Handler的send方法發送一個消息,這個消息同樣會在Looper中去處理。
先看下整體的架構圖:

Looper有一個MessageQueue消息隊列
MessageQueue有一組待處理的Message
Message中有一個用于處理消息的Handler
Handler中有Looper和MessageQueue
Looper的工作原理
Looper在Android的消息機制扮演着消息循環的角色,具體來說就是它會不停地從MessageQueue中檢視是否有新消息過來,如果有新的消息的就會立刻處理,否則就一直阻塞在那裡。首先看下它的構造方法:
在構造方法中,它會建立一個MessageQueue對象,然後将目前線程的對象給儲存起來。我們知道,Handler的工作需要Looper,沒有Looper線程就會報錯,那麼如何為一個線程建立Looper呢,有以下方法:
從中我們可以看出,每個線程隻有一個Looper,多建立一個會報錯,然後prepareMainLooper這個方法主要給主線程也就是ActivityThread建立Looper使用,其本質也是通過prepare方法來實作的。
Looper提供了quit和quitSafely方法退出一個Looper,這兩者最主要差別在于一個設定退出标記,一個是把消息隊列中的已有消息處理完畢後才安全地退出。
當然還有Looper的loop方法是最核心的。
這個也比較好了解,loop方法是一個死循環,唯一跳出循環的方式就是MessageQueue的next方法傳回了null。Looper就會調用MessageQueue的quit或者quitSafely方法來通知消息隊列退出,當消息隊列被标記為退出狀态時,它的next方法就會傳回null,也就是說looper必須退出,否則loop方法就會無限循序下去。
MessageQueue工作原理
在Android中MessageQueue主要包含兩個操作:插入和讀取。讀取操作本身會伴随着删除操作,插入和讀取對應的方法分别為enqueueMessage和next,其中enqueueMessage的作用是往消息隊列中插入一條消息,而next的作用是從消息隊列中取出一條消息并将其從消息隊列中移除。在MessageQueue内部通過一個單連結清單的資料結構來維護消息清單,單連結清單在插入和删除上比較有優勢。
看下enqueueMessage代碼:
主要操作其實就是單連結清單的插入操作。
看下next代碼:
可以發現next方法就是一個無限循環的方法,如果消息隊列中沒有消息,那麼next方法就會一直阻塞在這裡,當有新消息到來時,next方法會傳回這條消息并将其從單連結清單中移除。
Message
每個消息用<code>Message</code>表示,<code>Message</code>主要包含以下内容:
資料類型
成員變量
解釋
int
what
消息類别
long
when
消息觸發時間
arg1
參數1
arg2
參數2
Object
obj
消息内容
Handler
target
消息響應方
Runnable
callback
回調方法
建立消息的過程,就是填充消息的上述内容的一項或多項。
消息池
在代碼中,可能經常看到recycle()方法,咋一看,可能是在做虛拟機的gc()相關的工作,其實不然,這是用于把消息加入到消息池的作用。這樣的好處是,當消息池不為空時,可以直接從消息池中擷取Message對象,而不是直接建立,提高效率。
靜态變量<code>sPool</code>的資料類型為Message,通過next成員變量,維護一個消息池;靜态變量<code>MAX_POOL_SIZE</code>代表消息池的可用大小;消息池的預設大小為50。
消息池常用的操作方法是obtain()和recycle()。
obtain(),從消息池取Message,都是把消息池表頭的Message取走,再把表頭指向next。
recycle(),将Message加入到消息池的過程,都是把Message加到連結清單的表頭。
Handler工作原理
Handler的工作主要包含消息的發送和接收過程。消息發送可以通過post的一系列的方法以及send的一系列方法來實作,post其實也是通過send的方法來實作的。
看下Handler的構造方法。
從中可以看到關聯MessageQueue、Looper,是以在Handler之前Looper要prepare先,如果沒有Looper的話,就會抛出“Can't create handler inside thread that has not called Looper.prepare()”這句話。
從中可以看出,最終都是調用sendMessageAtTime/sendMessageAtFrontOfQueue方法,進而執行enqueueMessage方法,最終把消息發送到MessageQueue隊列中。
那麼消息又是如何在Handler處理的呢?
通過dispatchMessage來處理消息的。
ThreadLocal工作原理
ThreadLocal是一個 線程内部的資料存儲類,通過它可以在指定的線程中存儲資料,資料存儲以後,隻有在指定線程中可以擷取到存儲的資料,對于其他線程來說是無法擷取到資料。在日常開發中用到ThreadLocal的場景很少,但是在某些特殊的場景下,通過ThreadLocal可以輕松地實作一些看起來很複雜的功能,這一點在Android源碼中也有所展現,比如Looper、ActivityThread以及AMS中都用到ThreadLocal。
<code>ThreadLocal.set(T value)</code>:将value存儲到目前線程的TLS區域。
在set方法中,首先會通過values方法來擷取目前線程的ThreadLocal資料,通過put方式去擷取。
<code>ThreadLocal.get()</code>:擷取目前線程TLS區域的資料。
get方法同樣是取出目前線程的localValues對象,如果這個對象為null,那麼就傳回初始值。
在Looper源碼中,有這麼一句:
從ThreadLocal的set和get方法可以看出,它們所操作的對象都是目前線程的localValues對象的table數組,是以在不同線程中通路同一個ThreadLocal的set和get方法,它們對ThreadLocal所做的讀/寫操作僅限于各自線程的内部,這也就是為什麼ThreadLocal可以在多個線程中互不幹擾地存儲和修改資料。
是以,整體來說,Handler、Looper、MessageQueue、Message這三者之間的關系如下:
Android的主線程就是ActivityThread,主線程的入口方法在main,在main方法中系統會通過Looper.prepareMainLooper方法來建立主線程的Looper以及MessageQueue,并通過Looper.loop方法來開啟主線程的消息循環。
主線程的消息循環開始以後,ActivityThread還需要一個Handler來和消息隊列進行互動,這個Handler就是ActivityThread.H,它内部定義了一組消息類型,主要包括了四大元件的啟動和停止等過程。
ActivityThread通過ApplicationThread和AMS進行程序間通信,AMS以程序間通信的方式完成ActivityThread的請求回調ApplicationThread中Binder方法然後ApplicationThread向H發送消息,H收到消息後會将ApplicationThread的邏輯切換到ActivityThread中去執行,即切換到主線程中去執行,整個過程就是主線程的消息循環模型。
HandlerThread類的源碼:
可以看到HandlerThread繼承于Thread類,在擷取Looper對象時候,當線程已經啟動,則等待直到looper建立完成才能擷取,從本質上看HandlerThread是對Thread的封裝,主要用途在于多個線程的通信,會有同步的問題,那麼Android對此直接提供了HandlerThread類。
HandlerThread實戰
在HandlerThread線程中運作Loop()方法,在其他線程中通過Handler發送消息到HandlerThread線程。通過wait/notifyAll的方式,有效地解決了多線程的同步問題。從源碼中我們也可以看到當looper沒擷取成功就會阻塞,然後有運作完就會去喚醒所有阻塞的線程。
源于對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中可以看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關系
4,Android圖檔加載庫了解
5,談談Android運作時權限了解
6,EventBus初了解
7,Android 常見工具類
8,對于Fragment的一些了解
9,Android 四大元件之 " Activity "
10,Android 四大元件之" Service "
11,Android 四大元件之“ BroadcastReceiver "
12,Android 四大元件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的了解
15,Android 生命周期和啟動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工作原理
19,了解 Window 和 WindowManager
20,Activity 啟動過程分析
21,Service 啟動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 架構的一些了解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試