Android內(nèi)存泄漏分析 LeakCanary

為了能夠簡單迅速的發(fā)現(xiàn)內(nèi)存泄漏,Square公司基于MAT開源了LeakCanary。使用LeakCanary,在內(nèi)存泄漏后,通過分析引用鏈可以分析內(nèi)存泄漏的原因,LeakCanary用于檢測Activity、Fragment的內(nèi)存泄漏。

下面通過一些實際案例來進(jìn)行分析。

1、類編譯后結(jié)構(gòu)

非靜態(tài)成員類的每個實例都隱含著與外層類的一個外層類實例(屬性名為this$0)。在非靜態(tài)成員類實例方法內(nèi)部,可以調(diào)用外層類的方法,或者利用修飾過的this構(gòu)造獲得外層類實例的引用。

當(dāng)一個類文件編譯之后有很多類名字中有$符, 比如Test.class, Test$1.class, Test$2.class, Test$MyTest.class
$后面跟數(shù)字的類就是匿名類編譯出來的結(jié)果。Test$MyTest.class則是內(nèi)部類MyTest編譯后得到的。

在非靜態(tài)內(nèi)部類中,我們可以任意使用OuterClass.this來獲取外部類實例。


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

編譯后,生成的字節(jié)碼文件:

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、數(shù)據(jù)回調(diào)匿名內(nèi)部類泄漏

數(shù)據(jù)回調(diào)后匿名內(nèi)部類泄漏.png

整個引用鏈分析,匿名內(nèi)部類持有外部類引用,外部類通過持有surfaceView間接引用了Activity。匿名內(nèi)部類里處理數(shù)據(jù)回調(diào),activity退出了,但是數(shù)據(jù)回調(diào)可能還沒停止,從而最終導(dǎo)致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 修改后代碼,匿名顳部類改為靜態(tài)內(nèi)部類
            ffmepgPlay.setHikTransDataCallback(transDataCallback);
          }
//3 匿名內(nèi)部類改為靜態(tài)內(nèi)部類
    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){
                //處理具體業(yè)務(wù)邏輯
            }
        }
    }
}

修復(fù)前后如上所示。

2、SurfaceHolder泄漏

LeakCanary檢測結(jié)果.png
CustomSurfaceView類成員關(guān)系.PNG

SurfaceView$4.this$0

public class SurfaceView extends MockView {
//SurfaceHolder是SurfaceView的匿名內(nèi)部類,所以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、當(dāng)前方案已不需要surfaceHolder,調(diào)用play方法時,sfHolder可以不傳。
        if (ffmpegPlayer != 0){
            play(ffmpegPlayer,videoPath,null);
        }
    }
}

FFMpegAsyncPlayer中會有耗時處理,及處理后的回調(diào),所以會導(dǎo)致持有surfaceHolder,導(dǎo)致最終Activity無法回收。所以對于部分View可能比其所在Acitivity生存時間長的問題要引起注意。可以在子線程操作刷新的View如SurfaceView等幾個特例。

實現(xiàn)方案調(diào)整,原有方案會持有surfaceHolder,并傳給native層進(jìn)行解碼后視頻畫面渲染。新方案中渲染使用了另一個播放庫,已不再需要傳入surfaceHolder。所以解決方案,是調(diào)用這個方法的時候,不需傳入surfaceHolder,F(xiàn)FMpegAsyncPlayer已不需持有其引用。

3、Rxjava Consumer(匿名內(nèi)部類)導(dǎo)致的泄漏

Presenter$9匿名內(nèi)部類導(dǎo)致的泄漏.png

SurfaceView$4就是SurfaceHolder。


CustomSurfaceView如何引用到了Activity.png

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

CustomSurfaceView繼承自SurfaceView。

這個CameraPlaybackCompatPresenter$9匿名內(nèi)部類究竟在哪里。

匿名內(nèi)部類-9-rxjava-consumer.png

點開LeakCanary,看到是一個Rxjava 的Consumer。
具體是哪個,通過debug打斷點調(diào)試分析找到。


presenter匿名內(nèi)部類$9.png

主要原因是其他Rxjava的觀察者都通過CompositeDisposable,但dspPlayTimeRefresh并沒有加入到其中,導(dǎo)致Activity destroy的時候沒有取消其訂閱。

CameraPlaybackCompatPresenter{
   /**rxjava取消訂閱*/
   private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
//1、clearMessage在Activity 銷毀時調(diào)用
   public void clearMessage(){
       stopRefreshPlayOsdTime();      // 2、增加的解決方法,取消訂閱
       mCompositeDisposable.clear();
   }

   private void stopRefreshPlayOsdTime(){
       if (dspPlayTimeRefresh != null) {
           dspPlayTimeRefresh.dispose();
           dspPlayTimeRefresh = null;
       }
   }

}

總結(jié)

使用LeakCanary進(jìn)行內(nèi)存泄漏分析并不麻煩,將引用鏈分析清楚,內(nèi)存泄漏原因自然很快查到。
主要排查思路
1、查看類引用依賴關(guān)系
2、引用解除可以在引用鏈上一個合適節(jié)點解除,解決方案并不唯一。

android常見內(nèi)存泄漏原因:
1、Handler引起的內(nèi)存泄漏。即使用Handler(非靜態(tài)內(nèi)部類)持有外部類(Activity)引用,消息處理不合適導(dǎo)致Activity泄漏。
2、單例模式引起的內(nèi)存泄漏。例如單例持有Activity上下文導(dǎo)致泄漏。
3、非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例引起的內(nèi)存泄漏
4、非靜態(tài)匿名內(nèi)部類引起的內(nèi)存泄漏
5、注冊/反注冊未成對使用引起的內(nèi)存泄漏。廣播接收、EventBus等
6、資源對象沒有關(guān)閉引起的內(nèi)存泄漏
7、集合對象沒有及時清理引起的內(nèi)存泄漏

參考
https://blog.csdn.net/doctor_who2004/article/details/102329237
常見android內(nèi)存泄漏問題
https://www.cnblogs.com/andashu/p/6440944.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容