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、数据回调匿名内部类泄漏

数据回调后匿名内部类泄漏.png

整个引用链分析,匿名内部类持有外部类引用,外部类通过持有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 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泄漏

LeakCanary检测结果.png
CustomSurfaceView类成员关系.PNG

SurfaceView$4.this$0

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(匿名内部类)导致的泄漏

Presenter$9匿名内部类导致的泄漏.png

SurfaceView$4就是SurfaceHolder。


CustomSurfaceView如何引用到了Activity.png

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

CustomSurfaceView继承自SurfaceView。

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

匿名内部类-9-rxjava-consumer.png

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


presenter匿名内部类$9.png

主要原因是其他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、集合对象没有及时清理引起的内存泄漏

参考
https://blog.csdn.net/doctor_who2004/article/details/102329237
常见android内存泄漏问题
https://www.cnblogs.com/andashu/p/6440944.html

你可能感兴趣的:(Android内存泄漏分析 LeakCanary)