使用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

相关文章

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

解决RocketMQ的幂等性问题

《解决RocketMQ的幂等性问题》重复消费因调用链路长、消息发送超时或消费者故障导致,通过生产者消息查询、Redis缓存及消费者唯一主键可以确保幂等性,避免重复处理,本文主要介绍了解决RocketM... 目录造成重复消费的原因解决方法生产者端消费者端代码实现造成重复消费的原因当系统的调用链路比较长的时