預(yù)熱
在寫這篇文章前我不止一次的問自己,網(wǎng)上分析Handler機(jī)制原理的文章那么多,為啥還要畫蛇添足啊?不是說前人們寫的文章不好,我就是覺得他們寫的不細(xì),有些點(diǎn)不講清楚,邏輯很難通順的,每次我學(xué)個(gè)什么東西時(shí)遇到這種情況都賊難受。
我們處在這么一個(gè)被層層封裝的世界里搞開發(fā),被真相所蒙蔽本來就是一件很痛苦的事,如果分享知識(shí)的人再搞出了一堆天書般的“經(jīng)驗(yàn)總結(jié)”,這得多打擊求學(xué)者的心?。?/p>
此篇文章為我【 Android Framework層分析】系列的第一篇。說了這么一大堆就當(dāng)作個(gè)序吧。作為在探索技術(shù)之路中飽經(jīng)風(fēng)霜中一員,希望我能謹(jǐn)記前人的經(jīng)驗(yàn)教訓(xùn),多走點(diǎn)心,思路清晰的分析,盡量寫高質(zhì)量的文章,既對(duì)得起自己也對(duì)得起他人。
本文所分析的內(nèi)容大概有以下幾個(gè)模塊:
- 開發(fā)人員最初設(shè)計(jì)Handler時(shí)想要解決什么問題
- Handler 為我們提供了哪些功能以及如何使用
- Handler實(shí)現(xiàn)原理的理論分析
- Handler實(shí)現(xiàn)原理的源碼分析
- Android UI線程中Handler的特殊操作
文章很長(zhǎng),但思路是循序漸進(jìn)的,如果你能堅(jiān)持讀完我相信肯定不會(huì)讓你失望,只要跟著文章的思路走,就不會(huì)有需要反復(fù)讀好幾遍加深理解的地方。建議剛讀的時(shí)候先快速瀏覽一遍,在腦海中宏觀的理清邏輯,有想不通的細(xì)節(jié)再仔細(xì)研究,如有不明白的地方或覺得我分析有誤的地方歡迎留言評(píng)論。
設(shè)計(jì)Handler 的初衷
在分析Handler之前,需要先搞清楚兩個(gè)概念:
- 同步與異步的區(qū)別
- 線程與多線程的概念
講道理上述兩點(diǎn)中每個(gè)拿出來都涉及到很多東西,一方面我學(xué)的也不是很深不好意思獻(xiàn)丑另一方面這不是本文重點(diǎn),所以我就當(dāng)老鐵們都知道啦(嘿嘿一笑)。

