1. 程式人生 > >從原始碼一次徹底理解Android的訊息機制

從原始碼一次徹底理解Android的訊息機制

情景重現

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Thread.sleep(5 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                textView.setText("after changed"
); } });

開頭我們就看到了如上的一段簡單的偽碼。因為這裡我試圖去還原一種場景,一種可能我們不少人最初接觸Android時可能都會遇到的錯誤場景。
這種場景的邏輯很簡單:在程式執行中,某個控制元件的值會被動態的改變。這個值通過某種途徑獲取,但該途徑是耗時的(例如訪問網路,檔案讀寫等)。
上面的偽碼中的邏輯是:點選按鈕將會改變文字框的值,且這個值是通過某種耗時的操作獲取到,於是我們通過將執行緒休眠5秒來模擬這個耗時操作。

好的,現在我們通過編譯正式開始執行類似的程式碼。那麼,我們首先會收到熟悉的一個錯誤,即“Application No Response(ANR)”。
接著,通過查閱相關的資料,我們明白了:原來我們像上面這樣做時,耗時操作直接就是存在於主執行緒,即所謂的UI執行緒當中的。
那麼這就代表著:這個時候的UI執行緒會因為執行我們的耗時操作而被堵塞,也自然就無法響應使用者其它的UI操作。於是,就會引起ANR這個錯誤了。
現在我們瞭解了ANR出現的原因,所以我們自然就會通過一些手段來避開這種錯誤。我們決定將耗時的操作從UI執行緒拿走,放到一個新開的子執行緒中:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(5
* 1000); textView.setText("after changed"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } });

好的,現在我們模擬的耗時操作已經被我們放到了UI執行緒之外的執行緒。當我們信心十足的再次執行程式,確得到了如下的另一個異常資訊:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

從異常資訊中,我們看到系統似乎是在告訴我們一個資訊,那就是:只有建立一個檢視層次結構的原始執行緒才能觸控到它的檢視
那麼,我們似乎就能夠理解這種異常出現的原因了:我們將耗時操作放在了我們自己建立的分執行緒中,顯然它並非原始執行緒,自然就不能夠去訪問View。
這樣設計的初衷實際上是不難猜想的,如果任何執行緒都能去訪問UI,請聯想一下併發程式設計中各種不可預知且操蛋的問題,可能我們的介面最終就熱鬧了。
但是,現在我們針對於這一異常的解決方案似乎也不難給出了。既然只有主執行緒能夠訪問View,那麼我們只需要將更新UI的操作放到主執行緒就OK了。
那麼,這裡就順帶一提了。不知道有沒有人和我曾經一樣,想當然的寫出過類似下面一樣的程式碼:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(5 * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                // 這是在主執行緒裡執行的
                textView.setText("after changed");
            }
        });

是的,如上的程式碼十分的,非常的“想當然”。這當然是因為對Java多執行緒機制理解不夠所造成的。更新UI的操作確實是放到了主執行緒,但是!!!:
這並不代表著,UI更新一定會在分執行緒的耗時操作全部完成後才會執行,這自然是因為執行緒執行權是隨機切換的。也就是說,很可能出現的情況是:
分執行緒中的耗時操作現在並沒有執行完成,即我們還沒有得到一個正確的結果,便切換到了主執行緒執行UI的更新,這個時候自然就會出現錯誤。

Handler粉墨登場

