Handler機(jī)制實(shí)現(xiàn)原理(一)宏觀理論分析與Message源碼分析

預(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è)典型的,就不上代碼了。

  1. 通過synchronized關(guān)鍵字以“上鎖”機(jī)制實(shí)現(xiàn)線程間的通信。多個(gè)線程持有同一個(gè)對(duì)象,他們可以訪問同一個(gè)共享變量,利用synchronized“上鎖”機(jī)制,哪個(gè)線程拿到了鎖,它就可以對(duì)共享變量進(jìn)行修改,從而實(shí)現(xiàn)了通信。

  2. 使用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):

  1. 為保持用戶界面流暢UI線程不能被阻塞,如果線程阻塞界面會(huì)卡死,若干秒后Android系統(tǒng)拋出ANR。

  2. 除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線程不能被阻塞的問題。具體方法有以下幾類:

  1. view.post(Runnable action)系列,通過View對(duì)象引用切換回UI線程。

  2. activity.runOnUiThread(Runnable action),通過Activity對(duì)象引用切換回UI線程。

  3. AsyncTask,內(nèi)部封裝了UI線程與Worker線程切換的操作。

  4. 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í)間uptimeMillisRunnable運(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)但大部分都是注釋,代碼共分為三塊:

  1. 創(chuàng)建Handler,實(shí)現(xiàn)處理消息邏輯
  2. 定義了Worker線程,在Worker線程內(nèi)部使用Handler提供的方法。
  3. 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ò)吧?

兩個(gè)線程通信
多線程通信

根據(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>();

到這里再梳理一下流程:

  1. Handler 引用可以多線程間共享。
  2. 當(dāng)Handler對(duì)象在其他線程發(fā)送消息時(shí),通過Handler的引用找到它所在線程的Looper接收消息。
  3. Looper 負(fù)責(zé)接收消息再分發(fā)給Handler的接收消息方法。
Looper

但是!這樣還會(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ú)立的。

MessageQueue

小結(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è)部分:

  1. 操作數(shù)據(jù)相關(guān),類似getter()setter()這種方法還有之前提到過的whatobj這類屬性。
  2. 創(chuàng)建與回收對(duì)象實(shí)例相關(guān),除了用關(guān)鍵字new外,其他得到對(duì)象實(shí)例的方法。
  3. 其他工具類性質(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的。

鏈表結(jié)構(gòu)

所以,想要實(shí)現(xiàn)Message對(duì)象鏈表式緩存池就需要額外的兩個(gè)Message類型的引用headnext,都說了叫緩存池,所以把headpool更合適一點(diǎn)。

有了鏈表的基礎(chǔ)結(jié)構(gòu)我們?cè)傧雽?shí)例化對(duì)象的時(shí)候就可以先去鏈表緩存池中看看有沒有,有的話直接從緩存池中拿出來用,沒有再new一個(gè)。

從緩存池中取對(duì)象

由于代碼多起來邏輯有些復(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)單了,再看看圖解邏輯流程:

從緩存池中取出一個(gè)對(duì)象

分析到這里我們知道了為什么官方推薦我們使用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ì)象存入緩存池鏈表中。

回收Message對(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é)論:

  1. 表面上看我們使用Handler發(fā)送消息后,消息直接傳回到了Handler內(nèi)部的handleMessage(Message msg)方法中。
  2. 實(shí)際上是先把消息傳入了MessageQueue中,Looper再?gòu)腗essageQueue依次取出消息分發(fā)給Handler。
  3. 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è)Handler

既然每個(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 arg1int 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í)名感謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容