leaks Android内存泄露,Android内存泄露检测LeakCanary的使用

本文介绍了LeakCanary的使用方法,通过在Android项目中集成LeakCanary,实现实时检测内存泄露。文章以一个Demo项目为例,展示了LeakCanary如何捕获并分析内存泄露,特别是如何通过分析泄漏路径找到问题源头,例如发现由于静态Toast对象引起的内存泄露问题,并提供了修复建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言:

官网地址:https://square.github.io/leakcanary/getting_started/

目前为止最新的版本是2.6版本,相比于2.0之前的版本,2.0之后的版本在使用上简洁了很多,只需要在dependencies中加入LeakCanary的依赖即可。而且debugImplementation只在debug模式下有效,所以不用担心用户在正式环境下也会出现LeakCanary收集。

dependencies {

// debugImplementation because LeakCanary should only run in debug builds.

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'

}

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

项目运行起来之后,在控制台可以看到LeakCanary的打印信息:

D/LeakCanary: Check for retained object found no objects remaining

D/LeakCanary: Scheduling check for retained objects in 5000ms because app became invisible

D/LeakCanary: Check for retained object found no objects remaining

D/LeakCanary: Rescheduling check for retained objects in 2000ms because found only 3 retained obje

2、Android手机会有一个Leaks小鸟的APP

a04c85385493

如图.png

打开生成的Leaks应用,界面就类似下面这样婶儿滴。LeakCanary会计算一个泄漏路径并在UI上展示出来。这就是LeakCanary很友好的地方,通过UI展示,可以很直接的看到内存泄漏的过程。相对于mat和android studio 自带的profiler分析工具,这个简直太直观清晰了!

a04c85385493

图片.png

同时泄漏路径也在logcat中展示了出来:

HEAP ANALYSIS RESULT

====================================

1 APPLICATION LEAKS

References underlined with "~~~" are likely causes.

Learn more at https://squ.re/leaks.

111729 bytes retained by leaking objects

Signature: e030ebe81011d69c7a43074e799951b65ea73a

┬───

│ GC Root: Local variable in native code

├─ android.os.HandlerThread instance

│ Leaking: NO (PathClassLoader↓ is not leaking)

│ Thread name: 'LeakCanary-Heap-Dump'

│ ↓ HandlerThread.contextClassLoader

├─ dalvik.system.PathClassLoader instance

│ Leaking: NO (ToastUtil↓ is not leaking and A ClassLoader is never leaking)

│ ↓ PathClassLoader.runtimeInternalObjects

├─ java.lang.Object[] array

│ Leaking: NO (ToastUtil↓ is not leaking)

│ ↓ Object[].[871]

├─ com.example.leakcaneraytestapplication.ToastUtil class

│ Leaking: NO (a class is never leaking)

│ ↓ static ToastUtil.mToast

│ ~~~~~~

├─ android.widget.Toast instance

│ Leaking: YES (This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null))

│ ↓ Toast.mContext

╰→ com.example.leakcaneraytestapplication.LeakActivity instance

Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

key = c1de58ad-30d8-444c-8a40-16a3813f3593

watchDurationMillis = 40541

retainedDurationMillis = 35535

====================================

0 LIBRARY LEAKS

路径中的每一个节点都对应着一个java对象。熟悉java内存回收机制的同学都应该知道”可达性分析算法“,LeakCanary就是用可达性分析算法,从GC ROOTS向下搜索,一直去找引用链,如果某一个对象跟GC Roots没有任何引用链相连时,就证明对象是”不可达“的,可以被回收。

我们从上往下看:

GC Root: Local variable in native code

在泄漏路径的顶部是GC Root。GC Root是一些总是可达的特殊对象。

接着是:

├─ android.os.HandlerThread instance

│ Leaking: NO (PathClassLoader↓ is not leaking)

│ Thread name: 'LeakCanary-Heap-Dump'

│ ↓ HandlerThread.contextClassLoader

这里先看一下Leaking的状态(YES、NO、UNKNOWN),NO表示没泄露。那我们还得接着向下看。

├─ dalvik.system.PathClassLoader instance

│ Leaking: NO (ToastUtil↓ is not leaking and A ClassLoader is never leaking)

│ ↓ PathClassLoader.runtimeInternalObjects

上面的节点告诉我们Leaking的状态还是NO,那再往下看。

├─ android.widget.Toast instance

│ Leaking: YES (This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null))

│ ↓ Toast.mContext

中间Leaking是NO状态的我就不再贴出来,我们看看Leaking是YES的这一条,这里说明发生了内存泄露。