Java多線程通信
Java中有很多種方法實(shí)現(xiàn)線程之間相互通信訪問數(shù)據(jù),大概先簡(jiǎn)單的介紹兩個(gè)典型的,就不上代碼了。
通過
synchronized關(guān)鍵字以“上鎖”機(jī)制實(shí)現(xiàn)線程間的通信。多個(gè)線程持有同一個(gè)對(duì)象,他們可以訪問同一個(gè)共享變量,利用synchronized“上鎖”機(jī)制,哪個(gè)線程拿到了鎖,它就可以對(duì)共享變量進(jìn)行修改,從而實(shí)現(xiàn)了通信。使用
Object類的wait/notify機(jī)制,執(zhí)行代碼obj.wait();后這個(gè)對(duì)象obj所在的線程進(jìn)入阻塞狀態(tài),直到其他線程調(diào)用了obj.notify();方法后線程才會(huì)被喚醒。
Android多線程的特殊性
在上面的兩個(gè)Java多線程通信的方法中都有一個(gè)共同的特點(diǎn),那就是線程的阻塞。利用synchronized機(jī)制拿不到鎖的線程需要等拿到鎖了才會(huì)繼續(xù)執(zhí)行操作,obj.wait();需要等obj.notify();才會(huì)繼續(xù)執(zhí)行操作。
雖然Android系統(tǒng)是由Java封裝的,但是由于Android系統(tǒng)的特殊性,Google的開發(fā)人員對(duì)Android線程的設(shè)計(jì)進(jìn)行了改造。他們把啟動(dòng)APP時(shí)運(yùn)行的主線程定義為UI線程。
UI線程負(fù)責(zé)所有你能想到的所有的跟界面相關(guān)的操作,例如分發(fā)繪制事件,分發(fā)交互事件等可多了。由于其特殊性Android系統(tǒng)強(qiáng)制要求以下兩點(diǎn):
為保持用戶界面流暢UI線程不能被阻塞,如果線程阻塞界面會(huì)卡死,若干秒后Android系統(tǒng)拋出ANR。
除UI線程外其他線程不可執(zhí)行UI操作。
(此處只是簡(jiǎn)單介紹一下UI線程,后面會(huì)有專門一節(jié)分析Android UI線程。)
Android 多線程通信
既然UI線程中不能被阻塞,那么查詢數(shù)據(jù)庫(kù)和訪問網(wǎng)絡(luò)這類的耗時(shí)操作肯定就不能在UI線程中執(zhí)行了,我們就需要單獨(dú)開個(gè)線程操作。
但是除UI線程外其他線程又不可執(zhí)行UI操作,最后還是要回到UI線程更新UI,這就需要多線程之間的通信。
可Java中線程間通信又都是阻塞式方法,所以傳統(tǒng)的Java多線程通信方式在Android中并不適用。
為此Google開發(fā)人員就不得不設(shè)計(jì)一套UI線程與Worker線程通信的方法。既能實(shí)現(xiàn)多線程之間的通信,又能完美解決UI線程不能被阻塞的問題。具體方法有以下幾類:
view.post(Runnable action)系列,通過View對(duì)象引用切換回UI線程。activity.runOnUiThread(Runnable action),通過Activity對(duì)象引用切換回UI線程。AsyncTask,內(nèi)部封裝了UI線程與Worker線程切換的操作。Handler,本文的主角,異步消息處理機(jī)制,多線程通信。
小結(jié)
說到了這里應(yīng)該大概明白了當(dāng)初設(shè)計(jì)Handler的初衷。
由于Android系統(tǒng)的特殊性創(chuàng)造了UI線程。
由于UI線程的特殊性創(chuàng)造了若干個(gè)UI線程與Worker線程通信的方法。
在這若干個(gè)線程通信方法中就包含了Handler。
Handler就是針對(duì)Android系統(tǒng)中與UI線程通信而專門設(shè)計(jì)的多線程通信機(jī)制。
Handler 提供的一些方法
Handler API方法相對(duì)其他Android API方法來說算少了的,不過要是都拿出挨個(gè)介紹還是很多,所以這里給出官方的API文檔地址:不用搭梯子就能看的 Android官網(wǎng) - Handler API。
在介紹Handler的消息處理前還有一件事,為了線程傳遞數(shù)據(jù)時(shí)方便處理,開發(fā)人員為Handler專門設(shè)計(jì)了一個(gè)傳遞消息的載體Message,這樣就能讓傳輸數(shù)據(jù)比較規(guī)范化,它有兩個(gè)十分重要且常用的屬性:
-
int waht;開發(fā)者自定義的消息標(biāo)識(shí),我們可以根據(jù)它來區(qū)分不同的消息,例如switch(message.what)。 -
Object obj;開發(fā)者想要傳遞的數(shù)據(jù),具體什么類型的都可以。
同樣,此處就是簡(jiǎn)單介紹一下Message,后面會(huì)詳細(xì)分析的。
Handler 提供的方法有些我們是用不到的,能用到的方法大體分為發(fā)送消息,處理消息和切換線程三類。
發(fā)送消息類方法
1. sendEmptyMessage
boolean sendEmptyMessage (int what)
發(fā)送一個(gè)只有消息標(biāo)識(shí)waht的空消息。該方法適用于不需要傳遞具體消息只是單獨(dú)的發(fā)通知時(shí)。
2. sendEmptyMessageAtTime
boolean sendEmptyMessageAtTime (int what, long uptimeMillis)
在具體指定的時(shí)間uptimeMillis發(fā)送一個(gè)只有消息標(biāo)識(shí)waht的空消息。uptimeMillis為系統(tǒng)開機(jī)到當(dāng)前的時(shí)間(毫秒)。
3. sendEmptyMessageDelayed
boolean sendEmptyMessageDelayed (int what, long delayMillis)
在過了delayMillis毫秒之后發(fā)送一個(gè)只有消息標(biāo)識(shí)waht的空消息。
4. sendMessage
boolean sendMessage (Message msg)
發(fā)送一條消息。
5. sendMessageAtTime
boolean sendMessageAtTime (Message msg, long uptimeMillis)
在具體指定的時(shí)間uptimeMillis發(fā)送一條消息。uptimeMillis為系統(tǒng)開機(jī)到當(dāng)前的時(shí)間(毫秒)。
6. sendMessageDelayed
boolean sendMessageDelayed (Message msg, long sendMessageDelayed )
在過了delayMillis毫秒之后發(fā)送一條消息。
處理消息類方法
handleMessage
void handleMessage (Message msg)
負(fù)責(zé)接受消息,所有發(fā)送的消息都會(huì)返回該方法,注意!必須Override這個(gè)方法才能接收消息。
切換線程類方法
1. post
boolean post (Runnable r)
Runnable r 會(huì)運(yùn)行在handler對(duì)象被創(chuàng)建的線程上。當(dāng)我們?cè)赨I線程創(chuàng)建了Hnadler對(duì)象,在Worker線程調(diào)用handler.post()方法時(shí),Runnable就會(huì)運(yùn)行在UI線程中。
2. postAtTime
boolean postAtTime (Runnable r, long uptimeMillis)
在具體指定的時(shí)間uptimeMillis讓Runnable運(yùn)行在Handler對(duì)象被創(chuàng)建的線程中。
3. postDelayed
boolean postDelayed(Runnable r, long delayMillis)
在具體指定的時(shí)間delayMillis之后讓Runnable運(yùn)行在Handler對(duì)象被創(chuàng)建的線程中。
使用Handler
在上節(jié)方法介紹中出現(xiàn)了XXXAtTime(long uptimeMillis)和XXXDelayed(long delayMillis)這兩類控制時(shí)間的方法,兩類方法的時(shí)間參數(shù)雖然都是毫秒,但是代表的意義卻不一樣:
-
XXXDelayed(long delayMillis)中的時(shí)間參數(shù)是指從當(dāng)前時(shí)間開始delayMillis毫秒后。 -
XXXAtTime(long uptimeMillis)中的時(shí)間參數(shù)是指從系統(tǒng)開機(jī)算起uptimeMillis毫秒后。
利用靜態(tài)方法SystemClock.uptimeMillis()可以得到從系統(tǒng)開機(jī)到現(xiàn)在的毫秒數(shù),所以,下面兩個(gè)語(yǔ)句執(zhí)行的時(shí)間是相等的:
XXXDelayed(1000);XXXAtTime(SystemClock.uptimeMillis() + 1000)
知道了這些后就可以隨意使用Handler了。下面是使用Handler的一個(gè)小Demo,代碼有點(diǎn)長(zhǎng)但大部分都是注釋,代碼共分為三塊:
- 創(chuàng)建Handler,實(shí)現(xiàn)處理消息邏輯
- 定義了Worker線程,在Worker線程內(nèi)部使用Handler提供的方法。
- 在
activity.onCreate(Bundle savedInstanceState)初始化控件,啟動(dòng)Worker線程。
public class HandlerActivity extends AppCompatActivity {
TextView mTextView;
// 實(shí)現(xiàn)Handler
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
mTextView.setText("接收到一條從Worker Thread線程發(fā)來空消息");
break;
case 2:
mTextView.setText("接收到一條從Worker Thread線程發(fā)來指定時(shí)間的空消息");
break;
case 3:
mTextView.setText("接收到一條從Worker Thread線程發(fā)來指定時(shí)間之后的空消息");
break;
case 4:
String data = (String) msg.obj;
mTextView.setText("接收到一條從Worker Thread線程發(fā)來消息,消息中包含數(shù)據(jù):"+ data);
break;
case 5:
String data1 = (String) msg.obj;
mTextView.setText("接收到一條從Worker Thread線程發(fā)來指定時(shí)間的消息,消息中包含數(shù)據(jù):" + data1);
break;
case 6:
String data2 = (String) msg.obj;
mTextView.setText("接收到一條從Worker Thread線程發(fā)來指定時(shí)間之后消息,消息中包含數(shù)據(jù):" + data2);
break;
}
}
};
// 定義Worker線程
class WorkerThread extends Thread{
@Override
public void run() {
super.run();
// 在Worker Thread 向UI Thread 發(fā)送一條只有 what 的空消息
mHandler.sendEmptyMessage(1);
// 在指定的時(shí)間Worker Thread 向UI Thread 發(fā)送一條只有 what 的空消息
mHandler.sendEmptyMessageAtTime(2, SystemClock.uptimeMillis() + 1000);
// 在指定的時(shí)間之后Worker Thread 向UI Thread 發(fā)送一條只有 what 的空消息
mHandler.sendEmptyMessageDelayed(3,1000);
// 創(chuàng)建一個(gè)Message
Message msg1 = new Message();
msg1.what = 4;
msg1.obj = "這是一個(gè)從Worker Thread發(fā)送的普通信息";
// 在Worker Thread 向UI Thread 發(fā)送一條消息
mHandler.sendMessage(msg1);
// 創(chuàng)建一個(gè)Message
Message msg2 = new Message();
msg2.what = 5;
msg2.obj = "這是一個(gè)在指定的時(shí)間從Worker Thread發(fā)送的信息";
// 在指定的時(shí)間Worker Thread 向UI Thread 發(fā)送一條消息
mHandler.sendMessageAtTime(msg2,SystemClock.uptimeMillis() + 1000);
// 創(chuàng)建一個(gè)Message
Message msg3 = new Message();
msg3.what = 6;
msg3.obj = "這是一個(gè)在指定的時(shí)間之后從Worker Thread發(fā)送的信息";
// 在指定的時(shí)間之后Worker Thread 向UI Thread 發(fā)送一條消息
mHandler.sendMessageDelayed(msg3,1000);
// post方法可以讓Runnable直接運(yùn)行在UI Thread中
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText("通過post方法,從Worker Thread回到了UI Thread");
}
});
// postAtTime方法可以讓Runnable在指定的時(shí)間直接運(yùn)行在UI Thread中
mHandler.postAtTime(new Runnable() {
@Override
public void run() {
mTextView.setText("通過postAtTime方法,從Worker Thread回到了UI Thread");
}
},SystemClock.uptimeMillis() + 1000);
// postDelayed方法可以讓Runnable在指定的時(shí)間之后直接運(yùn)行在UI Thread中
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("通過postDelayed方法,從Worker Thread回到了UI Thread");
}
},1000);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 綁定控件
mTextView = (TextView)findViewById(R.id.tv);
// 啟動(dòng)worker線程
new WorkerThread().start();
}
}
小結(jié)
到這里我們已經(jīng)熟練的掌握了Handler提供的操作,是不是很簡(jiǎn)單啊?其實(shí)Handler中還有一些比較特別方法這里沒有介紹,在后面看源碼分析實(shí)現(xiàn)原理里會(huì)介紹。
Message的用法絕不是實(shí)例化出來賦值兩個(gè)屬性那么簡(jiǎn)單,本節(jié)只介紹了一點(diǎn)點(diǎn),同樣在分析源碼的時(shí)候會(huì)詳細(xì)的介紹。
Handler實(shí)現(xiàn)原理 - 理論分析
關(guān)于源碼分析的文章我真是有一肚子吐槽的話想說,總的來說就是介紹源碼的作者嗨的不行,而看文章的人一臉懵逼。吸取了前輩們的教訓(xùn),這里分析源碼前先來一波理論上的分析。
線程中接收消息端的特殊性
首先我們得知道理想狀態(tài)下使用Handler是希望它被實(shí)例化在哪個(gè)線程,哪個(gè)線程就是消息的接收端,雖然在其他線程內(nèi)發(fā)送消息時(shí)調(diào)用的同樣是這個(gè)Handler的引用。這沒錯(cuò)吧?


