Android:關於 Handler 消息傳遞機制

寫在前面

這一篇主要是對Android 的消息機制做一個總結。

在消息傳遞機制裏,Android提供了 Handler 來作爲對線程裏的消息隊列裏信息進行傳遞和處理的方法,所以並不是說 Handler 就是用來更新主線程裏的 UI,只是我們大多時候是用來在這麼做。

而且在 Handler 的介紹裏,並沒有說作用就是用來更新主線程 UI:

A Handler allows you to send and process {@link Message} and Runnable objects associated with a thread’s {@link MessageQueue}.

它主要的工作就是分發Message 和 Runnable (實際還是封裝成 Message) 到消息隊列裏,然後當它們從消息隊列裏出來的時候執行它們。

一說到這個就離不開三個關鍵詞:Handler,MessageQueue 和 Looper。關於這三者的內容和關係不少,但我們先從用法開始。


用法

常見的用法有幾種:

第一種方法

Handler handler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    tv.setText(msg.what + "");
                    return false;
                }
            });

private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Message message = new Message();
            message.what = 1;           
            handler.sendMessage(message);
        }
    });   

第二種方法

    private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
           Handler handler = new Handler(Looper.getMainLooper() , 
           new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    tv.setText(msg.what + "");
                    return false;
                }
            });

            Message message = new Message();
            message.what = 1;
            handler.sendMessage(message);
        }
    });

第三種方法


 private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            Handler handler = new Handler( 
            new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    //做操作
                    return false;
                }
            });

            Message message = new Message();
            message.what = 1;
            handler.sendMessage(message);
            
            Looper.loop();
        }
    });

用法說明

爲什麼第一種方法和第二種方法是一樣的?

第一種和第二種方法是一樣的,主要的區別是在於 Handler 初始化時的位置不同而導致的不同。

因爲 Handler 的構造方法裏會需要指定一個 Looper,默認是當前線程裏的 Looper:

public Handler(Callback callback, boolean async) {
      //省略其它代碼
      //關注這裏
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
      //省略其它代碼
    }

    /**
     * Return the Looper object associated with the current thread.  
     * Returns null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

在第一種方法裏,像有時我們在 Activity 裏,之所以不用指定 Looper,是因爲我們這樣寫就表示是默認使用主線程ActivityThread的 Looper (在這裏被稱爲 MainLooper)了,這個可以在 ActivityThread 的源碼裏看到:

//ActivityThread.java

 public static void main(String[] args) {
        //省略其它代碼...

        Looper.prepareMainLooper();

        //省略其它代碼...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

而在第二種方法裏,你不寫Looper.getMainLooper()的話,就默認是當前子線程,那麼運行起來你其實是更新不了 UI 的。

第三種是標準寫法

第三種是最原本的標準用法,要在用法上完整的使用 Handler,那麼就需要

  1. 有一個線程
  2. 準備Looper.prepare()
  3. 初始化 Handler,(順便把對消息回調的方法也實現了)
  4. Looper.loop();
  5. Handler 發送消息

第3點就是前面說的,Handler 是用來針對線程裏的消息隊列的,在初始化的時候需要有 Looper 參數,而這個 Looper 參數初始化的時候是獲取當前線程,也就是說要想知道 Handler 是針對哪個線程的,就看 Looper 是哪裏的。


消息傳遞機制

Handler 會投遞(send or post方法 )信息(Messasge or Runnable)到消息隊列(MessageQueue)裏,Looper 會從消息隊列裏取出消息,然後再將消息發給 Handler的回調(Callback)或是執行 Runnable 的 run 方法。


Handler

關於 post 和 send

這裏有個地方我們要注意,就是 Handler 的 send 和 post 方法,通過源碼的分析,我們可以知道,post 一個 Runnable 和 send一個 Message最後調用的方法都是一樣的,即sendMessageAtTime(Message , long)

當使用 post 方法時,它會把 Runnable 封裝進 Message 裏,因爲 Message 裏有個 Callback 的成員變量,就是 Runnable 類型,所以最後都可以當做 Message 來看待。

 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;
    }

那麼 send 一個 Message 的時候,我們知道有這樣的回調Handler.Callback:

 Handler handler = new Handler( 
            new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {

                    //做操作
                    return false;
                }
            });

而 post 一個 Runnable 的時候,就是執行 Runnable裏 的 run 方法了,所以它是先 post進隊列,然後等隊列裏出來後,才 run,相當於上面的回調。

//這個相當於在子線程裏進行個計時器的操作
handler.postDelayed(new Runnable() {
                @Override
                public void run() {

                }
            } , 100);

從最後Handler 對消息的分發方法dispatchMessage(Message)也可以看出來。

 public void dispatchMessage(Message msg) {
        //使用 post 方法時,就執行 Runnable 裏的 run
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //使用 send 方法,就回調
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

private static void handleCallback(Message message) {
        message.callback.run();
    }

另外由於 post 和 send 兩個方法的返回值是 boolean 類型,所以也可以讓我們來判斷是否成功加入隊列。

Looper

Looper 就是對消息隊列進行循環,MessageQueue 是它的一個成員變量,所以它可以在自己的loop()方法裏,對 MessageQueue 進行遍歷操作。

loop()方法是開啓循環的操作,然後我們可以調用quit()或是quitSafety()方法來退出Looper。這兩個方法最後都是調用了MessageQueue 的quit(boolean safe)方法。

區別在於quit()是直接清空消息隊列,而quitSafety()是隻清空那些延時的信息(即 postDelay),然後分發那些即時的消息。

我們可以看下最後調用的MessageQueue 的quit(boolean safe)方法:

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                //關注這個
                removeAllFutureMessagesLocked();
            } else {
                //關注這個
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

 private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            //如果超過了當前時間,即表示是要延時發送的消息,那就都清掉
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

MessageQueue

一個用鏈表實現的消息隊列,主要包含插入(enqueueMessage)和讀取(next)兩個操作,在讀取的時候,還包含將該消息進行刪除。

ThreadLocal

ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有在指定線程中可以獲取到存儲的數據,對於其它線程來說則無法獲取到數據。

在新的版本里,ThreadLocal 的 get 和 set 方法從原先的數組索引裏取值,變成了從一個自定義的 hashmap ——ThreadLocalMap裏取值。

ThreadLocal 的 set 方法會獲取當前線程的 value 值,沒有的話則對其初始化。然後會使用 ThreadLocalMap (一個自定義的 hashmap)來保存(在以前的版本里是用了一個數組,然後 i 的值爲 key,i+1的值爲 value。現在改成了用 Entry 類型的數組來保存,即一個 Entry 包含了 key 和 value)。

小插曲

經常看到 Thread 和 Runnable 在一起的操作,但現在我們在 Handler,Message 也看到Runnable 的出現。

那麼 Runnable 究竟是什麼?

Runnable 是個接口,按照規定,當一個類要想實現這個接口,就代表着它的實例是想在線程裏被執行,所以 Runnable 的接口裏會有個 run 方法。

它被設計於用來說當這個對象想在活躍狀態時運行自己那部分的代碼,例如Thread 這個類就是這樣,活躍狀態的意思就是已經開始但還沒結束。但其實它不是僅限於在 Thread 裏的。

參考

《Android 開發藝術探索》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章