使用LeakCanary快速定位解决Android内存泄漏

2024-05-07 05:32

本文主要是介绍使用LeakCanary快速定位解决Android内存泄漏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

这里提供一种快速定位解决Android内存泄漏的方案,希望大家看完有所收获。

1.奠基之石——内存泄漏概述

在介绍内存泄漏之前很有必要提及一下Android系统的垃圾回收机制。Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对虚拟机中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证虚拟机中的内存空间,防止出现内存泄露和溢出问题。Android系统的垃圾回收是基于可达性分析算法(根搜索算法)的。如下图所示,从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。

不可达的对象(如下图中的object5,6,7)会在系统GC的时候被回收,从而释放内存空间。

gc_root_object.png

如果所有的对象都可以被顺利回收就没有本文的诞生了,举个简单的例子,我们在开发中经常使用单例模式,单例的静态特性导致其生命周期同应用一样长。有时创建单例时如果我们需要Context对象,如果传入的是Application的Context那么不会有问题。如果传入的是Activity的Context对象,那么当Activity生命周期结束时,该Activity的引用依然被单例持有,所以不会被回收,而单例的生命周期又是跟应用一样长,这个情况就叫做内存泄露(Memory Leak)。它指的是当你不再需要某个实例后,但是这个对象却仍然被引用,防止被垃圾回收(Prevent from being bargage collected)。

public class Util {private Context mContext;private static Util sInstance;private Util(Context context) {thi s.mContext = context;}public static Util getInstance(Context context) {if (sInstance == null) {sInstance = new Util(context);}return sInstance;}
}

本杰明 富兰克林曾说:A small leak will sink a great ship.意即:小漏不补沉大船。基于Android系统的设备一般来说内存就不大,特别是早期的Android设备,内存泄漏是很致命的,内存泄漏积攒到一定程度会引发内存溢出(OOM),如果处理不当直接导致程序崩溃退出。

2.了然于胸——常见的内存泄漏

一般来说在开发中我们经常会犯下下面几个错误,导致内存泄漏。这几个都是前人踩坑总结出来的,非常有参考价值,至少我在排查解决内存泄漏的时候是这样的。
一、单例造成的内存泄漏
Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。例子见上面那段代码。
二、非静态内部类创建静态实例造成的内存泄漏
有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。例子如下

