Android内存泄露检测之LeakCanary的简单使用

Android  Tool  2020年7月13日 pm6:03发布4年前 (2020)更新 城堡大人
113 0 0

前言

使用MAT来分析内存问题,有一些门槛,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比才能找到问题原因。

为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary

PS:有不同版本的leakcanary出现不同的问题,因此20210422日重新更新,并验证

使用

在app build.gradle 中加入引用:

Android 9.0 上使用

dependencies {
    //leakcanary 2.3
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
}

Android 4.2 上

dependencies {
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}

然后在自定义application申明

public class TestMemApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.addLogAdapter(new AndroidLogAdapter());
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
        // Normal app init code...
    }
}

测试demo分析

在项目中加入LeakCanary之后就可以开始检测项目的内存泄露了,把项目运行起来之后, 开始随便点自己的项目,下面以一个Demo项目为例,来聊一下LeakCanary记录内存泄露的过程以及我如何解决内存泄露的。

使用Handler持有外部类引用来说明,代码片段如下:

public class MainActivity extends AppCompatActivity {
    private final int MSG_DELAY_GO = 0x1000;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //发送延迟处理消息
        mHandler.sendEmptyMessageDelayed(MSG_DELAY_GO, 60 * 1000 * 5);
    }

    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case MSG_DELAY_GO:

                    break;
            }
        }
    };
}

运行起来后。过滤日志LeakCanary日志TAG

D/LeakCanary( 3323): LeakCanary is running and ready to detect leaks

按Back键退出后(部分日志),需要等一段时间才有完整的日志显示哈。