根據(jù)上面的結(jié)論可以知道Handler接收消息端是線程獨(dú)立的,不管handler的引用在哪個(gè)線程發(fā)送消息都會(huì)傳回自己被實(shí)例化的那個(gè)線程中。
但顯而易見的是Handler不可能是線程獨(dú)立的,因?yàn)樗囊脮?huì)在別的線程作為消息的發(fā)送端,也就是說它本身就是多線程共享的引用,不可能獨(dú)立存在于某個(gè)線程內(nèi)。
所以!Handler需要一個(gè)獨(dú)立存在于線程內(nèi)部且私有使用的類幫助它接收消息!這個(gè)類就是Looper!
Looper - 線程獨(dú)立
通過上節(jié)分析我們已經(jīng)知道設(shè)計(jì)Looper就是為了輔助Handler接收消息且僅獨(dú)立于線程內(nèi)部。那如何才能實(shí)現(xiàn)線程獨(dú)立的呢?
好消息是Java早就考慮到了這一點(diǎn),早在JDK 1.2的版本中就提供ThreadLocal這么一個(gè)工具類來幫助開發(fā)者實(shí)現(xiàn)線程獨(dú)立。這里簡(jiǎn)單分析一下ThreadLocal的使用方法,不分析實(shí)現(xiàn)原理了。Android官網(wǎng) - ThreadLocal API
ThreadLocal 支持泛型,用于定義線程私有化變量的類型,實(shí)例化對(duì)象時(shí)可選Override一個(gè)初始化方法initialValue(),這個(gè)方法的作用就是給你的引用變量賦初始值,如果沒有Override這個(gè)方法那么默認(rèn)你的引用變量就是null的:
//定義一個(gè)線程私有的String類型變量
private static final ThreadLocal<String> local = new ThreadLocal<String>(){
// 設(shè)置引用變量的初始化值
@Override
protected String initialValue() {
return super.initialValue();
}
};
定義好了ThreadLocal之后還需要了解三個(gè)方法:
-
get()得到你的本地線程引用變量。 -
set(T value)為你的本地線程引用變量賦值。 -
remove()刪除本地線程引用變量。
是不是很簡(jiǎn)單呢?有了ThreadLocal之后我們只需要把Looper存進(jìn)去就能實(shí)現(xiàn)線程獨(dú)立了。
private static final ThreadLocal<Looper> mLooper = new ThreadLocal<Looper>();
到這里再梳理一下流程:
- Handler 引用可以多線程間共享。
- 當(dāng)Handler對(duì)象在其他線程發(fā)送消息時(shí),通過Handler的引用找到它所在線程的Looper接收消息。
- Looper 負(fù)責(zé)接收消息再分發(fā)給Handler的接收消息方法。

