android 中的 Handler 線程間通訊

來源:互聯網
上載者:User

標籤:android   style   blog   io   ar   color   使用   sp   for   

  一、 在MainActivity中為什麼只是類似的寫一行如下代碼就可以使用handler了呢?

 

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

 

開啟handler的源碼可以在它的建構函式中,看到如下的幾行代碼:

 

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(); 這條語句,近兒,可以看到下面的代碼:

 

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

 


其中 sThreadLocal Looper 類中被聲明為:

 

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()方法:

 

 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對象來存放資料的,跟進一步:

 

 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()方法:

 

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方法?

 

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

 

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對象。

可以跟進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)        }    }

 

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

 

三、遇到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 進行回收了。

 四、注意事項

 

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() 方法往自己的訊息佇列中存放訊息,並在有訊息時讀取並處理,沒有則阻塞。

android 中的 Handler 線程間通訊

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.