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

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


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

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

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

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

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

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

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

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