”This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null)“,这里说明Toast发生了泄露,android.widget.Toast 这是系统的Toast控件,这说明我们在使用Toast的过程中极有可能创建了Toast对象,但是该回收它的时候无法回收它,导致出现了内存泄露,这里我们再往下看:

╰→ com.example.leakcaneraytestapplication.LeakActivity instance

Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

这里就很明显的指出了内存泄露是发生在了那个activity里面,我们根据上面的提示,找到对应的activity,然后发现了一段跟Toast有关的代码:

image.png

这里再进入ToastUtil这个自定义Toast类里面,看看下面的代码,有没有发现什么问题?这里定义了一个static的Toast对象类型,然后在showToast的时候创建了对象,之后就没有然后了。我们要知道static的生命周期是存在于整个应用期间的,而一般Toast对象只需要显示那么几秒钟就可以了,因为这里创建一个静态的Toast,用完之后又没有销毁掉,所以这里提示有内存泄露了。因此我们这里要么不用static修饰,要么在用完之后把Toast置为null。

public class ToastUtil {

private static Toast mToast;

public static void showToast(Context context, int resId) {

String text = context.getString(resId);

showToast(context, text);

}

public static void showToast(Context context, String text){

showToast(context, text, Gravity.BOTTOM);

}

public static void showToastCenter(Context context, String text){

showToast(context, text, Gravity.CENTER);

}

public static void showToast(Context context, String text, int gravity){

cancelToast();

if (context != null){

LayoutInflater inflater = (LayoutInflater) context

.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View layout = inflater.inflate(R.layout.toast_layout, null);

((TextView) layout.findViewById(R.id.tv_toast_text)).setText(text);

mToast = new Toast(context);

mToast.setView(layout);

mToast.setGravity(gravity, 0, 20);

mToast.setDuration(Toast.LENGTH_LONG);

mToast.show();

}

}

public static void cancelToast() {

if (mToast != null){

mToast.cancel();

}

}

}

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

这里再补充一点上面的这个例子里面Leaking没有UNKNOWN的状态,一般情况下除了YES、NO还会出现UNKNOWN的状态,UNKNOWN表示这里可能出现了内存泄露,这些引用你需要花时间来调查一下,看看是哪里出了问题。一般推断内存泄露是从最后一个没有泄漏的节点(Leaking: NO )到第一个泄漏的节点(Leaking: YES)之间的引用。

### 如何在 Android Studio 中用 LeakCanary 检测和修复内存泄漏问题 #### 安装与配置 LeakCanary 要在项目中集成 LeakCanary 工具,首先需要将其依赖项添加到项目的 `build.gradle` 文件中。以下是具体的实现方式: ```gradle dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.10' } ``` 完成上述配置后,在应用启动时会自动初始化 LeakCanary,并可以通过日志确认其是否正常运行。如果一切设置无误,则会在控制台打印如下消息[^3]: ``` D/LeakCanary: LeakCanary is running and ready to detect leaks. ``` #### 使用场景说明 当应用程序存在潜在的内存泄漏风险时,LeakCanary 将自动生成报告并提供详细的上下文信息。这些信息通常包括泄漏对象的具体路径、引用链条以及涉及的相关类名等。 例如,在某些情况下可能会因为静态变量持有 Context 导致 Activity 泄漏。假设某 Activity 存在一个全局静态成员变量保存了自身的实例 (`sContext`),那么即使该 Activity 已经销毁,但由于被其他地方强引用而无法回收,最终形成泄漏现象[^5]。 一旦触发此类情况,LeakCanary 便会捕捉到这一行为并向开发者发出警告通知。同时还会附带一张图表展示完整的引用链路以便快速定位问题所在位置[^4]。 #### 解决方案建议 针对发现出来的具体问题点采取相应措施加以改进是非常重要的一步。下面列举了几种常见类型的解决方案及其对应的操作指南: - **优化Bitmap管理**: 如果是因为大尺寸位图占用过多内存而导致崩溃的话,可以考虑调整采样率或者切换至更高效的第三方框架来进行异步加载处理[^2]。 - **清理不必要的监听器注册**: 对于各种事件绑定机制而言(比如广播接收者),务必记得在其生命周期结束前提前解除关联以防长期滞留堆栈之中难以释放资源。 - **合理运用弱引用技术**: 当确实需要用到跨组件间传递数据又不想影响各自独立性的场合下,推荐尝试引入 WeakReference 来代替传统的直接赋值形式从而降低耦合度提升灵活性的同时减少意外状况发生的概率。 最后提醒一点的是除了依靠外部插件辅助排查之外平时也要养成良好的编码习惯时刻警惕可能出现的风险源做到未雨绸缪才是根本之道! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值