1. 程式人生 > >Android中Looper的架構設計與賞析

Android中Looper的架構設計與賞析

poster

0.你將獲得什麼?

看完本篇文章,你將搞清楚以下所有的關於Handler相關的任何問題。如果你以後在面試中碰到Handler相關的問題,相信你會給面試官眼前一亮的感覺。

  • Handler整個訊息處理的架構是什麼樣的?
  • 什麼是ThreadLocal?Looper?MessageQueue?
  • Handler的訊息處理是怎麼和執行緒關聯的?
  • 在子執行緒中處理訊息時為什麼必須使用Looper.prepare()?
  • post()和sendMessage()有什麼不同?

1.從使用出發

a.最常用的方式

我們最常用的是在我們的Activity或者什麼元件裡直接通過匿名內部類的方式使用Handler重新整理UI

 1    private void doLogic() {
 2        new Thread(()->{
 3            // TODO 在非同步的執行緒中請求資料或者其他耗時操作,請求完資料通知主執行緒重新整理UI
 4            myHandler.sendEmptyMessage(1);
 5        }).start();
 6    }
 7
 8    Handler myHandler = new Handler() {
 9        @Override
10        public void handleMessage(Message msg) {
11            super.handleMessage(msg);
12            // TODO 資料請求到了,在這裡重新整理資料吧,一般會在Message中將資料帶過來
13        }
14    };

上面這樣做是可以實現的,但是匿名內部類會持有外部類的引用,比如如果你的外部是一個Activity,如果myHandler有一個延遲的10分鐘的訊息傳送到MainLooper的訊息佇列中,在訊息還未執行之前外部的Activity呼叫finish結束自己時,此時由於主執行緒Looper持有myHandler的引用,而myHandler又持有外部類Activity的引用導致Activity的記憶體無法釋放,就會出現記憶體洩漏。所以推薦使用靜態內部類的方式實現以避免記憶體洩漏的可能如下:

 1    Handler mHandler = new MyHandler();
 2    private void doLogic() {
 3        new Thread(()->{
 4            // TODO 在非同步的執行緒中請求資料或者其他耗時操作,請求完資料通知主執行緒重新整理UI
 5            mHandler.sendEmptyMessage(1);
 6        }).start();
 7    }
 8
 9    private static class MyHandler extends Handler{
10        @Override
11        public void handleMessage(Message msg) {
12            super.handleMessage(msg);
13            // TODO 資料請求到了,在這裡重新整理資料吧,一般會在Message中將資料帶過來
14        }
15    }

b.在子執行緒中執行handMessage

上面的是在UI執行緒(主執行緒)中執行我們的訊息處理,那是否可以在子執行緒中執行訊息處理呢?當然是可以的,如下:

 1    private void doLogic() {
 2        new Thread(()->{
 3            Looper.prepare();
 4            Handler lHandler = new MyHandler(Looper.myLooper());
 5            Looper.loop();
 6            lHandler.sendEmptyMessage(1);
 7        }).start();
 8    }
 9
10    private static class MyHandler extends Handler{
11        MyHandler(Looper looper) {
12            super(looper);
13        }
14        @Override
15        public void handleMessage(Message msg) {
16            super.handleMessage(msg);
17            // TODO 此時該方法將在子執行緒中執行
18        }
19    }

2.Handler的架構

Handler裡面有一個重要的成員變數Looper,Looper裡面維護了一個MessageQueue(訊息佇列),當我們使用handler.post或者sendMessage相關的方法都是將訊息Message放入到訊息佇列中。每一個執行緒都將擁有一個自己的Looper,是通過:

1static final ThreadLocal<Looper> sThreadLocal

實現的,顧名思義ThreadLocal是和執行緒繫結的。當我們有一個執行緒A使用sThreadLocal.set(Looper a),執行緒B使用sThreadLocal.set(Looper b)的方式儲存,如果我們線上程B中使用sThreadLocal.get()將會得到Looper b的例項。所以我們每個執行緒可以擁有獨立的Looper,Looper裡面維護了一個訊息佇列,也就是每一個執行緒維護自己的訊息佇列。

當在主執行緒中時,在你的應用啟動時系統便給我們建立了一個MainLooper存入了sThreadLocal中,所以平時我們使用Handler時,如果是在主執行緒中建立的,我們是不需再去建立一個Looper給Handler的,因為系統已經做了,所以當我們new Handler時,系統便將之前存入的Looper通過sThreadLoca中get出來,然後再去從對應的訊息佇列中讀取執行。

而當我們在子執行緒中建立Handler時,如果直接new Handler執行時肯定會報錯的,提示我們必須先呼叫Looper.prepare()方法,為什麼呢?因為我們沒有建立子執行緒對應的Looper放入sThreadLocal當中,而prepare方法就是new了一個Looper的例項通過sThreadLocal.set設定到當前執行緒的。整個建立過程類似於下圖:

looper建立

也就是說,Handler建立的時候肯定會在一個執行緒當中(主執行緒或者子執行緒),並且建立一個Looper例項與此執行緒繫結(無論是系統幫我們建立或者通過prepare自己繫結),在Looper中維護一個訊息佇列,然後looper迴圈的從訊息佇列中讀取訊息執行(在訊息佇列所線上程執行)。這就是整個Handler的執行機制了。

3.幾種傳送訊息的方式

通過Handler有很多種傳送訊息的方式:

  • post(Runable run)
  • postDelayed
  • sendEmptyMessage()
  • …等等

其實無論是通過post的方式或者send的方式,最後都是通過:

1public final boolean sendMessageDelayed(Message msg, long delayMillis)
2

我們使用post傳入的Runnable例項,也是通過:

1    private static Message getPostMessage(Runnable r) {
2        Message m = Message.obtain();
3        m.callback = r;
4        return m;
5    }

通過上面的方法,將Runnable的例項轉換為Message的例項,然後呼叫通用的方法傳送到訊息佇列中,最終會通過下面的方式放入佇列:

1    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2        msg.target = this;
3        if (mAsynchronous) {
4            msg.setAsynchronous(true);
5        }
6        return queue.enqueueMessage(msg, uptimeMillis);
7    }

最後

由於時間和篇幅有限,上述文章講的還是比較粗糙,也就是給大家一個拋磚引玉的作用,如果想要真正的探究Handler實現的整個細節,大家不妨多看看他們的原始碼,真個Handler、Looper、ThreadLocal的原始碼和註釋全部加起來也就1900行左右,還是蠻小的。值得大家研究一下。

好了,今天就到這了。寫的不好,僅供參考。

 

如果對你又一點點的幫助,請關注微信公眾號:南京Android部落

 

code