public class MainActivity extends AppCompatActivity {private static TestResource mResource = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (mManager == null) {mManager = new TestResource();} //... }class TestResource {//... }}

三、Handler造成的内存泄漏
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,我们经常在Activity里面这样定义一个私有的Handler对象并初始化,这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。

private Handler mHandler = new Handler() {@Override public void handleMessage(Message msg) {      //... }
};

四、资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

3. 神兵利器——检测内存泄漏的常见工具

见到这个标题有经验的开发者可能要吐槽我是标题党了,特别是从Eclipse时代走过来的开发者,以为我一要开始贴那张像一样的MAT内存模型图或者AndroidStudio中Monitors下的实时内存占用图,又要开始分析那一条条剪不断理还乱的内存引用链,然后费尽九牛二虎之力去查找项目中无数的内存泄漏中的一个。但是,我要告诉你,你错了。其实,以前我看到内存泄漏分析文章的时候也是这样的想法,看着恐怖的MAT内存模型图,觉得内存泄漏的排查和解决简直是Android开发中登峰造极的技能。直到我遇到了她——LeakCanary,我才直到原来内存泄漏的排查和解决可以那么的优雅。LeakCanary**是Square开源了一个内存泄露自动探测神器 。这是项目的github仓库地址:https://github.com/square/leakcanary 。使用非常简单,在build.gradle中引入包依赖:

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

在Application中的onCreate方法中增加初始化代码:

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);

集成后什么都不用做,按照正常测试,当有内存泄漏发生后,应用会通过系统通知栏发出通知,点击通知就可以进入查看内存泄漏的具体信息。在这里举个实践中的例子。把LeakCanary集成到项目中后,等App启动后一会,系统通知到了,点击通知,跳转到泄漏的详情页面进行查看:

很明显,WebSiteQueryActivity泄露了。首先,static 的MaskHeadView.fLayout变量引用了FrameLayout.mContext对象,这个对象的引用就是指向了WebSiteQueryActivity的实例,导致了它的泄漏,在第二节中我们说过static对象是内部的static对象是比较容易造成内存泄漏的,检查代码发现,MaskHeadView直接在WebSiteQueryActivity的xml文件中使用了,因此持有WebSiteQueryActivity的实例,因为fLayout对象是静态的,因此它的生命周期与Application同样长,因此WebSiteQueryActivity退出后,它的实例引用依然被fLayout持有,导致它无法被回收从而内存泄露了。仔细检查代码,发现fLayout并没有被外部使用到,应该是之前的开发者手抖加了个static字段上去或者是现在不用了,但是没有去掉,在这里我直接去掉了这个修饰符,在此build代码,这个内存泄漏的现象消失了。

//去掉static修饰符,避免static对象引起的内存泄漏

private static FrameLayout fLayout;
public MaskHeadView(Context context, AttributeSet attrs){super(context, attrs);this.context=context; initView(context);
}
private void initView(Context context2) {
view = LayoutInflater.from(context).inflate(R.layout.mask_head_view, this);fLayout=(FrameLayout) view.findViewById(R.id.mask_container);
}

这只是个极简单的例子,但方法是一样的。顺便提一句,其实无论是MAT工具的内存分析,还是AndroidStudio中自带的分析工具亦或是LeakCanary,原理都是一样的,都是dump java heap出来进行分析,找到泄漏的问题,只是LeakCanary帮我们把分析的工作做了。
曾几何时,你以为内存泄漏分析都是这样的

但是现在你会发现其实也可以是酱紫的:



作者:山野纸鹤
链接:https://www.jianshu.com/p/7cd328fbfd68
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这篇关于使用LeakCanary快速定位解决Android内存泄漏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/966454

相关文章

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

如何解决mmcv无法安装或安装之后报错问题

《如何解决mmcv无法安装或安装之后报错问题》:本文主要介绍如何解决mmcv无法安装或安装之后报错问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mmcv无法安装或安装之后报错问题1.当我们运行YOwww.chinasem.cnLO时遇到2.找到下图所示这里3.

浅谈配置MMCV环境,解决报错,版本不匹配问题

《浅谈配置MMCV环境,解决报错,版本不匹配问题》:本文主要介绍浅谈配置MMCV环境,解决报错,版本不匹配问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录配置MMCV环境,解决报错,版本不匹配错误示例正确示例总结配置MMCV环境,解决报错,版本不匹配在col

Pydantic中Optional 和Union类型的使用

《Pydantic中Optional和Union类型的使用》本文主要介绍了Pydantic中Optional和Union类型的使用,这两者在处理可选字段和多类型字段时尤为重要,文中通过示例代码介绍的... 目录简介Optional 类型Union 类型Optional 和 Union 的组合总结简介Pyd

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

使用Python自建轻量级的HTTP调试工具

《使用Python自建轻量级的HTTP调试工具》这篇文章主要为大家详细介绍了如何使用Python自建一个轻量级的HTTP调试工具,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录一、为什么需要自建工具二、核心功能设计三、技术选型四、分步实现五、进阶优化技巧六、使用示例七、性能对比八、扩展方向建

Feign Client超时时间设置不生效的解决方法

《FeignClient超时时间设置不生效的解决方法》这篇文章主要为大家详细介绍了FeignClient超时时间设置不生效的原因与解决方法,具有一定的的参考价值,希望对大家有一定的帮助... 在使用Feign Client时,可以通过两种方式来设置超时时间:1.针对整个Feign Client设置超时时间