Android中Looper的架構設計與賞析
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設定到當前執行緒的。整個建立過程類似於下圖:
也就是說,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部落