为了能够简单迅速的发现内存泄漏,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() {
@Override
public 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);
}
@Override
public 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() {
@Override
public boolean isCreating() {
return false;
}
@Override
public 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、集合对象没有及时清理引起的内存泄漏