android 中的 Handler 線程間通訊

 

1、 在MainActivity中爲何只是相似的寫一行以下代碼就可使用handler了呢?

 

Handler handler = new Handler() {   @Override   public void handleMessage(Message msg) {     // handle the nsg message...
  } };  private void sendMessage() {    handler.sendEmptyMessage(11); }

 

打開handler的源碼能夠在它的構造函數中,看到以下的幾行代碼:android

 

mLooper = Looper.myLooper();        // (1)
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;

 

若是繼續跟進 (1) 號註釋中的 Looper.myLooper(); 這條語句,近兒,能夠看到下面的代碼:async

 

public static Looper myLooper() { return sThreadLocal.get();     // (2)
 }

 

 

其中 sThreadLocal Looper 類中被聲明爲:ide

 

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

 

可得它是一個靜態不可變的鍵值,由於它是靜態成員,由類的加載過程可知在 Looper 被加載後就當即對該對象進行了初始化,並且它被聲明爲final類型,在接下來的生命週期中將是一個不可改變的引用,這也是稱呼它爲一個鍵值一個緣由;固然,當你繼續跟進時會發現,稱呼它爲鍵值原來是有更好的理由的。跟進(2)號註釋中的sThreadLocal.get(); 語句,能夠獲得下面的邏輯:函數

 

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();    // (3)
        Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); // (4) }

 

咱們只關注上面的代碼中的 註釋(3)和 (4)兩行代碼,其中從註釋 (3)處咱們得知這裏的讀取操做是從當前線程的內部得到數據的,而註釋(4)則進一步告訴咱們,它是一個以 ThreadLocal 類型的對象爲鍵值,也就是Looper中的 static final 訪問控制的 sThreadLocal 對象。奇妙的地方就在這裏,由於每次調用時 ThreadLocal 雖然都是同一個 sThreadLocal 對象,但在ThreadLocal 內部它是從當前正在活動的線程中取出 Looper 對象的,因此達到了一種不一樣的線程調用同一個 Looper 中的同一個 sThreadLocal 對象的 get 方法,而獲得的 Looper是不同的目的。而且從註釋(1)處能夠得知,當咱們調用 sThreadLocal.get();若是返回是一個null時,咱們的調用將是失敗的,也就是在從當前正在活動的線程的內部讀取時尚未初始化,拋出的異常提醒咱們先執行ooper.prepare()方法,跟進Looper的prepare()方法:oop

 

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

 

可得知,它一樣是經過sThreadLocal對象來存放數據的,跟進一步ui

 

 public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }

 

從上面的代碼可得知,每一個線程至多隻能存放一個Looper對象,注意這裏強調的是每一個線程只能存放一份,並非sThreadLocal 只能放一個,這也是線程本地化存儲的祕密所在。因此咱們獲得一個前提條件,在調用相似new Handler(){};語句時,必須事先已執行 Looper.prepare(); 語句,並且要確保兩條語句都是在同一個線程中被調用的,否則可能得不到咱們指望的結果。可是爲何咱們在 MainAcitivity 中沒有顯示調用 Looper.prepare(); 方法,而只是簡單調用 new Handler(); 語句呢?原來在 MainActivity運行所在的主線程(也成UI線程,getId()獲得的id號是1)被android系統啓動時,就主動調用了一次Looper.prepare()方法:this

 

public static final void More ...main(String[] args) { SamplingProfilerIntegration.start(); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); (跟進該方法) ActivityThread thread = new ActivityThread(); thread.attach(false); Looper.loop(); if (Process.supportsProcesses()) { throw new RuntimeException("Main thread loop unexpectedly exited"); } thread.detach(); String name = (thread.mInitialApplication != null) ? thread.mInitialApplication.getPackageName() : "<unknown>"; Log.i(TAG, "Main thread of " + name + " is now exiting"); } }

 

 

二 、 Handler中是什麼原理,使得發送的消息時只要使用handler對象,而在消息被接受並處理時就能夠直接調用到handler中覆寫的handleMessge方法?

 

就從下面的這行代碼開始:spa

 

private void sendMessage() { handler.sendEmptyMessage(0x001); }

 

在handler類中一直跟進該方法,能夠發現下面的幾行代碼:線程

 

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

 

在上面的註釋 (5) 處能夠看到,這裏對每一個msg中的target字段進行了設置,這裏就是設置了每一個消息未來被處理時用到的handler對象。code

能夠跟進Looper方法的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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

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

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);    // (6)

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
   }

 

在註釋 (6)處能夠看到, msg.target.dispatchMessage(msg); 原來是調用的Message的 dispatchMessage()方法,不妨咱們跟進去看看:

 

/** * Handle system messages here.  */
    public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); //(7)
 } }

 

一系列優先考慮的回調判斷以後,終於輪到了自定義的處理的函數處理。

 

3、遇到This Handler class should be static or leaks might occur 警告 爲何?

 

大體意思是說:Handler 類應該爲static類型,不然有可能形成泄露。在程序消息隊列中排隊的消息保持了對目標Handler類的引用。若是Handler是個內部類,那 麼它也會保持它所在的外部類的引用。爲了不泄露這個外部類,應該將Handler聲明爲static嵌套類,而且使用對外部類的弱應用。說的很明白,由於內部類對外部類有個引用,因此當咱們在內部類中保存對外部類的弱引用時,這裏的內部類對外引用仍是存在的,這是jdk實現的,而只有咱們聲明爲靜態內部類,此時是對外部類沒有強引用的(這時它和普通的外部類沒有什麼區別,只是在訪問控制上有很大的方便性,它能夠直接訪問外部類的私有成員),這樣當線程中還有 Looper  和 Looper 的 MessageQueue 還有 Message 時( message 中的 target 保持了對 Handler 的引用),而此時 Activity已經死亡時,就能夠對Activity 進行回收了。

 

4、注意事項

 

1.  在本身定義的子線程中若是想實現handler?

Looper.prepare(); handler=new Handler(); ... Looper.loop();

 

這幾行代碼必定要在run方法中調用,而不是在自定義的 Thread 子類的構造函數中調用。由於子線程的構造方法被調用時,其實子線程尚未執行,也就是說當前的線程並不是子線程,還仍然是父線程,因此調用 Looper.prepare(); 也只是對父線程進行了線程本地化存儲了一個 Looper 對象。

2.  簡而言之

兩個線程之間用 handler 進行通訊,一個線程先在線程本地存放一個Looper對象,該對象中有消息隊列 MessageQueue 成員,只是這個Looper對象,是在線程運Looper.prepare() 時初始化的,且保證只初始化一次,進而該線程進入Loop.loop()消息循環狀態,等待其它線程調用本身的 hanldler 對象的 sendMessage() 方法往己的消息隊列中存放消息,並在有消息時讀取並處理,沒有則阻塞。

 

2.  多方協做 

若是同時有多個調用方使用一個同一工做線程(調用Looper.loop()的那個線程),則能夠先建立一個HandlerThread線程,而後調用它的getLooper方法獲得一個Looper對象,

這時每一個調用方均可以拿着這個Looper對象去構造知足各自處理邏輯的的Handler對象了,而後須要出的時候將消息丟到本身的Handler對象中便可。

相關文章
相關標籤/搜索