静态内部类、Handlers涉及的内存泄露

引例

先看一段代码:

public class SampleActivity extends Activity {
    private final Handler mLeakHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}

这段代码很可能导致内存泄露,Android Lint 会给出下列的警告:

This Handler class should be static or leaks might occur (anonymous android.os.Handler)
但是,哪里会导致内存泄露呢?内存泄漏又是怎么发生的呢?

分析

首先我们先说几点常识:
1. Android的Looper和Handler的一个过程:当Android application 启动之后,framework会创建一个Looper对象,接着Looper实现一个简单的消息队列,在一个消息循环中处理Message对象。主线程Looper存在于整个App的生命周期。
2. 在主线程中实例化Handler之后,它关联了Looper的消息队列,消息队列里面的Message对象会持有Handler的引用。这样是为了Hander能调用handler的handleMessage()方法去处理这个Message对象
3. 非静态的内部类和匿名类会隐式地持有一个他们外部类的引用。静态内部类则不会。
到底哪里发生了内存泄露?我们来看下列代码:

public class SampleActivity extends Activity {
    private final Handler mLeakHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLeakHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 1000 * 60 * 10);
        finish();
    }
}

注意:代码中的

new Handler() {
 4     @Override
 5     public void handleMessage(Message msg) {
 6       // ...
 7     }
 8   }

new Runnable() {
16       @Override
17       public void run() { }
18     }

都是匿名内部类。
这个Acitivity中,存在匿名内部类,当Activity被销毁之后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个消息持有这个Activity的Handler引用,这个Handler有隐式地持有他的外部类(在这个例子中是SampleActivity)。直到消息被处理前,这个引用都不会被释放。因此Activity不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,匿名Runnable类也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此context将被泄露。

静态内部类解决内存泄露

为了解决这个问题,Handler的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的Activity泄露。如果你需要在Handle内部调用外部Activity的方法,那么让Handler持有一个Activity的弱引用(WeakReference)以便你不会意外导致context泄露。为了解决我们实例化匿名Runnable类可能导致的内存泄露,我们将用一个静态变量来引用他(因为匿名类的静态实例不会隐式持有他们外部类的引用)。

public class SampleActivity extends Activity {

//匿名类的静态实例不会隐式持有他们外部类的引用
    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() {

        }
    };

    private final MyHandler mHandler = new MyHandler(this);

    /**
     * 静态内部类的实例不会隐式持有他们外部类的引用。
     */
    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;

        public MyHandler(SampleActivity mActivity) {
            this.mActivity = new WeakReference<SampleActivity>(mActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            SampleActivity activity = mActivity.get();
            if (activity != null) {

            }
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 1000 * 60 * 10);
        finish();
    }

不在一个Activity中使用非静态内部类, 以防它的生命周期比Activity长。相反,尽量使用持有Activity弱引用的静态内部类。

阅读更多

How to Leak a Context: Handlers & Inner Classes
android non-static内部类导致的内存泄露
【译】什么导致了Context泄露:Handler&内部类
非静态内部类可能导致的内存泄漏及其优化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值