這個時候,作為菜鳥的我們有點不知所措。於是,趕緊上網查查資料,看看有沒有現成的解決方案吧。這時,通常“Handler”就會進入我們的視線了:

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0x0001:
                    textView.setText("after changed");
            }
        }
    };
    //===============================================
    new Thread(new Runnable() {
        @Override
        public void run() {
           try {
              Thread.sleep(5 * 1000);
              mHandler.sendEmptyMessage(0x0001);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
             }
     }).start();

我們發現關於Handler的使用似乎十分容易不過,容易到當我們認為自己掌握了它的時候似乎都沒有成就感:

  • 首先,我們只需要建立一個Handler物件。
  • 接著,我們會在需要的地方,通過該Handler物件傳送指定的Message。
  • 最後,該Handler物件通過handleMessage方法處理接收到的Message。

但我們沉下心來想一想:Handler為什麼能夠解決我們之前碰到的非原始執行緒不能更新UI的錯誤呢?它的實現原理如何?它能做的就只是更新UI嗎?
掰扯了這麼多,帶著這些疑問,我們終於來到了我們這篇blog最終的目的,那就是搞清楚Android的訊息機制(主要就是指Handler的執行機制)。

從建構函式切入

就像醫生如果要弄清楚人體構造,方式當然是通過解剖來進行研究。而我們要研究一個物件的實現原理,最好的方式就是通過分析它的原始碼。
個人的習慣是,當我們沒有一個十分明確的切入點的時候,選擇建構函式切入通常是比較合適的,那我們現在就開啟Handler的建構函式來看一下:

    // 1.
    public Handler() {
        this(null, false);
    }
    // 2.
    public Handler(Callback callback) {
        this(callback, false);
    }
    // 3.
    public Handler(Looper looper) {
        this(looper, null, false);
    }
    // 4.
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    // 5.
    public Handler(boolean async) {
        this(null, async);
    }
    // 6.
    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    // 7.
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

好的,分析一下我們目前所看的,我覺得我們至少可以很容易的分析並掌握兩點:

  • Handler自身提供了7種構造器,但實際上只有最後兩種提供了具體實現。
  • 我們發現各種構造器最終圍繞了另外兩個類,即Callback與Looper。我們推測它們肯定不是做擺設的。

現在我們來分別看一下唯一兩個提供了具體實現的構造器,我們發現:
除了 ”if (FIND_POTENTIAL_LEAKS) “這一段看上去和反射有關的if程式碼塊之外,這兩個構造器剩下的實現其實基本上是完全一致的,即:

        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;

唯一的不同在於mLooper這一例項變數的賦值方式:

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //==========================================
        mLooper = looper;

“mLooper = Looper.myLooper();”這種方式究竟有何神奇,我們這裡暫且不提。我們的注意力聚焦在以上看到的幾個例項變數,開啟原始碼看看:

    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
    final boolean mAsynchronous;

mAsynchronous是一個布林型的變數,並且我們看到預設情況它的構造值是false,從命名我們就不難推測到,它多半與非同步有關。除此之外:
其餘型別分別是”MessageQueue,Looper,Callback“。一定記住它們!!!正是它們配合HandlerMessage完成了整個訊息傳遞的架構。

OK,首先我們來看Callback這個東西,從命名來看絕逼與回撥有關係,開啟原始碼,果不其然正是定義在Handler內部的一個介面:

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

我們看到了其中唯一宣告的一個方法介面,看上去似乎有點眼熟。是的,那麼它與Handler自身的handleMessage有何聯絡?我們暫且提不提。
現在,我們再接著看Looper和MessageQueue兩個型別。很遺憾的是,這裡我們發現:這是另外單獨定義的兩個全新的類。也就是說:
目前我們似乎無法在邏輯上將其與Handler聯絡起來。我們現在只知道從命名上來說,它們似乎分別代表著“迴圈”與“訊息佇列”的意思。

post()與sendMessage系列函式

那麼,到了這一步似乎情況有點糟糕,因為似乎失去了下一步的切入點。沒關係,這個時候我們回憶一下我們通常怎麼樣使用Handler:

  • mHandler.post();
  • mHandler.sendMessage();

沒錯,我們基本上就是通過以上兩種方式去使用Handler。所以現在我們開啟這兩個方法相關的原始碼來看看:

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

由此我們發現的是:post函式最終呼叫的仍是send系列的函式;而sendMessage底部也依然是通過sendMessageDelayed呼叫的。
並且!檢視一系列的send方法原始碼發現:它們最終都將通過sendMessageAtTime來完成整個呼叫。所以顯然這將是我們下一個關注點。

先分析一下post方法的實現,我們看到其實Handler內部是通過getPostMessage對我們傳入的Runnable物件進行了一次封裝。
當我們看到getPostMessage方法的實現,我們會發現沒什麼大不了的,只是將傳入Runnable物件賦值給了一個Message物件而已。
但我們也可能會觀察到另外一點。就是我們可能會在使用Message時會通過構造器得到訊息物件,而這裡是通過靜態方法obtain。

使用obtainMessage而非構造器

這二者有什麼不同呢?我們先開啟Message的構造器的方法來看一下:

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }

