Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 從源碼一次徹底理解Android的消息機制

從源碼一次徹底理解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 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 sThreadLocal = new ThreadLocal();

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

       static final ThreadLocal sThreadLocal = new ThreadLocal();

       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()方法獲取下一條MessageLooper獲取到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();
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved