Handler中的内存泄露究竟是怎么回事?

场景1

看码识错误1:

class Scene1Activity : AppCompatActivity() {
    private val mHandler = Handler()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //延迟10s
        val msg = Message.obtain()
        msg.what = WHAT_MSG
        mHandler.sendMessageDelayed(msg, 10000)
    }
    companion object {
        private const val WHAT_MSG = 1
        fun startActivity(activity: Activity){
            activity.startActivity(Intent(activity, Scene1Activity::class.java))
        }
    }
}

代码很简单,只是创建一个mHandler变量,并在onCreate中发送一个延迟消息。

那问题来了,这种写法有没有潜在的内存泄露?换一种说法Scene1Activity会不会泄露,mHandler会不会泄露?

答案是Scene1Activity没有泄露,mHandler会有潜在的泄露。

Scene1Activity没有泄露很好理解,退出Scene1Activity后没有对象引用它。

那问题又来了,而mHandler为什么会泄露呢?

我们先看一下mat专业版引用链。

抽象手动版引用链:

每个Message的target都会引用Handler对象,用于处理消息。Scene1Activity中的mHandler会被发送的Message对象引用。因此从MainThread(GCRoot)到Handler对象可达。mHandler会被泄露。只有当延迟的消息被处理以后才会释放mHandler对象。

Notice : 默认的Handler方法会获取当前的线程的Looper,Scene1Activity中的mHandler会持有主线程的Looper,因此发送消息的时候,也是向主线程的Looper的MessageQueue添加消息。

修改此问题可以直接把MessageQueue的到mMessage的引用链给咔嚓了,从MainThread(GcRoot)就到Handler对象不可达,自然就不会存在泄漏了。

也就是onDestroy中通过mHandler来移除消息。

修改后的代码:

class Scene1Activity : AppCompatActivity() {
    private val mHandler = Handler()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //延迟10s
        val msg = Message.obtain()
        msg.what = WHAT_MSG
        mHandler.sendMessageDelayed(msg, 10000)
    }
    override fun onDestroy() {
        mHandler.removeMessages(WHAT_MSG)
        super.onDestroy()
    }
    companion object {
        private const val WHAT_MSG = 1
        fun startActivity(activity: Activity){
            activity.startActivity(Intent(activity, Scene1Activity::class.java))
        }
    }
}

Notice:其实Scene1Activity中的mHandler最好在伴生对象中声明为常量,避免每次进入Scene1Activity中都会创建mHandler,避免内存抖动。

场景2

看码识错误2:

class Scene2Activity : AppCompatActivity() {

    private val mHandler = object : Handler() {
        override fun handleMessage(msg: Message?) {
            Toast.makeText(this@Scene2Activity, "haha", Toast.LENGTH_LONG).show()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val msg = Message.obtain()
        msg.what = WHAT_MSG
        mHandler.sendMessageDelayed(msg, 100_000)
    }

    companion object {
        private const val WHAT_MSG = 2
        fun startActivity(activity: Activity) {
            activity.startActivity(Intent(activity, Scene2Activity::class.java))
        }
    }
}

这个呢,猜也能猜对Scene2Activity,mHandler都会潜在的泄露。😏😏😏。

我们先来看一下mat中的引用链:

抽象手动版引用链:

从MainThread到Handler对象(target),到Scene2Activity对象都是可达的。因此两者都会被泄露。

我们修改此问题可以直接把MessageQueue的mMessage的引用链给咔嚓了,从MainThread(GcRoot)到Handler对象,到Scene2Activity对象不可达,自然也就不会存在泄漏了。

也就是在退出时onDestroy中移除Message。

修改后的部分代码:

class Scene2Activity : AppCompatActivity() {
	······
    override fun onDestroy() {
        mHandler.removeMessages(WHAT_MSG)
        super.onDestroy()
    }
    ······
}

场景3

在场景2的基础上有个想法,能不能把Handler对象到Scene2Activity的引用链给咔嚓了呢?从而实现让Scene2Activity可回收呢。

于是乎写了一个带有弱引用回调的Handler。

class WeakReferenceHandler<T : WeakReferenceHandler.Callback> : Handler {
    private var mWeakReference: WeakReference<T>

    constructor(callback: T) : super() {
        mWeakReference = WeakReference(callback)
    }

    override fun handleMessage(msg: Message) {
        val callback = mWeakReference.get()
        if (callback != null) {
            callback.handleMessage(msg)
        } else {
            Log.e(TAG, "mWeakReference.get() is null ")
        }
    }

    class Callback {
        fun handleMessage(msg: Message) {}
    }

    companion object {
        private const val TAG = "WeakReferenceHandler"
    }
}

看码识错误3:

class Scene3Activity : AppCompatActivity() {
    private val mHandler = WeakReferenceHandler(object : WeakReferenceHandler.Callback {
        override fun handleMessage(msg: Message) {
            Toast.makeText(this@Scene3Activity, "haha", Toast.LENGTH_LONG).show()
        }
    })

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val msg = Message.obtain()
        msg.what = WHAT_MSG
        mHandler.sendMessageDelayed(msg, 10_000)
    }

    companion object {
        private const val WHAT_MSG = 3
        fun startActivity(activity: Activity) {
            activity.startActivity(Intent(activity, Scene3Activity::class.java))
        }
    }
}

这段代码看起来非常完美。😁😁😁。

只是看起来完美罢了。有个隐藏的炸弹💣。我们来分析一下引用链。

ActivityThread作为GCRoot是假设的,这里只是为了方便分析。

在Scene3Activity中WeakReferenceHandler创建时直接传递匿名的Callback对象。而此Callback对象仅仅被mHandler持有弱引用。只有弱引用的对象只能存活到下次GC之前,一旦GC,只有弱引用的对象就会被回收。因此一旦GC,我们的Toast就不会弹了。🤣🤣🤣。惊不惊喜,意不意外。

修改的话让Scene3Activity中创建一个内部的变量来强引用匿名的Callback对象。

修改后的部分代码:

class Scene3Activity : AppCompatActivity() {
	private val mCallback = object : WeakReferenceHandler.Callback {
        override fun handleMessage(msg: Message) {
            Toast.makeText(this@Scene3Activity, "haha", Toast.LENGTH_LONG).show()
        }
    }
    private val mHandler = WeakReferenceHandler(mCallback)
    ······
}

修改后的应用链:

由于Scene3Activity强引用了Callback的匿名对象。因此可以正常了。

Notice:这一节也就分析分析就可以了,还是要正确的使用Handler。像这样的弱引用的方式其实没必要。

总结

Handler的内存泄露基本都是发送延迟消息导致的。注意恰当的时机移除消息,就可以避免内存泄露了。

Handler与Looper方法源码解析

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券