好的,我們發現構造器實際上沒有任何實現內容。而註釋告訴我們:更推薦使用obtain系列的方法來獲取一個Message物件。
那麼我們就好奇了?為什麼更推薦使用obtain呢?我們以無參的obtain方法為例,開啟原始碼瞧一瞧:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

從以上程式碼我們可以看到的是:obtain最終的本質仍是產生Message物件。關鍵在於一個叫sPool的東西,這兄弟到底是個什麼鬼呢?
實際上是Handler內部會通過這個叫做sPool的靜態全域性變數構建一個類似“池”的東西,而通過next屬性我們不難推斷”池”應該是以單鏈表來實現的。
再檢視方法的註釋:從全域性池中返回一個新的訊息例項。使我們能夠避免在許多情況下分配新的物件。由此我們好像已經知道為何推薦obtain了。
包括網上很多打著類似“new message與obtainMessage之間區別”的資料裡,一大段的文字之後,我們會發現最終實際有用的就類似一句話:
obtainMessage可以從池中獲取Message物件,從而避免新的物件建立,達到節約記憶體的效果。但這樣當然還是熄滅不了一顆好奇的心:
究竟為什麼這個“池”能夠避免新的物件建立呢?要解開這個疑問,我們還需要關注Handler類中的另一個方法“recycleUnchecked”的如下程式碼:

 void recycleUnchecked() {
        // 第一部分
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;
        // 第二部分
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

該方法顧名思義,主要的工作通常主要就是回收一個完成使命的Message物件。而這個回收動作發生的時機是什麼呢?
通常來說,我們可以通過人為呼叫msg.recycle()來完成回收;另一種更常見的回收時機發生在MessageQuene當中,我們稍後會看到。
接下來我們該方法中的回收工作都做了什麼,程式碼中註釋為第一部分的程式碼做的工作很易懂,就是將回收的message物件的各個屬性清空。
第二部分其實就是將回收的物件向“池”內新增的過程,而之前說到的obtain方法,其一旦判斷sPoll不為null,就直接從池內獲取閒置物件,不再建立。

到此實際上我們就已經分析了,為什麼obtain能夠節約記憶體開銷的原理了。但如果你的資料結構和我一樣渣,可能還會有點暈。沒關係,看如下程式碼:

Message msg1 = Message.obtain();
msg1.recycle();
Message msg2 = Message.obtain();

我們對應於這三行簡單的程式碼,來有代入感的分析一下它們執行的過程,相信就會有個比較清晰的理解了。

  • 首先獲取msg1的時候,這個時候sPool肯定是為null的。所以它的工作實際與直接通過構造器建立物件沒有區別。
  • 通過msg1物件呼叫recycle方法,最終進入之前所說的回收工作的第二部分執行。此時的結果為:msg1.next = sPoll(即null,沒有next節點);sPoll = msg1;
  • 這時我們再通過obtain去獲取物件msg2,進入方法後,判斷sPoll不為null。於是, Message m = msg1;注意:
    這代表我們已經從池中取出了msg1,於是執行sPool = m.next時,我們說到msg1.next是null,所以sPool再次等於null,邏輯完全正確。
    與此同時,我們也可以想得到,假設m.next不等於null時:sPool = m.next的邏輯實際上就轉換成了,將sPool指向next節點,即代表我們已經取走一個物件了,池將指向下一個節點,即為我們下次要獲取的訊息物件。

MessageQuene 訊息佇列的工作機制

好了,現在相信我們都清楚以上的概念了。我們的關注點將回到我們之前提到的關鍵位置,即sendMessageAtTime方法,開啟其原始碼:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

我們發現該方法的實現很簡單,但最終會呼叫另一個方法“enqueueMessage”,趕緊開啟這個方法看一看:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

我們發現該方法顯然使用了委託設計模式,將最終的方法實現委託了給了quene物件,即MessageQuene來實現。
對於MessageQuene中的enqueueMessage方法,該方法的原始碼個人覺得沒必要全部關注。我們先看下面這小段程式碼:

            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

我們注意到msg.recycle方法,記得我們之前說過的回收工作嗎?這裡正是另一種發生時機,這個時機的標準如上所示,正是:
“mQuitting”為true,而在什麼時候mQuitting會被設定為true,我們稍後將會看到,這裡先暫且一提。接著看另一端更為關鍵的程式碼:

                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;

有了之前的基礎,我們發現該方法所做的工作實際上很簡單,它仍然是以單鏈表的形式,通過不斷追加next節點達到向佇列中新增Message的效果。
由此,我們發現:當我們通過handler物件post或send了一條訊息,其實最終的工作很簡單,就是向MessageQuene即訊息佇列中追加一條訊息而已。
那麼,接下來呢?自然的,訊息追加到了隊列當中。我們則需要從佇列中依次取出訊息物件,才能對其作出處理。苦苦尋覓一番之後:
我們發現了next()方法,該方法的實現歸根結底是通過迴圈來不斷的從佇列中拉取訊息,考慮到篇幅,我們不再貼出原始碼。唯一注意:

                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } 

