Handler用法和内存泄漏处理

  1. 关于Handler内存泄露可以看下面的博客,我经常看的博主
    Android 内存泄露:详解 Handler 内存泄露的原因

Handler的内存泄露就是Handler默认持有了Activity或这Fragment的引用,从而导致Activity销毁后,Handler还持有Activity的引用导致Activity不能被gc回收。解决方案就是静态内部类+弱引用,使用方式可以学Fragment在Activity的绑定与解绑方式,因为静态类不会持有外部类的 引用。

我用的kotlin,Java的方式代码也是一样,自己去写了,懒得贴了
/**
     * Handler,避免内存泄露
     */
    private class CustomHandler : Handler() {
        private lateinit var weakReference: WeakReference<HomeFragment>

        fun attachHandler(homeFragment: HomeFragment) {
            weakReference = WeakReference(homeFragment)
        }

        fun detachHandler() {
            weakReference.clear()
        }

        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
            when (msg?.what) {
                100 -> {
                    // 然后这里使用weakReference.get()获取弱引用保存的对象
                    // 我这里是HomeFragment
                }
            }

        }
    }

Handler的三种用法

模拟子线程发送数据

new Thread(new Runnable() {
            @Override
            public void run() {
                // 休眠5秒
                SystemClock.sleep(5000);
                Message message = Message.obtain();
                message.obj = "Handler赋值";
                //handler1.sendMessage(message);
                // 如果在休眠5秒的时间内退出界面(界面销毁),
                // 但是当休眠5秒结束此处仍然会发送消息
                // 就会造成内存泄漏
                if (handler2 != null) handler2.sendMessage(message);

                handler3.post(new Runnable() {
                    @Override
                    public void run() {
                        tvHandler.setText("Handler赋值");
                    }
                });
            }
        }).start();

1、第一种用法

/**
     * 第一种用法
     */
    private Handler handler1 = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            tvHandler.setText(msg.obj.toString());
        }
    };

2、第二种用法,推荐使用第二种用法

 /**
     * 第二种用法,推荐使用第二种用法
     */
    private Handler handler2 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            tvHandler.setText(msg.obj.toString());
            return false;
        }
    });

为什么推荐第二种用法,我们可以查看Handler源码的消息分发

/**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        // 1、此处是Handler的post用法,第三种用法
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 2、此处就是我们第二种用法传入的callback
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 3、此处就是第一种用法,算是Handler消息分发最后的倔强,哈哈
            // 用来保底接收数据
            handleMessage(msg);
        }
    }

3、Handler第三种用法,Post方法,注释1处执行的就是post传递的Runnable对象

private Handler handler3 = new Handler();

// 直接在子线程中使用post方法刷新UI
handler3.post(new Runnable() {
                    @Override
                    public void run() {
                        tvHandler.setText("Handler赋值");
                    }
                });

查看post方法的源码,我们看到Handler源码中将Runnable对象
封装到了Message对象中。

public final boolean post(@NonNull Runnable r) {
       // post方法还是调用的sendMessageDelayed,最终
       // 还是会调用到sendMessageAtTime
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
// 通过如下代码,我们看到将Runnable对象封装到了Message中
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        // 将Runnable对象封装到了Message的callback中
        m.callback = r;
        return m;
    }

最后我们回到dispatchMessage方法

public void dispatchMessage(@NonNull Message msg) {
        // 从上述源码分析,我们知道此处就是post方法的使用
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

最后看到handleCallback方法,其实就是调用了Runnable的run方法
执行run方法中的代码

private static void handleCallback(Message message) {
        message.callback.run();
    }

Handler的内存泄漏处理

1、弱引用处理(代码在最上方)

2、删除消息和回调callback

这种方式需要有消息在消息队列并且使用第二种方式使用Handler,
否则这种处理内存泄漏的方式应该还是会造成内存泄漏

@Override
    public void onDestroy() {
        super.onDestroy();
        // 清除消息和回调的callback
        handler2.removeCallbacksAndMessages(null);
    }

我验证过如下方式,在5秒消息等待之前销毁Activity,使用第一种方式创建爱女Handler还是会发送消息,还是会造成内存泄漏

new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(5000);
                Message message = Message.obtain();
                message.obj = "Handler赋值";
                message.what = 100;
                if (handler2 != null) handler1.sendMessage(message);
            }
        }).start();

即使调用了removeCallbacksAndMessages方法,第一种方式创建的
Handler仍然能收到消息,造成内存泄漏

private Handler handler1 = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            LogUtils.d(TAG, "我还是收到消息了");
            tvHandler.setText(msg.obj.toString());
        }
    };

3、Handler置空

将Handler置空之后,也就不会再继续发送消息了

@Override
    public void onDestroy() {
        super.onDestroy();
        // 为了保险可以清除一次数据
        handler2.removeMessages(100);
        // 将Handler置空,这个跟第一种弱引用有异曲同工
        handler2 = null;
    }

我们解释一下为啥清除数据是可选的,比如我们模拟消息发送的例子,
1、休眠5秒之后在发送消息,当在休眠5秒的时间段内退出界面,此时消息
还没有存储到消息队列
,所以此时清除消息是没有消息可清除的。

就像如下代码

SystemClock.sleep(5000);
Message message = Message.obtain();
message.obj = "Handler赋值";
message.what = 100;
if (handler2 != null) handler2.sendMessage(message);

2、比如如下代码,清除数据是会生效的,因为此时已经将消息发送到消息队列中。

Message message = Message.obtain();
message.obj = "Handler赋值";
message.what = 100;
// 使用Handler提供的延迟消息发送,此时消息已经存到消息队列
if (handler2 != null) handler2.sendMessageDelayed(message, 5000);

因此,推荐使用第一种和第三种处理内存泄漏的方式

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃骨头不吐股骨头皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值