Handler 詳解,handler詳解
這個詳解基本小結了Handler的使用和相關的知識,在瞭解這些知識前,我們先來看看這些問題.
- Handler 是什嗎?
- 子線程更新UI有幾種典型方式,這些方式本質上是什麼樣的?
- 子線程真的不能更新UI嗎?
- HandlerThread是什嗎?
- 主線程和子線程之間如何相互連信?
好了,如果上面的問題,你能對答如流,並且深知其中的原理那麼沒有必要繼續看下去了,反之,就該好好補補了(^o^)/~。
Handler是什麼
handler是Android給我們提供來更新UI的一套機制,也是一套訊息處理的機制,我們可以發送訊息,也可以通過塔來處理訊息,handler在我們的framework中是非常常見的。
看完上面的解釋,我們有個疑問,為什麼要用Handler了?
因為android在設計的時候,就封裝了一套訊息建立、傳遞、處理機制,如果不遵循這樣的機制就沒有辦法更新UI資訊,就會拋出異常資訊;
接下來我們就從源碼的角度看一下Google是如何線上程中封裝這麼一套Handler機制的,我們先來找一下Java的入口函數Main方法。
在android.app.ActivityThread.java中。
接著我們重點關注 L4 L10 L20這三行代碼,我們先一步一步跟進去
L4:
然後看看L88和L93
由上面的代碼可以看到,一開始就初始化了一個Looper執行個體,然後將這個執行個體儲存在sThreadLocal成員變數中,然後在需要的時候調用myLoop()方法,再把這個Looper執行個體取出來。所以很容易得到,一個線程對應唯一的Looper執行個體
L10:
在看第10行代碼sMainThreadHandler = thread.getHandler();之前,先看看第6行代碼
ActivityThread thread = new ActivityThread();
然後我們看一下ActivityThread這個類的成員變數:
第27行,可以看到一個很奇怪的類 H 類,成員變數 final H mH = new H();
接下來看看這個最精簡的類:
是不是看到了我們熟悉的Activity的生命週期,對的,Activity的生命週期就是在這裡觸發的,所以最開始的L10sMainThreadHandler = thread.getHandler();取回的Handler執行個體就是這麼一個H類的執行個體。
final Handler getHandler() { return mH;}
L20:
上面的代碼首先取得Looper執行個體,然後取得該執行個體的MessageQueue,然後死迴圈遍曆這個queue。
至此,UI線程的Looper準備完畢。
子線程更新UI有幾種典型方式,這些方式本質上是什麼樣的
既然Google為我們準備了Handler機制,所以我們在子線程中和主線程互動變得很簡單了,那麼子線程如何通過Handler和UI線程互動呢,又有哪些方法,這些互動的方式本質上是什麼樣子的呢,下面我們一起來探個究竟。
從Google給我們提供的SDK中我們大概知道有以下幾種方式:
我們直接看一下上面四種互動方式的Demo:
這裡特別補充一下L24添加父類構造器方法:
public UpdateViewHandler(TextView textView){ super(); this.textView = new WeakReference<>(textView);}
上面代碼是java1.8引入Lambda運算式的寫法。代碼比較簡單,這裡不在贅述,我們接下來感興趣的是,上面的四種互動方法的源碼是神馬樣子的?
- 我們先看 updateViewByHandlerPost 第一種方式
上面代碼需要注意的是L37行msg.target = this;,這行代碼說明,Handler發送訊息總得有個接收方,而這個接收方不是別人,就是自己(Handler執行個體本身)。
- 接下來看updateViewByHandlerMessage
上面的代碼需要注意的是L3,這裡的Message執行個體有兩種方式,分別是
Message message = Message.obtain();和 Message message = new Message();這兩種方式哪種更好還是沒有區別?為什麼,自己想一想。
- 接下來看updateViewByRunOnUiThread
如果不在UI線程,還是調用Handler的post
還是調用Handler的post,所以上面四種方式,本質上都是調用Handler的通訊機制,所以在Android中子線程更新UI本質上都是通過Handler機制來處理。
子線程真的不能更新UI嗎
通過上面的子線程更新UI四種方式,我們基本瞭解了Android的Handler機制,但是子線程真的不能更新UI嗎?我們來做個實驗好了。
運行以後,小夥伴都驚呆了,居然完美運行,這是神馬情況?
我們先不直接回答這個問題,換個方式實現一下:
這樣運行以後,是不是立馬Crash了?並拋出Only the original thread that created a view hierarchy can touch its views.異常,這又是神馬情況?
回答這個問題還是需要回到ActvityThread這個類中去找答案,具體的細節這裡不在贅述了,直接給出原因吧,onCreate生命週期的時候ViewParent還沒有初始化完畢,這個時候在View上更新UI的時候會引起重繪,直接來看看代碼:
注意L16行,p為空白,所以不會執行p.invalidateChild(this, damage);方法
我們看看這個方法是弄啥咧。
是不是看到熟悉的異常出現在哪裡了,以上的詭異事件是不是到此就弄清楚了,主要是ViewParent沒有初始化完畢,所以不會執行UI線程檢查,如果一旦初始化完畢,必然檢查UI線程的更新操作,這個時候任何子線程企圖更新UI的操作都無處遁形了。
HandlerThread是什麼
上面講述的都是子線程主動和UI線程溝通,而一向高貴冷豔的UI線程從來沒有主動和子線程打聲招呼,是不是過於無禮了,那麼主線程怎麼樣和子線程打招呼呢?這裡Google很任性話的給我們提供了HandlerThread,這個類就是用來方便UI線程和子線程溝通的,那麼我們可以不甩這個類嗎,直接自己動手,豐衣足食如何,我們來首先不用HandlerThread小試一下。
代碼的思路是麼有問題的,主線程和子線程先溝通,然後子線程有結果以後再和主線程溝通,這個邏輯沒有問題,但是我們run的時候提示在L66 有個null 指標異常,這個問題是由於多線程並非引起的,因為這個looper還沒有來得及初始化,所以報了個null 指標異常,那麼Google給我們提供的HandlerThread就做了同步處理,所以如果將我們自己寫的帶有Looper的子線程換成HandlerThread就不會有這個多線程並發引起的問題。
主線程和子線程之間如何相互連信
上面已經基本回答了主線程和子線程之間如何相互連信,一般建議使用HandlerThread,如果不樂意直接使用,也可以自己定義一個帶有Looper的Thread的,但是必須處理好多線程並發問題。另外,對於Handler的構造器,其中有個參數是Callback,這個Callback作用是什嗎?看看源碼一切皆清楚了
很明顯的L35行,如果這個callback返回的是true 就不會繼續往下執行了,所以這個callback有截獲的作用。
關於Handler的介紹到這裡基本結束了,當然Handler的其他知識還有不少,這裡不在贅述。