为了能够简单迅速的发现内存泄漏,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、集合对象没有及时清理引起的内存泄漏



















