> 文章列表 > android内存泄漏检测,Android内存泄露检测之LeakCanary的使用

android内存泄漏检测,Android内存泄露检测之LeakCanary的使用

android内存泄漏检测,Android内存泄露检测之LeakCanary的使用

为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary。使用LeakCanary,在内存泄漏后,通过分析引用链可以分析内存泄漏的原因,LeakCanary用于检测Activity、Fragment的内存泄漏。

下面通过一些实际案例来进行分析。

1、类编译后结构

静态成员类的每个实例都隐含着与外层类的一个外层类实例(属性名为this$0)。在非静态成员类实例方法内部,可以调用外层类的方法,或者利用修饰过的this构造获得外层类实例的引用。

当一个类文件编译之后有很多类名字中有$符, 比如Test.class, Test$1.class, Test$2.class, Test$MyTest.class
$后面跟数字的类就是匿名类编译出来的结果。Test$MyTest.class则是内部类MyTest编译后得到的。

在非静态内部类中,我们可以任意使用OuterClass.this来获取外部类实例。

package com.sdcuike.java.nestclass;public class OuterClass {private static class StaticInnerClass {}private class NoStaticInnerClass {}

编译后,生成的字节码文件:

Compiled from "OuterClass.java"
class com.sdcuike.java.nestclass.OuterClass$NoStaticInnerClass {final com.sdcuike.java.nestclass.OuterClass this$0;
}
Compiled from "OuterClass.java"
class com.sdcuike.java.nestclass.OuterClass$StaticInnerClass {
}

1、数据回调匿名内部类泄漏

整个引用链分析,匿名内部类持有外部类引用,外部类通过持有surfaceView间接引用了Activity。匿名内部类里处理数据回调,activity退出了,但是数据回调可能还没停止,从而最终导致Activity泄漏。

public class HXPlaybackTransModel {/ surfaceview句柄 */private SurfaceView devSurfaceView = null;
public void setParams(){//1、修改前代码ffmepgPlay.setHikTransDataCallback(new FFMpegAsyncPlayer.GA_SystemTransDataCallback() {@Overridepublic void onTransStreamDataCallback(int datatype, byte[] pdata, int datalen) {···}});//2 修改后代码,匿名颞部类改为静态内部类ffmepgPlay.setHikTransDataCallback(transDataCallback);}
//3 匿名内部类改为静态内部类private TransDataCallback transDataCallback = new TransDataCallback(this);private static class TransDataCallback implements FFMpegAsyncPlayer.GA_SystemTransDataCallback{private WeakReference<HXPlaybackTransModel> playModelRef;public TransDataCallback(HXPlaybackTransModel playmodel) {this.playModelRef = new WeakReference<>(playmodel);}@Overridepublic void onTransStreamDataCallback(int datatype, byte[] pdata, int datalen) {HXPlaybackTransModel playModel = playModelRef.get();if (playModel != null){//处理具体业务逻辑}}}
}

 修复前后如上所示。

2、SurfaceHolder泄漏

 

public class SurfaceView extends MockView {
//SurfaceHolder是SurfaceView的匿名内部类,所以SurfaceView$4即是指SurfaceHolder。而.this$0即是指其外部类,即SurfaceView。
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {@Overridepublic boolean isCreating() {return false;}@Overridepublic void addCallback(Callback callback) {}···
}
}

 解决方法

// 1、修改前
public class FFMpegAsyncPlayer {private SurfaceHolder surfaceHolder;
···public void play(String videoPath, Surface surface, SurfaceHolder sfHolder){this.surfaceHolder = sfHolder;if (ffmpegPlayer != 0){play(ffmpegPlayer,videoPath,surface);}}
}···
//2 、修改后
public class FFMpegAsyncPlayer {
···public void play(String videoPath){//1、当前方案已不需要surfaceHolder,调用play方法时,sfHolder可以不传。if (ffmpegPlayer != 0){play(ffmpegPlayer,videoPath,null);}}
}

FFMpegAsyncPlayer中会有耗时处理,及处理后的回调,所以会导致持有surfaceHolder,导致最终Activity无法回收。所以对于部分View可能比其所在Acitivity生存时间长的问题要引起注意。可以在子线程操作刷新的View如SurfaceView等几个特例。

实现方案调整,原有方案会持有surfaceHolder,并传给native层进行解码后视频画面渲染。新方案中渲染使用了另一个播放库,已不再需要传入surfaceHolder。所以解决方案,是调用这个方法的时候,不需传入surfaceHolder,FFMpegAsyncPlayer已不需持有其引用。

3、Rxjava Consumer(匿名内部类)导致的泄漏

SurfaceView$4就是SurfaceHolder。

 

每个view都有一个上下文Context,所以SurfaceView的mContext(在继承的View中)最终引用到了其所在的Activity。

CustomSurfaceView继承自SurfaceView。

这个CameraPlaybackCompatPresenter$9匿名内部类究竟在哪里。

点开LeakCanary,看到是一个Rxjava 的Consumer。
具体是哪个,通过debug打断点调试分析找到。

主要原因是其他Rxjava的观察者都通过CompositeDisposable,但dspPlayTimeRefresh并没有加入到其中,导致Activity destroy的时候没有取消其订阅。

CameraPlaybackCompatPresenter{/rxjava取消订阅*/private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
//1、clearMessage在Activity 销毁时调用public void clearMessage(){stopRefreshPlayOsdTime();      // 2、增加的解决方法,取消订阅mCompositeDisposable.clear();}private void stopRefreshPlayOsdTime(){if (dspPlayTimeRefresh != null) {dspPlayTimeRefresh.dispose();dspPlayTimeRefresh = null;}}}

 

总结

使用LeakCanary进行内存泄漏分析并不麻烦,将引用链分析清楚,内存泄漏原因自然很快查到。
主要排查思路
1、查看类引用依赖关系
2、引用解除可以在引用链上一个合适节点解除,解决方案并不唯一。

android常见内存泄漏原因:
1、Handler引起的内存泄漏。即使用Handler(非静态内部类)持有外部类(Activity)引用,消息处理不合适导致Activity泄漏。
2、单例模式引起的内存泄漏。例如单例持有Activity上下文导致泄漏。
3、非静态内部类创建静态实例引起的内存泄漏
4、非静态匿名内部类引起的内存泄漏
5、注册/反注册未成对使用引起的内存泄漏。广播接收、EventBus等
6、资源对象没有关闭引起的内存泄漏
7、集合对象没有及时清理引起的内存泄漏