转载注明出处: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的帮助下,定位到内存泄露的代码已经简化了许多,只需按图索骥修改就好。