但是!這樣還會(huì)有一個(gè)問題,如果多個(gè)線程同時(shí)使用一個(gè)Handler發(fā)消息,Looper該怎么辦?給接收消息的方法上鎖嗎?顯然不能這樣做??!于是就設(shè)計(jì)了MessageQueue來解決這個(gè)問題。
MessageQueue - 多線程同時(shí)發(fā)消息
為了防止多個(gè)線程同時(shí)發(fā)送消息Looper一下著忙不過來,于是設(shè)計(jì)一個(gè)MessageQueue類以隊(duì)列的方式保存著待發(fā)送的消息,這樣Looper就可以一個(gè)個(gè)的有序的從MessageQueue中取出消息處理了。
既然MessageQueue是為L(zhǎng)ooper服務(wù)的,而Looper又是線程獨(dú)立的,所以MessageQueue也是線程獨(dú)立的。

小結(jié)
現(xiàn)在我們已經(jīng)知道為了完成異步消息功能需要有Handler家族的四位成員共同合作:
- Handler: 負(fù)責(zé)發(fā)送消息,為開發(fā)者提供發(fā)送消息與接收消息的方法。
- Message: 消息載體,負(fù)責(zé)保存消息具體的數(shù)據(jù)。
- MessageQueue:消息隊(duì)列,以隊(duì)列形式保存著所有待處理的消息。
- Looper:消息接受端,負(fù)責(zé)不斷從MessageQueue中取出消息分發(fā)給Handler接受消息端。
這四位成員哪個(gè)都不是平白無(wú)故出現(xiàn)的。因?yàn)橐?guī)范化消息傳遞格式而定義了Message;為了實(shí)現(xiàn)消息接收端只存在線程內(nèi)部私有化使用而定義了Looper;為了解決多線程同時(shí)發(fā)送數(shù)據(jù)Looper分發(fā)消息處理時(shí)會(huì)產(chǎn)生的問題而設(shè)計(jì)MessageQueue隊(duì)列化消息。
到這里你應(yīng)該知道了Handler家族四位成員各自負(fù)責(zé)的是什么工作,以及他們自身的特點(diǎn)特殊性,比如Handler是線程間共享的而Looper是線程獨(dú)立的,MessageQueue跟Looper又是一對(duì)一的。
接下來我們就可以開始讀源碼了!
Message 源碼分析
本文出現(xiàn)的源碼版本均為Android 7.1.1(Nougat) - API 25 版本。
Message作為消息傳遞的載體,源碼主要分為以下幾個(gè)部分:
- 操作數(shù)據(jù)相關(guān),類似
getter()和setter()這種方法還有之前提到過的what和obj這類屬性。 - 創(chuàng)建與回收對(duì)象實(shí)例相關(guān),除了用關(guān)鍵字
new外,其他得到對(duì)象實(shí)例的方法。 - 其他工具類性質(zhì)的擴(kuò)展方法。
Message中的數(shù)據(jù)屬性與方法
首先說一個(gè)本篇文章忽略的屬性及相關(guān)方法:public Messenger replyTo;。
為什么要忽略過去呢?因?yàn)?code>Messenger類是基于Message上實(shí)現(xiàn)進(jìn)程間通信的類。注意,是進(jìn)程間通信,不是線程間通信。一方面進(jìn)程間通信不是本文分析的重點(diǎn),另一方面進(jìn)程間通信需要掌握AIDL方面的知識(shí)。
接下來就讓我們看看Message源碼有哪些可供我們使用的屬性吧:
-
public int what;:開發(fā)者可自定義的消息標(biāo)識(shí)代碼,用于區(qū)分不同的消息。 -
public int arg1;:如果要傳遞的消息只有少量的integer型數(shù)據(jù),可以使用這個(gè)屬性。 -
public int arg2;:同上面arg1。 -
public Object obj;開發(fā)者可自定義類型的傳輸數(shù)據(jù)。
上面四個(gè)屬性作為常用的消息傳遞的數(shù)據(jù)載體可直接賦值,例如msg.arg1 = 100;?;究梢詽M足我們?nèi)粘i_發(fā)中簡(jiǎn)單消息傳遞。
如果上面幾個(gè)數(shù)據(jù)屬性不能滿足我們的需求,可以使用擴(kuò)展數(shù)據(jù):Bundle來傳遞(Bundle是啥應(yīng)該都知道吧?):
Bundle data;
// 得到Bundle數(shù)據(jù),如果data是空的就new一個(gè)
public Bundle getData() {
if (data == null) {
data = new Bundle();
}
return data;
}
// 得到Bundle數(shù)據(jù),如果data是空的就返回 null
public Bundle peekData() {
return data;
}
// 設(shè)置Bundle數(shù)據(jù)
public void setData(Bundle data) {
this.data = data;
}
這段代碼也沒什么邏輯好分析的,值得一提就是Bundle data不是public的,所以我們不能直接操作這個(gè)屬性,需要通過上面三個(gè)方法操作數(shù)據(jù)。使用Bundle數(shù)據(jù)也非常簡(jiǎn)單:
Bundle bundle = new Bundle();
bundle.putString("String","value");
bundle.putFloat("float",0.1f);
Message msg = Message.obtain();
msg.setData(bundle);
創(chuàng)建與回收Message對(duì)象的基本方法
先看一下源碼中Meesage的構(gòu)造方法:
/** Constructor (but the preferred way
to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}
沒錯(cuò),這貨的構(gòu)造方法里什么也沒有,不過它的注釋卻告訴我們想要得到Message對(duì)象首選的方法應(yīng)該是調(diào)用靜態(tài)方法Message.obtain()。那這個(gè)obtain()方法干了什么呢?其實(shí)就是內(nèi)部維持了一個(gè)鏈表形式的Meesage對(duì)象緩存池,這樣會(huì)節(jié)省重復(fù)實(shí)例化對(duì)象產(chǎn)生的開銷成本。
老樣子還是理論分析一波,數(shù)據(jù)結(jié)構(gòu)中的鏈表一個(gè)單元有兩個(gè)值,當(dāng)前單元的值(head)和下一個(gè)單元的地址指針(next),如果下一個(gè)單元不存在那么next就是null的。

所以,想要實(shí)現(xiàn)Message對(duì)象鏈表式緩存池就需要額外的兩個(gè)Message類型的引用head和next,都說了叫緩存池,所以把head叫pool更合適一點(diǎn)。
有了鏈表的基礎(chǔ)結(jié)構(gòu)我們?cè)傧雽?shí)例化對(duì)象的時(shí)候就可以先去鏈表緩存池中看看有沒有,有的話直接從緩存池中拿出來用,沒有再new一個(gè)。

由于代碼多起來邏輯有些復(fù)雜,這樣不太好分析,所以我在源碼中加了許多自己的注釋,下面代碼看上去很長(zhǎng),其實(shí)把注釋都去掉后并沒有多少。
// 用于標(biāo)識(shí)當(dāng)前對(duì)象是否存在于緩存池,0代表不在緩存池中
int flags;
/**
* 這個(gè)常量是供上面的 flags 使用的,它表示in use(正在使用)狀態(tài)
*
* 如果Message對(duì)象被存入了MessageQueue消息隊(duì)列排隊(duì)等待Looper處
* 理或者被回收到緩存池中等待重復(fù)利用時(shí),那么它就是in use(正在使用)狀態(tài)
*
* 只有在new Message()和Message.obtain()時(shí)候才可以清除掉flags上的in use狀態(tài)
*
* 你不可以讓一個(gè)in use狀態(tài)的Message對(duì)象去傳遞消息。
*
* 1<< 0 還是1,真不知道為啥要這么寫,直接寫等于1不就得了
*/
static final int FLAG_IN_USE = 1 << 0;
/** 靜態(tài)常量對(duì)象,通過synchronized (sPoolSync)讓它作為線程并發(fā)操作時(shí)的鎖
* 確保同一時(shí)刻只有一個(gè)線程可以訪問當(dāng)前對(duì)象的引用
*/
private static final Object sPoolSync = new Object();
// 當(dāng)前鏈表緩存池的入口,裝載著緩存池中第一個(gè)可用的對(duì)象
private static Message sPool;
// 鏈表緩存池中指向下一個(gè)對(duì)象引用的next指針
Message next;
// 當(dāng)前鏈表緩存池中對(duì)象的數(shù)量
private static int sPoolSize = 0;
/**
* 從緩存池中拿出來一個(gè)Message對(duì)象給你
* 可以讓我們?cè)谠S多情況下避免分配新對(duì)象。
*/
public static Message obtain() {
// 上鎖,這期間只有一個(gè)線程可以執(zhí)行這段代碼
synchronized (sPoolSync) {
// pool不等于空就說明緩存池中還有可用的對(duì)象,直接取出來
if (sPool != null) {
// 聲明一個(gè)Message引用指向緩存池中的pool對(duì)象
Message m = sPool;
// 讓緩存池中pool引用指向它的next引用的對(duì)象
sPool = m.next;
// 因?yàn)樵搶?duì)象已經(jīng)從緩存池中被取出,所以將next指針置空
m.next = null;
// 將從緩存池中取出的對(duì)象的flags的in use標(biāo)識(shí)清除掉
m.flags = 0;
// 緩存池中Message對(duì)象數(shù)量減去一個(gè)
sPoolSize--;
return m;
}
}
// 如果緩存池中沒有可用的對(duì)象就new一個(gè)吧
return new Message();
}
理論上我們希望sPool引用指向了鏈表緩存池中的第一個(gè)對(duì)象,讓它作為整個(gè)緩存池的出入口。所以我們把它設(shè)置成static的,這樣它就與實(shí)例化出來的對(duì)象無(wú)關(guān),也就是說無(wú)論我們?cè)谀膫€(gè)Message對(duì)象中進(jìn)行操作,sPool還是sPool。
靜態(tài)方法obtain()的代碼邏輯流程:
先判斷緩存池是不是空的:if(sPool != null),如果是空的就直接:return new Message();,不是空的就聲明一個(gè)引用讓它指向緩存池第一個(gè)對(duì)象:Message m = sPool;,而緩存池的鏈表頭部sPool引用就指向了鏈表中下一個(gè)對(duì)象:sPool = m.next;,因?yàn)檫@個(gè)時(shí)候緩存池中第一個(gè)對(duì)象已經(jīng)取出交給了引用Message m,所以需要清除掉這個(gè)對(duì)象身上的特殊標(biāo)識(shí),包括緩存池中的next引用和用來標(biāo)記對(duì)象狀態(tài)的flags值:m.next = null; m.flags = 0;,最后將緩存池中的對(duì)象數(shù)量減一:sPoolSize--;。
邏輯理清了整個(gè)流程就顯得很簡(jiǎn)單了,再看看圖解邏輯流程:

