由内部类引起的内存泄露

转载注明出处:https://blog.youkuaiyun.com/skysukai

1、背景

前面有一篇博客记录了一次由单列模式引起的内存泄露,参考[https://blog.youkuaiyun.com/skysukai/article/details/85709354].在一次例行测试中,又发现了一处内存泄露。提笔记录一下。

2、场景复现

在文件管理器中,点击一个分类进入,比如:图片。长按图片删除该条目后退出APP,leakcanary捕捉到内存泄露。根据提示定位到相应代码:

    public class CustomMediaScannerConnectionClient implements MediaScannerConnection.MediaScannerConnectionClient {
        private MediaScannerConnection mConnection;

        public CustomMediaScannerConnectionClient() {
            mConnection = new MediaScannerConnection(BaseActivity.this, this);
        }

        public MediaScannerConnection getConnection() {
            return mConnection;
        }

        public void start() {
            mConnection.connect();
        }
        @Override
        public void onMediaScannerConnected() {
            if (QLog.isDebuggable()) { Log.d(LOG_TAG, "Connected to MediaScanner. Start scanning."); }
        }

        @Override
        public void onScanCompleted(String path, Uri uri) {
            if (QLog.isDebuggable()) { Log.d(LOG_TAG, "scan completed: " + path); }

            Message msg = new Message();
            msg.what = MSG_SCAN_COMPLETED;
            msg.obj = path;
            mHandler.sendMessageDelayed(msg, 100);
        }
    }

报错出在第五行mConnection = new MediaScannerConnection(BaseActivity.this, this);即建立CustomMediaScannerConnectionClient的时候context是用的activity,在以前的博客中提到过如果对象持有的context生命周期过短导致内存无法被回收,通常的改法就是延长对象的生命周期,第一次修改后的代码:

mConnection = new CustomMediaScannerConnectionClient(getApplicationContext(), this);

修改后编译运行,执行相同的操作,这次依然报了内存泄露。不过,这次报错的原因是内部类持有了外部类的引用导致内存泄露。CustomMediaScannerConnectionClient这个类位于activity中,不难理解,CustomMediaScannerConnectionClient默认持有了activity的引用,app退出activity被回收而CustomMediaScannerConnectionClient的对象无法被回收,导致内存泄露。既然是由内部类持有外部类的引用导致的内存泄露,那么改法就是把相应的内部类移动到外部,单独封装成一个类,这样就不会有内部类和外部类的引用关系。修改之后,再次编译运行代码,还是有内存泄露报出。
这次报错的原因是Handler持有了activity的引用导致。Handler在实例化的时候会默认持有activity的引用,一番搜索之后找到了通常这种情况下的做法:将Handler单独包装,取消它和activity之间的引用关系。给出完全代码:

public class CustomMediaScannerConnectionClient implements MediaScannerConnection.MediaScannerConnectionClient {
    private static final String LOG_TAG = "CustomMediaScannerConnectionClient";
    private static final int MSG_SCAN_COMPLETED = 1;
    private NoLeakHandler mHandler;
    private MediaScannerConnection mConnection;

    public CustomMediaScannerConnectionClient(Context context, BaseActivity activity) {
        mConnection = new MediaScannerConnection(context, this);
        mHandler = new NoLeakHandler(activity);
    }

    public MediaScannerConnection getConnection() {
        return mConnection;
    }

    public void start() {
        mConnection.connect();
    }
    @Override
    public void onMediaScannerConnected() {
        if (QLog.isDebuggable()) { Log.d(LOG_TAG, "Connected to MediaScanner. Start scanning."); }
    }

    @Override
    public void onScanCompleted(String path, Uri uri) {
        if (QLog.isDebuggable()) { Log.d(LOG_TAG, "scan completed: " + path); }

        if(mHandler != null) {
            Message msg = new Message();
            msg.what = MSG_SCAN_COMPLETED;
            msg.obj = path;
            mHandler.sendMessageDelayed(msg, 100);
        }
    }

    private static class NoLeakHandler extends Handler{
        private WeakReference<BaseActivity> mActivity;

        public NoLeakHandler(BaseActivity activity){
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            mActivity.get().getHandler().handleMessage(msg);
        }
    }
}

将Handler用WeakReference来修饰,取消Handler和activity之间的引用关系。再次编译运行,这次没有内存泄露被捕捉到。

3、后记

内存泄露的修改有时简单有时复杂,在leakcanary的帮助下,定位到内存泄露的代码已经简化了许多,只需按图索骥修改就好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值