前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Handler中的内存泄露究竟是怎么回事?

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

原创
作者头像
Android架构
修改2019-06-19 18:00:05
9110
修改2019-06-19 18:00:05
举报
文章被收录于专栏:Android进阶之路Android进阶之路

场景1

看码识错误1:

代码语言:txt
复制
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来移除消息。

修改后的代码:

代码语言:txt
复制
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:

代码语言:txt
复制
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。

修改后的部分代码:

代码语言:txt
复制
class Scene2Activity : AppCompatActivity() {
	······
    override fun onDestroy() {
        mHandler.removeMessages(WHAT_MSG)
        super.onDestroy()
    }
    ······
}

场景3

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

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

代码语言:txt
复制
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:

代码语言:txt
复制
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对象。

修改后的部分代码:

代码语言:txt
复制
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方法源码解析

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景1
  • 场景2
  • 场景3
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档