當沒有新的訊息來臨之前,如上的程式碼將能夠確保佇列“阻塞”而一直等待新的訊息物件來臨。好了,我們總結一下:
MessageQuene將通過enqueueMessage方法向佇列中插入訊息,而通過next方法取出訊息。但現在的關鍵點在:
關於enqueueMessage方法我們已經知道它在Handler當中被呼叫,而next方法目前我們只看到宣告,還沒看到呼叫的產生。

Looper - 訊息的拉取者

以next()方法的呼叫為關鍵字按圖索驥,我們最終發現它在我們之前提到的另一個關鍵的東西”Lopper”中產生了呼叫,具體是Lopper中的loop方法。

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        ...

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...

            msg.target.dispatchMessage(msg);

            ...

            ...

            msg.recycleUnchecked();
        }
    }

我們只保留了關於loop方法最為關鍵的部分,我們依次來分析一下,首先我們注意到的,肯定是一個名為“myLooper()”的方法呼叫:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

ThreadLocal顯神通

我們從程式碼中看到邏輯十分簡單清晰,就是通過myLooper()來獲取looper物件,而最終的方式則是通過sThreadLocal來獲取。
這裡,就不得不提到一個功能強大的東西ThreadLocal。我們來看一下Looper的原始碼當中關於sThreadLocal的定義:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

這個東西的作用究竟如何?簡單來說,我們知道普通的定義一個例項變數,它將建立在“堆”上。
而“堆”並非執行緒私有的,所以例項變數也將被執行緒共享。而ThreadLocal則是將變數的作用域限制為執行緒私有。舉例來說:

       static final ThreadLocal<String> sThreadLocal = new ThreadLocal<String>();

       sThreadLocal.set("1");

        new Thread(new Runnable() {
            @Override
            public void run() {
                sThreadLocal.set("2");
            }
        }).start();

上面的程式碼通過sThreadLocal.get()來獲取string,在主執行緒中和我們new的執行緒當中獲取的值是獨立的,分別是“1”和“2”。

接下來,我們看到的就是將會在一個無限迴圈中一直通過呼叫MessageQuene的next()方法來獲取訊息物件。
假設此次獲取到了msg物件,則會通過msg.target呼叫dispatchMessage方法來分發訊息。問題在於target是個什麼東西?
在Message類中檢視原始碼,我們可以知道target自身是一個Handler型別的物件。但通常我們都沒有人為的去為這個變數賦值。
那麼這個變數通常預設是什麼呢?回到之前Handler類的enqueneMessage方法當中,看到如下程式碼:

 msg.target = this;

