美文网首页
Android内存泄漏分析 LeakCanary

Android内存泄漏分析 LeakCanary

作者: 神迹12 | 来源:发表于2020-04-17 23:42 被阅读0次

    为了能够简单迅速的发现内存泄漏,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<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泄漏

    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

          本文链接:https://www.haomeiwen.com/subject/gvvuvhtx.html