Android安全且无泄露Handler

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去发送消息 。

【Android安全且无泄露Handler】


    推荐阅读