Handle消息機制之一個問題引發的源碼分析

我們都知道,在子線程中嘗試進行UI操作或者在主線程中做耗時的操作(網絡請求,大量數據庫操作等),程序就有可能會報ANR或造成崩潰。

轉載請注明出處:Vincent Blog‘s

為什么說是一個問題引發的對Handler源碼的分析呢?先賣個關子這里先不說。我們先談一談Handler機制的通俗解釋,如下:

  • Handler機制是處理不同線程間通信,那么我們是不是主要涉及到Handler類和Thread類

Thread類(實現了Runnable接口)

MessageQueue:我們應該可以猜出來是消息隊列,存放線程存入的消息
Looper:管理線程的工具

Handler類

重寫了handlerMessage方法,用于不同線程間通信,舉個實際例子:
在實際開發中我們要從網絡獲取數據,對于網絡請求數據是不可能放在主線程(程序啟動時就會開啟主線程也叫UI線程)中執行,
請求數據是耗時操作,如果放在主線程會造成程序ANR,一般我們是怎樣處理,開啟一條子線程,
把一切耗時的操作放在子線程執行,讓UI線程(從字面就可看出是專門管理UI界面的)去更新UI,
但是問題來了,當我們網絡數據請回來后,UI線程就怎么知道去將數據顯示在界面呢?

首先在主線中創建子線程,讓子線程去完成網絡請求這一耗時操作,在此之前需要在主線程創建Handler對象

     Handler mHandler=new Handler(){
      @Override
      public void handleMessage(Message msg) {
          super.handleMessage(msg);
       }
   };

接下來說一下怎樣將數據通過主線程更新顯示在界面。

  • 子線程將自己請求回來的消息放進自己的MessageQueue(消息隊列)
  • 子線程通過Looper將MessageQueue里面的數據取出
  • 子線程通過Looper將數據發送給Handler
  • 主線程通過Looper將Handler中的數據取出
  • 主線程通過Looper將取出的的數據發送給主線程的MessageQueue
  • 最后主線程執行對消息處理的任務

我相信,大家對Handler機制有一定的了解了,Handler機制在面試中也是經常問的一道題。OK,接下來就要引入標題的問題了,這個問題還是我同學問我的,Can't create handler inside thread that has not called Looper.prepare()報這個錯問我怎樣解決,我當時看了一下他的代碼(很高冷的-)對他說需要在子線程調一下Looper.prepare(),之后在創建Handler對象,但是我們就僅限于知道怎樣解決問題嗎,顯然這不符合我的風格,要想弄清楚原理我們只能從源碼著手,OK,我們來看一下Handler的源碼,為什么不調用Looper.prepare()就不行呢

Handler無參構造函數

Handler.png

??看紅色矩形框內Looper.myLooper()方法獲取了一個對象,做了一個判斷如果mLooper等于空時拋了一個運行時異常,該錯誤就是Can't create handler inside thread that has not called Looper.prepare(),那么mLooper對象什么時候為空呢?我們需要看一下Looper.myLooper()方法中的代碼:

mylooper.png

只有一行,sThreadLocal取出的是Looper,如果Looper存在,那么就直接返回Looper,如果不存在那就直接返回null,我們再接著看Looper.prepare()方法中的代碼:

looperprepare.png

不帶參的prepare方法中調用的還是帶參的prepare方法,我們直接看帶參的這個,首先做了一個判斷,sThreadLocal是否存在Looper,存在拋出一個運行時異常,如果不存在就直接創建一個新的Looper,這里應該知道為什么要先調用Looper.prepare()方法了吧,只有存在Looper后才能new Handler對象,并且每一個線程只能對應一個Looper。可能有的哥們會問,我在主線程也沒有調用Looper.prepare()啊,為什么沒有報錯呢?這是因為主線程默認就存在一個Looper并不需要我們手動創建。我們看一下主線程ActivityThread類的main方法:

activitymain.png

我們看一下紅色框內調用了Looper.prepareMainLooper()方法,其實看一下prepareMainLooper()里面的代碼我們就會知道調用的還是Looper.prepare()

prepareMainLooper.png

這次是不是很清楚了,也就是主線程可以直接創建Handler對象,子線程需要先調用Looper.prepare();在創建Handler對象。

在上面我們已經通俗的講解了Handler機制,現在我們在進一步來了解Handler,在Handler中有好幾種發送消息的方法,我們主要看一下sendMessageAtTime(Message msg,long uptimeMillis)方法

sendMessage.png

msg就是要發送的消息,uptimeMillis是發送消息的時間,然后將這兩個參數傳入MessageQueue的enqueueMessage方法中,MessageQueue我們在上面已經說了就是消息隊列,對消息進行排列,并且有入隊出隊的方法,MessageQueue通過Looper的構造函數創建的,一個Looper對應一個MessageQueue,而enqueueMessage我們應該能猜出就是入隊方法,我們來看一看它的源碼:

enqueueMessage.png

我們要知道,消息在MessageQueue中并不是用集合或者其他保存起來的,而是使用mMessages對象表示當前待處理的消息,從紅色框內可以看出,所謂入隊其實就是將消息按照發送消息時的時間進行排序,這里的時間就是上面說的uptimeMillis,通過時間順序調用msg.next確定下一條消息是什么。看完入隊,我們看一下出隊,我們來看一看Looper.loop()方法:

loop.png

你們看出來了什么,next()是不是就是出隊的方法,你們有興趣可以去MessageQueue中看看next()方法源碼,我這里就不貼出來了,要不然一說停不下收不了尾。這里簡單說一說消息出隊的原理,如果MessageQueue存在mMessages待處理消息,那么Looper就取出這條消息,然后下一條就成為mMessages待處理消息,就這里一直循環,如果MessageQueue不存在mMessages待處理消息,那么就成為阻塞狀態,直到下一條消息入隊。看第二條紅色線我們發現出隊的消息都經過msg.target.dispatchMessage()進行處理,心細的同學可能知道msg.target其實就是Handler,不知道的同學返回去看sendMessageAtTime方法的源碼第六行,接下來我們接著看Handler的dispatchMessage()

dispatchMessage.png

看了之后是不是感覺豁然開朗,我們經過一系列的發送消息、入隊、出隊、接收消息等,到最后調用Handler的handlerMessage方法,這時handlerMessage方法已經在主線程運行,就可以更新UI了。

呼,真心累,就到這里吧,我們下面文章見-

<b><i>聯系方式</i>:lijiandongv@163.com</b> 有什么問題或者建議歡迎留言到我的郵箱

每日一碗雞湯

<b>每當你想放棄的時候,就想想那一刻你的勇氣。讓成為過去的那一刻的勇氣,變成此時此刻,你堅持的勇氣。</b>

3.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容