分析到這里我們知道了為什么官方推薦我們使用Message.obtain()得到對(duì)象了,因?yàn)樗窃诰彺娉刂腥〕鰜碇貜?fù)利用的,但是通過上面代碼也看可以看到,只有緩存池里有東西時(shí)也就是sPool != null的時(shí)候才可以取,Message是怎么把對(duì)象回收到緩存池中的呢?
回收Message對(duì)象到緩存池的方法
閱讀源碼后發(fā)現(xiàn)有一個(gè)public void recycle()方法用于回收Message對(duì)象,但是它也牽扯出了一堆其他方法與屬性:
// 緩存池最大存儲(chǔ)值
private static final int MAX_POOL_SIZE = 50;
// 區(qū)分當(dāng)前Android版本是否大于或者等于LOLLIPOP版本的全局靜態(tài)變量,默認(rèn)初始值為true
private static boolean gCheckRecycle = true;
/**
* 用于區(qū)分當(dāng)前Android版本是否大于或者等于LOLLIPOP版本
* 內(nèi)部隱藏方法,在APP啟動(dòng)時(shí)就會(huì)執(zhí)行該方法,開發(fā)者是不可見的
* @hide
*/
public static void updateCheckRecycle(int targetSdkVersion) {
if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
gCheckRecycle = false;
}
}
/**
* 判斷當(dāng)前對(duì)象的flags是否為in-use狀態(tài)
*/
boolean isInUse() {
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
}
/**
* 調(diào)用這個(gè)方法后,當(dāng)前對(duì)象就會(huì)被回收入緩存池中。
* 你不能回收一個(gè)在MessageQueue排隊(duì)等待處理或者正在交付給Handler處理的Message對(duì)象
* 說白了就是in-use狀態(tài)的不可回收
*/
public void recycle() {
// 判斷當(dāng)前對(duì)象是否為in-use狀態(tài)
if (isInUse()) {
// 如果當(dāng)前版本大于或者等于LOLLIPOP則拋出異常
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
// 如果當(dāng)前版本小于LOLLIPOP什么也不干直接結(jié)束方法
return;
}
// 回收Message對(duì)象
recycleUnchecked();
}
/**
* 回收一個(gè)可能是in use狀態(tài)的Message對(duì)象
* 在MessageQueue和Looper內(nèi)部處理排隊(duì)Message時(shí)也會(huì)使用這個(gè)方法
*/
void recycleUnchecked() {
// 將當(dāng)前Message對(duì)象置為in-use狀態(tài)
flags = FLAG_IN_USE;
// 清除當(dāng)前Message對(duì)象的所有數(shù)據(jù)屬性
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
// 上鎖
synchronized (sPoolSync) {
// 如果當(dāng)前緩存池對(duì)象中的數(shù)量小于緩存池最大存儲(chǔ)值(50)就存入緩存池中
if (sPoolSize < MAX_POOL_SIZE) {
// 存入緩存池
next = sPool;
sPool = this;
// 緩存池?cái)?shù)量加1
sPoolSize++;
}
}
}
上面代碼的邏輯很清晰,執(zhí)行recycle()方法后先判斷當(dāng)前對(duì)象是否為in-use狀態(tài):if (isInUse()),如果是in-use狀態(tài)的話當(dāng)前Android版本是LOLLIPOP(5.0)版本之前直接結(jié)束程序,LOLLIPOP及之后版本拋出異常。如果當(dāng)前對(duì)象不是in-use狀態(tài),那么就執(zhí)行recycleUnchecked()方法先將它切換到in-use狀態(tài):flags = FLAG_IN_USE;,再把所有的數(shù)據(jù)屬性全部清除,最后把對(duì)象存入緩存池鏈表中。

