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

預熱

在寫這篇文章前我不止一次的問自己,網(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ù),大概先簡單的介紹兩個典型的,就不上代碼了。

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

  2. 使用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)強制要求以下兩點:

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

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

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

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

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

  4. 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)
在具體指定的時間uptimeMillisRunnable運行在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,代碼有點長但大部分都是注釋,代碼共分為三塊:

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

到這里再梳理一下流程:

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

但是!這樣還會有一個問題,如果多個線程同時使用一個Handler發(fā)消息,Looper該怎么辦?給接收消息的方法上鎖嗎?顯然不能這樣做啊!于是就設計了MessageQueue來解決這個問題。

MessageQueue - 多線程同時發(fā)消息

為了防止多個線程同時發(fā)送消息Looper一下著忙不過來,于是設計一個MessageQueue類以隊列的方式保存著待發(fā)送的消息,這樣Looper就可以一個個的有序的從MessageQueue中取出消息處理了。

既然MessageQueue是為Looper服務的,而Looper又是線程獨立的,所以MessageQueue也是線程獨立的。

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作為消息傳遞的載體,源碼主要分為以下幾個部分:

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

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

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

有了鏈表的基礎結(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ù)屬性全部清除,最后把對象存入緩存池鏈表中。

回收Message對象

為什么要區(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é)論:

  1. 表面上看我們使用Handler發(fā)送消息后,消息直接傳回到了Handler內(nèi)部的handleMessage(Message msg)方法中。
  2. 實際上是先把消息傳入了MessageQueue中,Looper再從MessageQueue依次取出消息分發(fā)給Handler。
  3. Looper是線程獨立的, Looper和MessageQueue是一對一的。

但是,你有沒有想過Looper和Handler是不是一對一的?答案當然是否定的,MessageQueue只負責隊列消息,Looper只負責取出消息分發(fā)。他們的功能很明確而且通用。

所以,無論當前線程有多少個Handler,同樣都只有一個Lopper和一個MessageQueue。

一個線程存在多個Handler

既然每個線程只有一個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 arg1int arg2
  • 自定義類型數(shù)據(jù)Object obj
  • 擴展數(shù)據(jù)Bundle data

其他功能相關屬性及方法

  • 控制消息被處理的時間屬性int when
  • 序列化對象相關方法
  • 設置異步傳輸方法相關方法

未完待續(xù)&致謝

由于篇幅有限,所以我計劃將Handler機制原理系列分解為3或4章,既方便我管理文章也方便其他人觀看。文章寫到這里已經(jīng)用時將近兩個星期,我也總算是知道了自己理解和讓別人理解的差距,接下來速度應該就會快多了。

此篇文章大部分觀點均為我自己分析后得出結(jié)論猜想,可能某些地方是錯誤的,歡迎大家提出自己的看法,我們可以一起討論,觀點成立的話,我會及時更新文章中的錯誤或需要改進的地方,并在附上參與者主頁鏈接實名感謝。

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

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

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