Handler内存泄漏

最近看了handler相关代码,将所学handler内存泄漏相关总结一下。

1.handler内存泄漏根本原因:

sThreadLocal是GC Roots,可达Activity。故Activity不会被回收。

2.下面分析整个流程

2.1我们常使用Handler的方式是在主线程中构造一个Handler对象。如下

Handler handler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
};

java匿名内部类会持有外部类引用,故handler持有activity,即handler->activity。

2.2下面看Handler类的代码

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

Handler类的enqueueMessage()方法第一句:msg.target = this;message持有handler,即message->handler。
之后是MessageQueue持有Message,这就不作分析了。现有持有链:messageQueue->message->handler->activity

2.3然后看下messageQueue是被谁持有了,在Looper的构造方法中

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

从这里看出looper持有messageQueue。即looper->messageQueue。

2.4接着看下Looper.prepare()方法做了什么

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

这里将looper对象放进sThreadLocal。sThreadLocal持有looper。

2.5我们继续看sThreadLocal是什么。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

sThreadLocal是一个静态变量,即GC Roots。
至此,我们完整的可达链为sThreadLocal->looper->messageQueue->message->handler->activity。

总结:由于sThreadLocal是GC Root,activity是可达的,所以activity不会被回收,就会造成内存泄漏。

3.修改handler内存泄漏

  1. 修改Handler为static类,并使用弱引用,这样才能在静态内部类中调用外部类方法。
    此种方式是打断handler->activity。因为java静态匿名内部类不会持有外部类引用。

下面代码是Kotlin中使用Handler的方式(静态内部类 + 弱引用)

class MainActivity : AppCompatActivity() {
    private val myMessage = 1
    
    private val handler by lazy { StaticHandler(this) }
    
    lateinit var binding: ActivityMainBinding
    
    /**
     * 静态内部类 + 弱引用
     */
    class StaticHandler(obj: RepaymentConfirmActivity) : Handler(Looper.getMainLooper()) {
        private val ref = WeakReference(obj)
        override fun handleMessage(msg: Message) {
            ref.get()?.apply {
                when(msg.what) {
                    myMessage -> Log.d("mainActivity", "handleMessage: ${getData()}")
                }
            }
        }
    }

    fun getData() : Int {
        return 2
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val message = Message.obtain()
        message.what = myMessage
        handler.sendMessage(message)
    }
}
  1. 在activity.onDestory()方法中,将messageQueue中的消息清除掉。
    此种方式是打断messageQueue->message。
    override fun onDestroy() {
        handler.removeCallbacksAndMessages(null)
        super.onDestroy()
    }

4.GC Roots是什么

以下引用自刘望舒大佬的著作《android进阶解密》:
选定一些对象作为GC Roots,并组成根对象集合,然后以这些GC Roots的对象作为起始点,向下搜索,如果目标对象到GC Roots是连接着的,我们则称该目标对象是可达的,如果目标对象不可达则说明目标对象是可以被回收的对象。

在java中,可以作为GC Roots的对象主要有以下几种:
1.java栈中引用的对象
2.本地方法栈中JNI引用的对象
3.方法区中运行时常量池引用的对象
4.方法区中静态属性引用的对象
5.运行中的线程
6.由引导类加载器加载的对象
7.GC控制的对象

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值