前言
使用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的给出的引用链一个个的往下找,找到发生内存泄露的地方,切断引用链就可以释放内存了。
参考文章
- 《LeakCanary说明文档》
- 《Android内存泄露检测之LeakCanary的使用》
- 《性能优化工具(九)-LeakCanary》
- 《Handler内存泄漏之使用静态内部类并持有外部类的弱引用》
- 《使用LeakCanary分析并解决Android内存泄露》
历史上的今天
暂无评论...
随机推荐
Java的反射简介
前言本文主要参考其他作者的文章,然后自己整理一下,原文写得很仔细,但还得自己走一遍流程。感谢大佬分享。正文什么是Java的反射机制java允许开发者在程序运行过程中操作(访问和修改)类的各种属性以及方法。获取Class类对象java给我们提供了三种方式获取Class类对象。Sour...
JNI动态注册封装C++版
前言之前JNI一直用C语言写,但发现Android Framework中大都用C++写,为了阅读方便,改为C++。其实C++跟C语言写法一样的,只不过C++更简洁些。正文修改点,举个例子不同点hello.c文件后缀改为hello.cpp,还有就是C++传入的参数更少,看起来更简洁。he...
Can not perform this action after onSaveInstanceState
java.lang.IllegalStateException异常 Line 151151: 06-14 19:15:46.601 1804 1804 E Media: java.lang.IllegalStateException: Can not perform this action ...
聂鲁达:雨
不,女王最好也不要认出你的面孔,这更甜美这方式,我的爱,远比偶像更甜美,你的头发的重量在我手中,你还记得吗?芒果树的花朵落在你的发间?这些手指不像洁白的花瓣:看看它们,它们像根,它们像石头击中正滑动的蝎子。别害怕,我们正在等待雨的降临,赤裸着,雨,正同样地降临在马努塔拉山上。就...
常见的文件头或文件尾十六进制表示
前言最近在加载图片时,由于需要对不同图片使用不同的加载方式,因此需要通过判断图片的类型进行条用不同的接口。因此摘抄于此,以便查阅。正文下面的文件头或文件尾都是用十六进制表示的。JPEG (jpg)文件头:FFD8FF文件尾:FFD9PNG (png)文件头:89504E47文件尾...
Android Studio工程中.idea没有*.iml文件
前言我使用其他同事电脑时,Android Studio(Android Studio Dolphin | 2021.3.1 Patch 1)的版本新建的工程中.idea目录没有对应module和*.iml。百度或谷歌后解决了。记录一下,方便自己查阅。正文解决这个问题很简单,就是Androi...