Handler引發的泄露(通常發生在Activity、Fragment等容器)、crash是Android開發中常見的問題,也是面試時非常容易被問到的技術點。關于Handler為何會引起容器泄露,網上有很多的文章,這里就簡單提一下引用鏈:
Thread->ThreadLocal->Looper->MessageQueue->Message->Handler->Activity
Handler產生的泄露一般是暫時的,當消息成功調度后,從消息隊列中移除,上面的引用鏈也便就不存在了,在下次gc時,Activity便可以正常釋放。因此大多情況下,Handler引起的泄露問題并不可怕(極端情況另說),可怕的是引起crash。下面重點討論下Handler如何引起crash,看個偽代碼:
class TestFragment: Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Toast.makeText(activity, "AAA", Toast.LENGTH_SHORT).show()
Handler().postDelayed({
Toast.makeText(activity, "BBB", Toast.LENGTH_SHORT).show()
}, 5000)
parentFragmentManager.beginTransaction().run {
remove(this@TestFragment)
commitAllowingStateLoss()
}
}
}
啟動TestFragment后,會先看到一條"AAA"的吐司,然后在logcat中看到如下crash日志:
JAVA.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
at android.widget.Toast.<init>(Toast.java:121)
at android.widget.Toast.makeText(Toast.java:286)
at android.widget.Toast.makeText(Toast.java:276)
at com.ada.test_App.TestFragment.onCreate$lambda-0(MainActivity.kt:136)
at com.ada.test_app.TestFragment.$r8$lambda$ZvBB3ieIi-neYI0Ok2qP--pCPEg(Unknown Source:0)
at com.ada.test_app.TestFragment$$ExternalSyntheticLambda0.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:238)
at android.app.ActivityThread.main(ActivityThread.java:7798)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995)
原因是當Fragment生命周期結束時,會將activity對象置為null,等消息延遲調度時,取得的便是一個空的activity,因此出現了空指針異常。解決的辦法很簡單,加個空判斷就好了。然而現實中的場景會復雜很多,而且開發人員素質參差不齊,沒法保證所有場景都正確處理了,我們希望能有一套通用的解決方案。以下是筆者寫的SafeHandler,在實際項目中已經廣泛使用,是一個小而美的組件:
class SafeHandler(owner: LifecycleOwner, looper: Looper = Looper.getMainLooper()): Handler(looper), LifecycleObserver {
private val host = WeakReference(owner)
init {
owner.lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
removeCallbacksAndMessages(null)
}
override fun dispatchMessage(msg: Message) {
val owner = host.get()
if (owner != null && owner.lifecycle.currentState != Lifecycle.State.DESTROYED) {
super.dispatchMessage(msg)
}
}
}
fun handlerOf(owner: LifecycleOwner, looper: Looper = Looper.getMainLooper()): Handler {
return SafeHandler(owner, looper)
}
fun LifecycleOwner.newHandler(looper: Looper = Looper.getMainLooper()): Handler {
return handlerOf(this, looper)
}
分析下代碼:
- 這里looper默認為主線程looper,而構建系統的Handler在沒有設置looper時,默認是獲取當前線程looper。從Handler的通用性來說這樣設計沒有問題,但從業務的角度來說,我們使用的Handler絕大多數是位于主線程中,因此這樣設計會更安全一些,避免一些開發者因為對Handler的機制不夠了解而使用默認構建方法構建出了錯誤的Handler。
- LifecycleOwner使用弱引用存儲,SafeHandler本身就是為了解決內存泄露及crash,當然不能因為自身的缺陷導致另外的泄露了。
- 監聽LifecycleOwner的銷毀,在銷毀時清除所有消息。
- 在調度消息時判斷LifecycleOwner的狀態,如果已經銷毀,就不允許執行。既然已經在onDestroy時清空消息了,為什么還要做這步操作呢?這是因為外部有可能在onDestroy后依然使用Handler去發送消息。