也就是說,如果我們沒有明確的去為Message物件的target域賦值,它將被預設賦值為傳送這條Message的Handler物件自身。
那麼,我們先要做的就簡單了,回到Handler類當中,檢視dispatchMessage方法的原始碼如下:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

這個方法的實現邏輯也很清晰,它的具體分發過程如下:

  • 如果msg.callback不為null,那麼將通過handleCallback方法來處理訊息(實際上就是msg.callback.run());
  • 否則進一步判斷mCallback是否為null,不為null則通過mCallback.handleMessage來處理訊息。
  • 最後如果mCallback也為null,則會呼叫Handler自身的handleMessage方法來處理訊息。

邏輯很簡單,我們唯一注意的就是msg.callback和mCallback這兩兄弟是指什麼東西?很簡單:
msg.callback是我們通過Message.obtain(Handler h, Runnable callback)或者通過Handler.post(Runnable r)傳入的Runnable物件。
而mCallback就更熟悉了,回想我們之前檢視Handler的構造器時看到的東西。
mCallback的本質就是Handler內部定義的介面Callback,所以通過它實際就是通過回撥介面處理訊息。

而這裡,我覺得值得一說的是msg.callback這個東西。我們知道當它不為null,最終實際將通過message.callback.run()來處理訊息。
也就是說最終實際上是呼叫了Runnable物件的run方法,但有Java的基礎就會知道這樣的呼叫實際與執行緒無關,只相當於普通的呼叫一個例項方法而已。
對於這點,我們一定要有清楚的認識,否則可能會因為使用不當造成一些想不到的錯誤。具體的例子我們暫且不說,放在最後的總結部分來看。

實際上到了這裡,我們就已經把構建Android訊息機制的四個關鍵,即Handler,Message,MessageQuene及Looper給聯絡起來了。簡單總結一下:

  • 通過呼叫Handler的post或者send系列的方法來發送一條Message
  • 這一條Message最終會加入到連結串列結構的MessageQuene當中存放。
  • Looper會通過內部的loop方法不斷呼叫MessageQuene的next()方法獲取下一條Message
  • Looper獲取到Message方法後,又會通過Handler的dispatchMessage來分發並處理訊息。

Looper的正確建立

我相信到了這裡,我們或多或少都會有些收穫。但對於剛接觸Andoid訊息機制的朋友來說,還可能存在一個疑問,那就是:
通過之前我們的分析與理解,我們知道了對於Handler處理訊息的機制來說,Lopper的參與是至關重要的。
但與此同時,我們發現之前我們似乎並沒有建立Looper。我們不免會考慮,是系統幫助我們建立了嗎?答案是肯定的。
回憶一下之前的程式碼,我們是通過無參的構造器來建立Handler物件的。我們也可以看到,該構造器最終會呼叫我們之前說到的第6個構造器。
然後我們發現在第6種構造器當中,是通過“mLooper = Looper.myLooper();”的方式來獲取looper物件的。
這時我們就想起了之前的ThreadLocal,但即使是使用ThreadLocal,也起碼得有一個ThreadLocal.set(Looper)的過程吧。
這個過程是在什麼時候完成的呢?正常來說,我們推斷這個過程很可能發生在Looper的構造器中。但一番查詢我們發現Looper壓根沒提供公有的構造器。
經過苦苦的尋覓之後,最終我們會發現在Looper的靜態方法“prepare”中,終於找到了我們想要看見的程式碼:

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

好了,現在我們回想一下,我們之前的程式碼中建立的Handler,是一個屬於當前Activity的例項域。這代表它的建立是在主執行緒中完成的。
而關於Looper的建立的確也是在Android的主執行緒,即ActivityThread中完成建立的。具體的呼叫位於主執行緒的入口main方法中。
並且,主執行緒裡的Looper,是通過呼叫Looper類專門為主執行緒建立Looper物件封裝的方法“prepareMainLooper()”來建立的。

