前言
沈陽剛剛入職,最近在閱讀之前同事的代碼法瑟,因為他的架構設計中使用了Handler模型冀膝,所以再次總結一下Handler的使用問題,這也面試的常見問題之一霎挟。
本文中可能涉及到一些源碼相關的問題窝剖,建議先了解一下Handler源碼。
正文
問題一:構建Handler異常
Handler與Looper酥夭,MessageQueue協(xié)作赐纱,是Android線程切換的主要手段之一脊奋,官方推薦開發(fā)者自己指定Handler的執(zhí)行線程,如果你使用的Handler構造函數(shù)是無參構造方法:
標記有刪除線的方法疙描,表示該方法已經廢棄诚隙,如果在開發(fā)中遇到了這樣的方法一定要了解具體api廢棄的原因,這樣可以幫助我們避免很多意想不到的問題淫痰,例如:
Default constructor associates this handler with the Looper for the current thread. If this thread does not have a looper, this handler won't be able to receive messages so an exception is thrown.
Deprecated
Implicitly choosing a Looper during Handler construction can lead to bugs where operations are silently lost (if the Handler is not expecting new tasks and quits), crashes (if a handler is sometimes created on a thread without a Looper active), or race conditions, where the thread a handler is associated with is not what the author anticipated. Instead, use an java.util.concurrent.Executor or specify the Looper explicitly, using Looper.getMainLooper, {link android.view.View#getHandler}, or similar. If the implicit thread local behavior is required for compatibility, use new Handler(Looper.myLooper()) to make it clear to readers.
大概的意思就是Handler默認的構造方法會使用Looper.myLooper()作為Handler的執(zhí)行線程最楷,如果當前線程沒有調用過Looper.prepare(),就會拋出異常:
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
...
}
所以為了避免這個異常待错,我們最好顯式的指定Looper籽孙。除了提前調用Looper.prepare(),我們還可以使用HandlerThread幫助我們創(chuàng)建Looper火俄,然后再綁定到Handler上:
val handlerThread = HandlerThread("test")
handlerThread.start()
val handler = Handler(handlerThread.looper)
到現(xiàn)在我還有疑問犯建,HandlerThread僅僅是為當前線程創(chuàng)建了Looper,為什么不叫LooperThread瓜客?既然和Handler的關聯(lián)如此密切适瓦,為什么getThreadHandler()沒有開放?希望有心得朋友留言幫我解開這個問題谱仪。
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
問題二:內存泄漏
這個問題網上已經有了很多的討論和分享玻熙,這個簡單的總結一下問題的原因:
如果Handler作為Context(Activity/Service等等)的內部類,默認內部類持有外部類的引用疯攒;
通過Handler.post或Handler.postDelay執(zhí)行一個任務之前嗦随,Context已經退出(Activity.finish), MessageQueue仍然持有這個任務,在任務都完成之前敬尺,Activity因為強引用關系枚尼,無法立即回收,導致內存泄漏或是Context使用不當導致程序崩潰砂吞;
解決辦法也非常的簡單署恍,首先通過靜態(tài)內部類的方式解決強引用的問題,如果需要引用關系蜻直,通過WeakReference修改Handler與Activity的引用關系為弱引用盯质,然后每次使用Handler的時候也去判斷一下是否Activity已經被銷毀了,防止意外操作:
/**
* Kotlin default is static inner class
* */
open class SafeHandler(looper: Looper, activity: Activity) : Handler(looper) {
private val weakActivity = WeakReference<Activity>(activity)
override fun dispatchMessage(msg: Message) {
// activity has destroyed, do not need dispatchMessage
if (isActivityDestroyed()) {
return
}
super.dispatchMessage(msg)
}
override fun sendMessageAtTime(msg: Message, uptimeMillis: Long): Boolean {
// activity has destroyed, do not need sendMessageAtTime
if (isActivityDestroyed()) {
return false
}
return super.sendMessageAtTime(msg, uptimeMillis)
}
private fun isActivityDestroyed(): Boolean {
// activity has recycled, do not need dispatchMessage
if (weakActivity.get() == null) {
return true
}
// activity has destroyed, do not need dispatchMessage
if (weakActivity.get()!!.isFinishing || weakActivity.get()!!.isDestroyed) {
return true
}
return false
}
}
private val mSafeHandler = object : SafeHandler(Looper.getMainLooper(), this) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
// do some thing
}
}
上面的代碼用的是Kotlin袭蝗,默認內部類就是靜態(tài)的唤殴,Java靜態(tài)內部類請使用:
static class SafeHandler extends Handler
除此之外,我們還應該在Activity銷毀或Handler使用結束時到腥,手動調用removeCallbacksAndMessages方法朵逝,及時清除掉殘留的任務:
handler.removeCallbacksAndMessages(null)
問題三:任務執(zhí)行時間的不穩(wěn)定性
首先要明確一點:Handler的任務調度是單線程模型。對于某些延遲任務乡范,例如我們需要500毫秒后配名,執(zhí)行某個任務啤咽,可以使用Handler:
Handler.postDelay({ /**do some thing*/}, 500)
但是這個500毫秒準確嗎?答案是非常不穩(wěn)定渠脉,因為Handler是單線程模型宇整,如果是中間等待的500ms中,Handler開啟了一個耗時任務芋膘,只有它結束了鳞青,Looper才能繼續(xù)繼續(xù)取出下一個Message,例如下面的代碼:
fun startPost() {
// 10s task
mHandler.post {
Log.e("lzp", "Sleep Task start")
Thread.sleep(10_000)
}
// 1s delay task
mHandler.postDelayed({
Log.e("lzp", "Delay Task start")
}, 1000)
}
按照我的期望为朋,應該是1s后就立刻執(zhí)行延時任務臂拓,但是我不小心之前跑了一個10s的任務,看一下log:
中間正好間隔了10s习寸,等10s任務結束胶惰,Looper發(fā)現(xiàn)延時的任務的時間戳早就過了,于是立刻執(zhí)行了延時任務霞溪。這不符合我的期望孵滞,為了解決這個問題,必須將10s任務放入其他線程中鸯匹,停止占用當前線程的資源坊饶。
除了單線程模型的問題,Handler的調度還會收到系統(tǒng)的影響殴蓬,如果app在后臺幼东,Handler可能會執(zhí)行慢或不執(zhí)行,所以Handler也并不適合后臺任務科雳。
所以總結一句話:Handler適合執(zhí)行短小精悍,反應迅速脓杉,執(zhí)行時間要求不精確的任務糟秘,它是一個調度角色,而非執(zhí)行角色球散。
總結
以上三個問題就是今天跟大家分享的內容尿赚,Handler作為Android線程調度老大哥,個人覺得它的解耦方式有點類似于觀察者模式蕉堰,閱讀代碼時反復橫跳凌净,對于閱讀體驗還是影響挺大的,所以Handler雖然好用屋讶,但是不要濫用冰寻。