07-08 19:31:40.013 D/LeakCanary( 3323): 107241 bytes retained by leaking objects
07-08 19:31:40.013 D/LeakCanary( 3323): Signature: 4f5bc1c21df027da9d327d87c73072dfb27e734c
07-08 19:31:40.013 D/LeakCanary( 3323): ┬───
07-08 19:31:40.013 D/LeakCanary( 3323): │ GC Root: Input or output parameters in native code
07-08 19:31:40.013 D/LeakCanary( 3323): │
07-08 19:31:40.013 D/LeakCanary( 3323): ├─ android.os.MessageQueue instance
07-08 19:31:40.013 D/LeakCanary( 3323): │    Leaking: NO (MessageQueue#mQuitting is false)
07-08 19:31:40.013 D/LeakCanary( 3323): │    ↓ MessageQueue.mMessages
07-08 19:31:40.013 D/LeakCanary( 3323): │                   ~~~~~~~~~
07-08 19:31:40.013 D/LeakCanary( 3323): ├─ android.os.Message instance
07-08 19:31:40.013 D/LeakCanary( 3323): │    Leaking: UNKNOWN
07-08 19:31:40.013 D/LeakCanary( 3323): │    ↓ Message.target
07-08 19:31:40.013 D/LeakCanary( 3323): │              ~~~~~~
07-08 19:31:40.013 D/LeakCanary( 3323): ├─ com.la.testleakcanary.MainActivity$1 instance
07-08 19:31:40.013 D/LeakCanary( 3323): │    Leaking: UNKNOWN
07-08 19:31:40.013 D/LeakCanary( 3323): │    Anonymous subclass of android.os.Handler
07-08 19:31:40.013 D/LeakCanary( 3323): │    ↓ MainActivity$1.this$0
07-08 19:31:40.013 D/LeakCanary( 3323): │                     ~~~~~~
07-08 19:31:40.013 D/LeakCanary( 3323): ╰→ com.la.testleakcanary.MainActivity instance
07-08 19:31:40.013 D/LeakCanary( 3323): ​     Leaking: YES (ObjectWatcher was watching this because com.la.testleakcanary.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
07-08 19:31:40.013 D/LeakCanary( 3323): ​     key = 13f228b5-c5cf-4279-8513-9fac9bc8b9c4
07-08 19:31:40.013 D/LeakCanary( 3323): ​     watchDurationMillis = 5258
07-08 19:31:40.013 D/LeakCanary( 3323): ​     retainedDurationMillis = 223
07-08 19:31:40.013 D/LeakCanary( 3323):
07-08 19:31:40.013 D/LeakCanary( 3323): ====================================
07-08 19:31:40.013 D/LeakCanary( 3323): METADATA
07-08 19:31:40.013 D/LeakCanary( 3323):
07-08 19:31:40.013 D/LeakCanary( 3323): Please include this in bug reports and Stack Overflow questions.
07-08 19:31:40.013 D/LeakCanary( 3323):
07-08 19:31:40.013 D/LeakCanary( 3323): Build.VERSION.SDK_INT: 28
07-08 19:31:40.013 D/LeakCanary( 3323): Build.MANUFACTURER: alps
07-08 19:31:40.013 D/LeakCanary( 3323): LeakCanary version: 2.3
07-08 19:31:40.013 D/LeakCanary( 3323): App process name: com.la.testleakcanary
07-08 19:31:40.013 D/LeakCanary( 3323): Analysis duration: 22673 ms
07-08 19:31:40.013 D/LeakCanary( 3323): Heap dump file path: /data/user/0/com.la.testleakcanary/files/leakcanary/2020-07-08_19-31-13_439.hprof
07-08 19:31:40.013 D/LeakCanary( 3323): Heap dump timestamp: 1594207899988
07-08 19:31:40.013 D/LeakCanary( 3323): ====================================

从上面打印看出,存在内存泄漏。

同时也生成了/data/user/0/com.la.testleakcanary/files/leakcanary/2020-07-08_19-31-13_439.hprof 。这个文件可以用MAT工具在分析一波。

上面是日志打印的。

如果你的应用有SystemUI,那状态栏上也会有提示相关的信息。(具体我就不写了,可以看参考文章2,这里写得很详细)

解决内存泄漏

对于上面的有两种

onDestory时移除所有的回调和消息
@Override
protected void onDestroy() {
    super.onDestroy();
    //退出时移除所有的回调和消息
    if (null != mHandler) {
        mHandler.removeCallbacksAndMessages(null);
    }
}

使用弱引用

    private static class InnerHandler extends Handler {

        WeakReference<MainActivity> weakReference;

        public InnerHandler(MainActivity activity) {
            weakReference = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_DELAY_GO:
                    break;
            }
        }
    }

具体使用,可以访问《Handler内存泄漏之使用静态内部类并持有外部类的弱引用

小结

其实内存泄露的本质是长周期对象持有了短周期对象的引用,导致短周期对象该被回收的时候无法被回收,从而导致内存泄露。我们只要顺着LeakCaneray的给出的引用链一个个的往下找,找到发生内存泄露的地方,切断引用链就可以释放内存了。

参考文章

  1. LeakCanary说明文档
  2. Android内存泄露检测之LeakCanary的使用
  3. 性能优化工具(九)-LeakCanary
  4. Handler内存泄漏之使用静态内部类并持有外部类的弱引用
  5. 使用LeakCanary分析并解决Android内存泄露

 历史上的今天

  1. 2023: Android初始化第三方app权限(0条评论)
  2. 2021: 付志勇:为你写诗(0条评论)
  3. 2019: 龙应台:一个人怎样才算是有文化?(0条评论)
版权声明 1、 本站名称: 笔友城堡
2、 本站网址: https://www.biumall.com/
3、 本站部分文章来源于网络,仅供学习与参考,如有侵权,请留言

暂无评论

暂无评论...

随机推荐

[代码片段]Java获取某目录下文件总大小

前言代码功能:获取某目录下文件总大小项目中不仅需要判断磁盘大小,还需要限制拷贝目录大小。正文思路:递归目录下的所有文件,累加文件大小。当然,这种递归方式不是很好,因为存在目录层级复杂和文件多,导致耗时。 /** * * @param file * @...

Android P之Launcher启动

前言记录下一下Launcher是啥时候启动,至于启动中所有步骤这里不细写。记录于此,主要是方便自己的。PS : 分析源码 Android 9.0(P)正文正常情况下,Launcher是Android中第一个启动的应用。Launcher是Android系统的桌面,也是提供进入其他应用的...

Android中ImageView半边圆角处理记录2

前言之前介绍过对ImageView进行圆角处理,具体文章《Android中ImageView半边圆角处理记录 -笔友城堡 - 阅读是一种生活方式 ()》,后面发现网上还有一种更简单的一种方式。有点:代码少,简单缺点:边界不够圆滑(存在锯齿)正文具体效果如下(左侧是RoundImage...

纪伯伦:你的孩子其实不是你的孩子

你的孩子,其实不是你的孩子,他们是生命对于自身渴望而诞生的孩子。 他们通过你来到这世界,却非因你而来,他们在你身边,却并不属于你。 你可以给予他们的是你的爱,却不是你的想法,因为他们自己有自己的思想。 你可以庇护的是他们的身体,却不是他们的灵魂,...

史铁生:合欢树

10岁那年,我在一次作文比赛中得了第一。母亲那时候还年轻,急着跟我说她自己,说她小时候的作文作得比我还要好,老师甚至不相信那么好的文章会是她写的。“老师找到家来问,是不是家里的大人帮了忙。我那时可能还不到10岁呢。”我听得扫兴,故意笑:“可能?什么叫‘可能还不到’?”她就解释,我装作根本不在意她的话...

叔本华:人生两大苦

一般人把身外之物当作人生的幸福来源,希望从财产、社会地位、妻室儿女、朋友或是社会那里得到幸福,所以当他失去这些东西或是发现这些令他失望的时候,他的幸福基础就崩塌了。换句话说,这个人的人生重心随着每一次心血来潮而不停改变,完全不在他自身。倘若他是一个富有的人,就可能是今天在乡下别墅消磨时光,明天在买马...