現在我們再看關於”Can’t create handler inside thread that has not called Looper.prepare()”的這個異常,我們就很容易找到原因了。
因為通過原始碼我們知道這個異常就是在第6個構造器中,當通過Looper.myLooper()獲取結果為null時報告的。
同時,我們知道我們在主執行緒建立Handler的時候,沒有問題是因為主執行緒預設就建立了Looper物件。那麼:
當我們要在主執行緒以外的執行緒中建立Handler物件的時候,只要我們也為它建立對應的Looper物件就行了。示例如下:

        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }).start();

是的,在建立完物件後,別忘了呼叫loop()方法,因為這才是開啟迴圈從訊息佇列中獲取訊息的關鍵。

好了,現在我們正式接著看loop()方法中接下來的程式碼msg.recycleUnchecked();。是的,我們很熟悉的”回收”,由此我們可以知道:
回收Message的另一個時機實際就是,當Message物件從佇列中取出處理完成之後,就會進行回收,放入池內。
如果具體閱讀MessageQuene的原始碼,我們會發現還有多種不同的回收時機,我們簡單總結幾種常見的時機:

  • 人為呼叫message.recycle()來回收物件。
  • message從佇列中取出被處理完成後會自動回收。
  • 呼叫Lopper.quit()/quitSafely(),該方法最終會呼叫MessageQuene的quit()方法。
  • MessageQuene呼叫quit在合適的時機將自身佇列中的Message物件進行回收。
  • MessageQuene的quit方法還會將我們之前談到的mQuitting設定為true,這代表著當呼叫了quit之後,再通過handler來send任何message,都將被直接回收。

總結

到這裡,對於Android的訊息機制我們也就研究的差不多了。雖然我總覺得在寫的過程中,我本來還有個別想總結的點,但最後似乎忘了,也想不起了。

我們最後總結一個問題,那就是Handler為什麼能夠執行更新UI的操作!現在我們就來分析一下這個過程,回想一下:
通常我們在另開的執行緒中執行耗時操作,當耗時操作執行完畢後,則呼叫sendMessage()最終讓handler更新UI。
現在我們知道了,這時的Handler我們一定會是定義在主執行緒,即UI執行緒當中的。當我們在分執行緒中sendMessage的時候:
經過我們之前說到的一系列呼叫,最終會通過Handler來進行最終處理。而Handler本身是在主執行緒中執行的,自然也就能夠操作UI。
所以說,如果說Handler能夠讓我們更新UI,不如說其本質是將操作切換到該Handler所在的執行緒來執行。
我們可以試著傳送訊息給在分執行緒中建立的Handler物件,然後在handleMessage仍然試圖去訪問UI。會發現結果當然是行不通的。

這裡就說到我們之前說到的handler.post()這樣的使用方式了,因為引數型別是Runnable,所以我們很容易認為是通過handler執行一個執行緒任務。
但實際情況是,假設Handler物件是在主執行緒中建立的。那麼,通過post()方法,我們仍然可以去更新UI。這是為什麼?
這就回到了我們之前說的,當handler去dispatchMessage後,如果判斷msg.callback不等於null,就會通過msg.callback.run()來處理訊息。
這個時候實際本質上就是在切換到主執行緒去執行了Runnable物件的例項方法run()而已。所以當然能夠更新UI。

而我們可能會有這樣一種需求,那就是想要在指定的時間之後去執行一個耗時的執行緒任務,這個時候可能會想到Handler的postDelayed方法。
我想說的使用誤區就是,這個時候的Handler一定不要是建立在主執行緒當中的,因為這樣耗時操作最終還是在主執行緒執行,自然也就會引發ANR。
如果我們一定想要通過Handler實現我們這樣的需求,其實很簡單,當然就是要把Handler建立在分執行緒當中,就像下面這樣:

              new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Looper.prepare();;
                        final Handler handler = new Handler();
                        Looper.loop();
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Thread.sleep(5000);
                                    handler.sendEmptyMessage(0);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }

                            }
                        });
                    }
                }).start();