為什么要區(qū)分Android LOLLIPOP(5.0)前后版本?
源碼剛開始就有兩個(gè)用于區(qū)分Android版本的全局屬性和方法:
private static boolean gCheckRecycle = true;public static void updateCheckRecycle(int targetSdkVersion)
通過查看源碼發(fā)現(xiàn)Message類在LOLLIPOP版本進(jìn)行了一次更新也就是我們現(xiàn)在看到的源碼,在LOLLIPOP版本之前雖然recycle()方法的注釋上同樣警告了我們不能回收in-use對(duì)象,但是如果你堅(jiān)持讓in-use狀態(tài)的對(duì)象調(diào)用recycle()的話也會(huì)也會(huì)被回收:
/**
* Android LOLLIPOP版本源碼
*
* Return a Message instance to the global pool. You MUST NOT touch
* the Message after calling this function -- it has effectively been
* freed.
*/
public void recycle() {
// 清除數(shù)據(jù)
clearForRecycle();
// 存入緩存池
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
所以在LOLLIPOP版本的時(shí)候Google進(jìn)行了改進(jìn),強(qiáng)制要求不可以回收in-use狀態(tài)的對(duì)象否則拋出異常,但是為了兼容之前的版本,所以新增加了個(gè)內(nèi)部私有的區(qū)分Android版本的方法。
我們需要手動(dòng)回收嗎
現(xiàn)在我們知道了通過執(zhí)行recycle()方法回收Message對(duì)象,但是如果要為每個(gè)Message對(duì)象都進(jìn)行手動(dòng)回收豈不是很麻煩?
慶幸的是開發(fā)人員也想到了這一點(diǎn),從源碼中可以看到其實(shí)最終真正執(zhí)行回收操作的調(diào)用recycleUnchecked()方法,且注釋中告訴我們MessageQueue和Looper內(nèi)部也會(huì)調(diào)用該方法執(zhí)行回收。
這里先說一個(gè)結(jié)論,MessageQueue和Looper內(nèi)部分發(fā)處理消息時(shí),當(dāng)它們得知當(dāng)前這個(gè)Message對(duì)象已經(jīng)使用完畢后就會(huì)直接調(diào)用recycleUnchecked()方法將它回收掉,等分析到MessageQueue和Looper再具體講這個(gè)地方。
所以,如果我們用實(shí)例化Message對(duì)象是放入Handler中去傳消息的,那么我們就不需要手動(dòng)回收,他們內(nèi)部自己就回收了。如果我們使用的Message對(duì)象跟Handler,Looper,MessageQueue一點(diǎn)交互都沒有,那我們就自己去回收。
包含Handler參數(shù)的obtain()方法
給Message內(nèi)部裝了一個(gè)Handler起了什么作用呢?首先,我們可以通過上面講解我們可以得出以下已知的結(jié)論:
- 表面上看我們使用Handler發(fā)送消息后,消息直接傳回到了Handler內(nèi)部的
handleMessage(Message msg)方法中。 - 實(shí)際上是先把消息傳入了MessageQueue中,Looper再?gòu)腗essageQueue依次取出消息分發(fā)給Handler。
- Looper是線程獨(dú)立的, Looper和MessageQueue是一對(duì)一的。
但是,你有沒有想過Looper和Handler是不是一對(duì)一的?答案當(dāng)然是否定的,MessageQueue只負(fù)責(zé)隊(duì)列消息,Looper只負(fù)責(zé)取出消息分發(fā)。他們的功能很明確而且通用。
所以,無(wú)論當(dāng)前線程有多少個(gè)Handler,同樣都只有一個(gè)Lopper和一個(gè)MessageQueue。

