Handler内存泄漏

本文详细解析了Android中Handler如何导致内存泄漏,分析了内存泄漏的原因,并提供了多种解决方法,包括静态内部类、弱引用、及时清除消息等。同时,通过一个具体的例子展示了内存泄漏的过程和解决思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、先上代码

有如下代码:

    private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                mText.setText("收到消息");
            }
        };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);
        mHandler.sendEmptyMessageDelayed(0, 5000);
        finish();
    }

在android studio中,lint工具会提示:
这里写图片描述
提示信息是:Handler类应该被静态,否则可能会发生内存泄露。

二、为什么会是这样呢?了解一下Handler:

  1、当Android程序第一次创建的时候,在主线程同时会创建一个Looper对象。Looper实现了一个简单的消息队列,一个接着一个处理Message对象。程序框架所有主要的事件(例如:屏幕上的点击时间,Activity生命周期的方法等等)都包含在Message对象中,然后添加到Looper的消息队列中,一个一个处理。主线程的Looper存在整个应用程序的生命周期内。
  2、当一个Handler对象在主线程中创建的时候,它会关联到Looper的 messagequeue 。Message添加到消息队列中的时候,Message会持有当前Handler引用,当Looper处理到当前消息的时候,会调用Handler.handleMessage(Message).
  3、在java中,非静态 的内部类会隐式的持有当前类的一个引用。static的类则没有。
  4、如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

三、使用Handler导致内存泄露的解决方法:

方法一:通过程序逻辑来进行保护。
  1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
    Handler.getLooper().quitSafely() 将线程安全退出
  2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:

 private static Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mText.setText("收到消息");
        }
    };

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

@InjectView(R.id.text)
    static TextView mText;

    private Handler mHandler = new MyHandler(this);

    private static class MyHandler extends Handler {
        WeakReference<Activity > mActivityReference;
        public MyHandler(Activity activity) {
            mActivityReference= new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            final Activity activity = mActivityReference.get();
            if (activity != null) {
                mText.setText("收到消息");
            }
        }
    }

四、一个内存泄漏的例子:

public class MainActivity extends Activity {

    private Handler mHandler = new Handler();
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.hello_text);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("Done");
            }
        }, 80000);
    }
}

这是一个基本的activity。在handler的post方法中我们加入了一个匿名的runnable,同时我将其执行延迟了整整80秒。我们运行这个程序,并且旋转几次手机,发现内存有好几个activity,并没有被GC掉。
这里写图片描述
$$从上图中我们可以看到其中一个对mainactivity的引用是来自this$0,this$0是什么呢?以下是关于this$0的解释:static的inner class里面都会有一个this$0的字段保存它的父对象。在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。

在我们的代码中,匿名的runnable是一个非静态的内部类,因此他会使用this$0来保存MainActivity,然后runnable会继续被它的callback引用,而callback又接着被接下来一连串的message引用,这样主线程的引用就太他妈多了。 当Activity finish后,延时消息会继续存在主线程消息队列中80秒,然后处理消息,因此handler继续存在于内存中,而handler引用了Activity,在我们旋转手机的时候,Activity不停的重建和finish,导致多个activity的引用出现。

一旦将Runnable或者是Message 发送进handler,将保存一连串的引用了主线程(这里是MainActivity吧)的Message命令,直到message执行完。如果发送Runnable设置了延迟时间,那么至少在这段延迟时间内内存泄漏是肯定的,如果是直接发送,在Message比较大的情况下,也是有可能发生暂时的泄漏的。

解决方法一:使用static

public class MainActivity extends Activity {

    private Handler mHandler = new Handler();
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.hello_text);
        mHandler.postDelayed(new DoneRunnable(mTextView), 80000);
    }
    private static final class DoneRunnable implements Runnable{
        private final TextView textView;
        public DoneRunnable(TextView textView){
            this.textView = textView;
        }
        @Override
        public void run() {
            textView.setText("Done");
        }
    }
}

这里写图片描述

在最底下我们发现activity继续被DoneRunnable里面mTextView中的mContext引用着。看来在这种情况下,看来仅仅使用static并没有解决问题啊。还需要做点工作才行。

既然是因为mTextView引起的,那我们把mTextView换成弱引用好了,需要注意的,既然mTextView是弱引用,所以随时都可能为null,因此需要在使用前判断是否为空。

public class MainActivity extends Activity {

    private Handler mHandler = new Handler();
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.hello_text);
        mHandler.postDelayed(new DoneRunnable(mTextView), 80000);
    }
    private static final class DoneRunnable implements Runnable{
        private final WeakReference<TextView> weakReference;
        public DoneRunnable(TextView textView){
            weakReference = new WeakReference<TextView>(textView);
        }
        @Override
        public void run() {
            TextView textView = weakReference.get();
            if (textView != null) {
                textView.setText("Done");
            }
        }
    }
}

总结上面的方法:
使用静态的内部类
对所有handler/Runnable中的变量都用弱引用。
但是这种方式代码很多,而且还必须得小心翼翼。

解决方法二:在onDestroy中清理掉所有Messages
Handler有个很方便的方法:removeCallbacksAndMessages,当参数为null的时候,可以清除掉所有跟次handler相关的Runnable和Message,我们在onDestroy中调用次方法也就不会发生内存泄漏了。

public class MainActivity extends Activity {

    private Handler mHandler = new Handler();
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.hello_text);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("Done");
            }
        }, 80000);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

解决方法三:WeakHandler

WeakHandler使用起来和handler一模一样,但是他是安全的,你只需要把以前的Handler替换成WeakHandler就行了WeakHandler使用如下:

public class MainActivity extends Activity {

    private WeakHandler mHandler = new WeakHandler();
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.hello_text);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("Done");
            }
        }, 80000);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

WeakHandler的实现原理:
WeakHandler的思想是将Handler和Runnable做一次封装,我们使用的是封装后的WeakHandler,但其实真正起到handler作用的是封装的内部,而封装的内部对handler和runnable都是用的弱引用。
>
WeakHandler的github地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值