這是 面試系列 的第五期映挂。本期我們將來探討一下 Android 異步消息處理線程 —— Handler。
往期內(nèi)容傳遞:
Android 面試(一):說說 Android 的四種啟動模式
Android 面試(二):如何理解 Activity 的生命周期
Android 面試(三):用廣播 BroadcastReceiver 更新 UI 界面真的好嗎盗尸?
Android 面試(四):Android Service 你真的能應(yīng)答自如了嗎柑船?
開始
Android 的消息機制,也就是 Handler
機制泼各,相信各位都已經(jīng)是爛熟于心了吧鞍时。即創(chuàng)建一個 Message
對象,然后借助 Handler
發(fā)送出去扣蜻,之后在 Handler
的 handleMessage()
方法中獲取剛才發(fā)送的 Message
對象逆巍,然后在這里進行 UI 操作就不會出現(xiàn)崩潰了。
既然 Handler 操作都爛熟于心莽使,還講這個干什么锐极?
嗯,對芳肌,在 Android 開發(fā)中灵再,我們確實經(jīng)常用到它,對于基本代碼流程自然也是倒背如流亿笤,但了解它的原理的人卻不是很多翎迁,所以面試官通常會考驗?zāi)銓?Handler
源碼機制的理解,畢竟只有知己知彼净薛,才能百戰(zhàn)不殆嘛汪榔。
我們都知道 UI 操作只能在主線程進行,通常是怎么在子線程更新 UI 的罕拂?
- Handler
- Activity.runOnUiThread()
- View.post(Runnable r)
講講 Handler 機制吧
Handler 主要由以下部分組成揍异。
Handler
Handler
是一個消息輔助類全陨,主要負責(zé)向消息池發(fā)送各種消息事件Handler.sendMessage()
和處理相應(yīng)的消息事件Handler.handleMessage()
。Message
Message
即消息衷掷,它能容納任意數(shù)據(jù)辱姨,相當(dāng)于一個信息載體。MessageQueue
MessageQueue
如其名戚嗅,消息隊列雨涛。它按時序?qū)⑾⒉迦腙犃校钚〉臅r間戳將被優(yōu)先處理懦胞。Looper
Looper
負責(zé)從消息隊列讀取消息替久,然后分發(fā)給對應(yīng)的Handler
進行處理。它是一個死循環(huán)躏尉,不斷地調(diào)用MessageQueue.next()
去讀取消息蚯根,在沒有消息分發(fā)的時候會變成阻塞狀態(tài),在有消息可用時繼續(xù)輪詢胀糜。
在 Android 開發(fā)中使用 Handler 有什么需要注意的
首先自然是在工作線程中創(chuàng)建自己的消息隊列必須要調(diào)用 Looper.prepare()
颅拦,并且在一個線程中只能調(diào)用一次。當(dāng)然教藻,僅僅創(chuàng)建了 Looper
還不行距帅,還必須使用 Looper.loop()
開啟消息循環(huán),要不然要 Looper
也沒用括堤。
我們平時在開發(fā)中不用調(diào)用是因為默認會調(diào)用主線程的 Looper碌秸。
此外,一個線程中只能有一個 Looper 對象和一個 MessageQueue 對象悄窃。
大概的標(biāo)準(zhǔn)寫法是這樣讥电。
Looper.prepare();
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "在子線程中定義Handler,并接收到消息轧抗。允趟。。");
}
};
Looper.loop();
另外一個比較逞恢拢考察的就是 Handler 可能引起的內(nèi)存泄漏了。
Handler 可能引起的內(nèi)存泄漏
我們經(jīng)常會寫這樣的代碼涣楷。
private final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
當(dāng)你這樣寫的時候分唾,你一定會收到編譯器的黃色警告。
In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class
在 Java 中狮斗,非靜態(tài)的內(nèi)部類和匿名內(nèi)部類都會隱式地持有其外部類的引用绽乔,而靜態(tài)的內(nèi)部類不會持有外部類的引用。
要解決這樣的問題碳褒,我們在繼承 Handler
的時候折砸,要么是放在單獨的類文件中看疗,要么直接使用靜態(tài)內(nèi)部類。當(dāng)需要在靜態(tài)內(nèi)部類中調(diào)用外部的 Activity 的時候睦授,我們可以直接采用弱引用進行處理两芳,所以我們大概修改后的代碼如下。
private static final class MyHandler extends Handler{
private final WeakReference<MainActivity> mWeakReference;
public MyHandler(MainActivity activity){
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = mWeakReference.get();
if (activity != null){
// 開始寫業(yè)務(wù)代碼
}
}
}
private MyHandler mMyHandler = new MyHandler(this);
其實在我們實際開發(fā)中去枷,不止一個地方可能會用到內(nèi)部類怖辆,我們都需要在這樣的情況下盡量使用靜態(tài)內(nèi)部類加弱引用的方式解決我們可能出現(xiàn)的內(nèi)存泄漏問題。
用過 HandlerThread 嗎删顶?它是干嘛的竖螃?
HandlerThread
是 Android API 提供的一個便捷的類,使用它可以讓我們快速地創(chuàng)建一個帶有 Looper
的線程逗余,有了 Looper
這個線程特咆,我們又可以生成 Handler
,本質(zhì)上也就是一個建立了內(nèi)部 Looper
的普通 Thread
录粱。
我們在上面提到的子線程建立 Handler 大概代碼是這樣腻格。
new Thread(new Runnable() {
@Override public void run() {
// 準(zhǔn)備一個 Looper,Looper 創(chuàng)建時對應(yīng)的 MessageQueue 也會被創(chuàng)建
Looper.prepare();
// 創(chuàng)建 Handler 并 post 一個 Message 到 MessageQueue
new Handler().post(new Runnable() {
@Override
public void run() {
MLog.i("Handler in " + Thread.currentThread().getName());
}
});
// Looper 開始不斷的從 MessageQueue 取出消息并再次交給 Handler 執(zhí)行
// 此時 Lopper 進入到一個無限循環(huán)中关摇,后面的代碼都不會被執(zhí)行
Looper.loop();
}
}).start();
而采用 HandlerThread
可以直接把步驟簡化為這樣:
// 1. 創(chuàng)建 HandlerThread 并準(zhǔn)備 Looper
handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();
// 2. 創(chuàng)建 Handler 并綁定 handlerThread 的 Looper
new Handler(handlerThread.getLooper()).post(new Runnable() {
@Override
public void run() {
// 注意:Handler 綁定了子線程的 Looper荒叶,這個方法也會運行在子線程,不可以更新 UI
MLog.i("Handler in " + Thread.currentThread().getName());
}
});
// 3. 退出
@Override public void onDestroy() {
super.onDestroy();
handlerThread.quit();
}
其中必須注意的是输虱,workerThread.start() 是必須要執(zhí)行的些楣。
至于如何使用 HandlerThread
來執(zhí)行任務(wù),主要是調(diào)用 Handler
的 API宪睹。
- 使用 post 方法提交任務(wù)愁茁,
postAtFrontOfQueue()
將任務(wù)加入到隊列前端,postAtTime()
指定時間提交任務(wù)亭病,postDelayed()
延后提交任務(wù)鹅很。 - 使用
sendMessage()
方法可以發(fā)送消息,sendMessageAtFrontOfQueue()
將該消息放入消息隊列前端罪帖,sendMessageAtTime()
指定時間發(fā)送消息促煮,sendMessageDelayed()
延后提交消息。
HandlerThread 的 quit() 和 quitSafety() 有啥區(qū)別整袁?
兩個方法作用都是結(jié)束 Looper
的運行菠齿。它們的區(qū)別是,quit()
方法會直接移除 MessageQueue
中的所有消息坐昙,然后終止 MesseageQueue
绳匀,而 quitSafety()
會將 MessageQueue
中已有的消息處理完成后(不再接收新消息)再終止 MessageQueue
。