既然每個(gè)線程只有一個(gè)Looper和MessageQueue的話那么Looper分發(fā)消息的時(shí)候要如何判斷當(dāng)前這個(gè)Message是哪個(gè)Handler的呢?所以開發(fā)人員就給Message內(nèi)部配置了一個(gè)Handler屬性,這樣Looper分發(fā)消息時(shí)直接調(diào)用Messgae內(nèi)部的Handler屬性就能找到它對(duì)應(yīng)的handleMessage(Message msg)接收消息的方法了。
源碼很簡(jiǎn)單,就是在空參方法obtain()基礎(chǔ)上加了個(gè)Handler屬性,還有它的getter()和setter():
Handler target;
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
public void setTarget(Handler target) {
this.target = target;
}
public Handler getTarget() {
return target;
}
包含Runnable參數(shù)的obtain()方法
跟上面類似,該方法就是在上面基礎(chǔ)加了個(gè)Runnable參數(shù),源碼如下:
Runnable callback;
public static Message obtain(Handler h, Runnable callback) {
Message m = obtain();
m.target = h;
m.callback = callback;
return m;
}
public Runnable getCallback() {
return callback;
}
這個(gè)Runnable的作用是:在Looper分發(fā)消息時(shí)如果Runnable callback不是空的,那么就不調(diào)用Handler的handleMessage(Message msg)方法,直接運(yùn)行這個(gè)Runnable callback。注意,這里的運(yùn)行是已經(jīng)回到了Handler被創(chuàng)建的線程上,也就是說Runnable會(huì)運(yùn)行在Handler被創(chuàng)建的線程上。
更多包含參數(shù)的obtain()方法
下面這些帶參的obtain()方法我相信不用介紹大家也都能看的懂:
public static Message obtain(Handler h, int what) {
Message m = obtain();
m.target = h;
m.what = what;
return m;
}
public static Message obtain(Handler h, int what, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.obj = obj;
return m;
}
public static Message obtain(Handler h, int what, int arg1, int arg2) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
return m;
}
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
擴(kuò)展方法
特殊屬性long when;
long when;
public long getWhen() {
return when;
}
既然這個(gè)屬性的名字都叫when了那肯定就是跟時(shí)間有關(guān)了。還記得Handler給我們提供的方法中有幾個(gè)可以控制時(shí)間的方法嗎?例如XXXAtTime()和XXXDelayed(),when這個(gè)屬性就是就用存儲(chǔ)當(dāng)前這個(gè)Message應(yīng)該被處理的時(shí)間。當(dāng)我們講Handler和MessageQueue時(shí)會(huì)在提到它。
序列化對(duì)象
Message支持對(duì)象的序列化,就是可以把對(duì)象轉(zhuǎn)為字節(jié)形式,可以保存到本地也可以用于網(wǎng)絡(luò)傳輸。如果不了解這方面的知識(shí)建議先查閱相關(guān)文章。
為了實(shí)現(xiàn)對(duì)象序列化,我們需要實(shí)現(xiàn)Parcelable接口,實(shí)例化Parcelable.Creator接口,并重寫describeContents()和writeToParcel()方法。先看源碼,再講他們都是干啥的。
// 實(shí)現(xiàn)Parcelable接口
public final class Message implements Parcelable {
...
// 實(shí)例化Parcelable.Creator接口,完成Parcel對(duì)象轉(zhuǎn)Message對(duì)象的操作
public static final Parcelable.Creator<Message> CREATOR
= new Parcelable.Creator<Message>() {
public Message createFromParcel(Parcel source) {
Message msg = Message.obtain();
msg.readFromParcel(source);
return msg;
}
public Message[] newArray(int size) {
return new Message[size];
}
};
// 序列化對(duì)象時(shí)的特殊種類對(duì)象描述,這里開發(fā)人員沒有修改,就是默認(rèn)的0
public int describeContents() {
return 0;
}
// 重寫Parcelable接口的writeToParcel方法,將Message對(duì)象轉(zhuǎn)為Parcel對(duì)象,
public void writeToParcel(Parcel dest, int flags) {
// 以下代碼均是將Message對(duì)象中的屬性寫入Parcel對(duì)象中
if (callback != null) {
throw new RuntimeException(
"Can't marshal callbacks across processes.");
}
dest.writeInt(what);
dest.writeInt(arg1);
dest.writeInt(arg2);
if (obj != null) {
try {
Parcelable p = (Parcelable)obj;
dest.writeInt(1);
dest.writeParcelable(p, flags);
} catch (ClassCastException e) {
throw new RuntimeException(
"Can't marshal non-Parcelable objects across processes.");
}
} else {
dest.writeInt(0);
}
dest.writeLong(when);
dest.writeBundle(data);
Messenger.writeMessengerOrNullToParcel(replyTo, dest);
dest.writeInt(sendingUid);
}
// 從Parcel對(duì)象中讀取數(shù)據(jù)轉(zhuǎn)為當(dāng)前對(duì)象的屬性
private void readFromParcel(Parcel source) {
what = source.readInt();
arg1 = source.readInt();
arg2 = source.readInt();
if (source.readInt() != 0) {
obj = source.readParcelable(getClass().getClassLoader());
}
when = source.readLong();
data = source.readBundle();
replyTo = Messenger.readMessengerOrNullFromParcel(source);
sendingUid = source.readInt();
}
}
代碼看上去很長(zhǎng),其實(shí)很簡(jiǎn)單,就是實(shí)現(xiàn)了Parcelable接口,接著重寫將Message對(duì)象轉(zhuǎn)為Parcel對(duì)象的方法writeToParcel(),再重寫了接口Parcelable.Creator<T>完成Parcel對(duì)象轉(zhuǎn)Message對(duì)象的方法。
這里用到的主要都是序列化對(duì)象Parcelable接口相關(guān)的知識(shí)。
設(shè)置Message是異步傳輸還是同步傳輸
正常情況下,我們的消息其實(shí)是同步處理的,為什么這么說呢?
Looper的工作就是把消息隊(duì)列MessageQueue中的消息依次取出然后分發(fā),每個(gè)消息傳輸都是有時(shí)間順序的,這個(gè)動(dòng)作都是可控制的。
然而,將消息設(shè)置成異步傳輸后那么Message對(duì)象將不再受Looper的控制,傳輸?shù)捻樞蚩赡軙?huì)被打斷,不一定哪個(gè)消息先傳過來。
所以,請(qǐng)謹(jǐn)慎使用異步傳輸。
// 該常量代表為異步傳輸方式
static final int FLAG_ASYNCHRONOUS = 1 << 1;
// 判斷是否為異步傳輸
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
// 設(shè)置當(dāng)前對(duì)象是否為異步傳輸
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
值得一提的是,用于標(biāo)記是否為異步傳輸?shù)臉?biāo)識(shí)跟用于判斷是否為in-use狀態(tài)的標(biāo)識(shí)是共用的一個(gè)屬性flags。
小結(jié)
到這里整個(gè)Message源碼就已經(jīng)分析的差不多了,如果你有認(rèn)真看到這個(gè)相信你打開電腦中的Message源碼看起來將對(duì)其中的代碼了如指掌(除了本文沒介紹的Messenger相關(guān)代碼)。
在簡(jiǎn)單的總結(jié)一下Message源碼,他們大致分為三類:
利用鏈表式緩存池避操作對(duì)象的相關(guān)方法:
-
obtain()方法 - 其他包含參數(shù)的
obtain()方法 - 手動(dòng)回收對(duì)象到緩存池的
recycle()方法 - Looper與MessageQueue內(nèi)部也會(huì)使用的回收對(duì)象到緩存池的
recycleUnchecked()方法
保存數(shù)據(jù)的相關(guān)屬性及方法
- 消息標(biāo)識(shí)
int what - 簡(jiǎn)單整數(shù)型屬性
int arg1和int arg2 - 自定義類型數(shù)據(jù)
Object obj - 擴(kuò)展數(shù)據(jù)
Bundle data
其他功能相關(guān)屬性及方法
- 控制消息被處理的時(shí)間屬性
int when - 序列化對(duì)象相關(guān)方法
- 設(shè)置異步傳輸方法相關(guān)方法
未完待續(xù)&致謝
由于篇幅有限,所以我計(jì)劃將Handler機(jī)制原理系列分解為3或4章,既方便我管理文章也方便其他人觀看。文章寫到這里已經(jīng)用時(shí)將近兩個(gè)星期,我也總算是知道了自己理解和讓別人理解的差距,接下來速度應(yīng)該就會(huì)快多了。
此篇文章大部分觀點(diǎn)均為我自己分析后得出結(jié)論猜想,可能某些地方是錯(cuò)誤的,歡迎大家提出自己的看法,我們可以一起討論,觀點(diǎn)成立的話,我會(huì)及時(shí)更新文章中的錯(cuò)誤或需要改進(jìn)的地方,并在附上參與者主頁(yè)鏈接實(shí)名感謝。