Android---内存泄漏的优化

news2024/11/18 8:42:43

内存泄漏是一个隐形炸弹,其本身并不会造成程序异常,但是随着量的增长会导致其他各种并发症:OOM,UI 卡顿等。

为什么要将 Activity 单独做预防?

因为 Activity 承担了与用户交互的职责,因此内部需要持有大量的资源引用以及与系统交互的 Context,这会导致一个 Activity 对象的 retained size 特别大。一旦 Activity 因为被外部系统所持有而导致内存泄漏,会牵连导致其他对象的内存泄漏也会非常多。

造成 Activity 内存泄漏的场景

1)将 Context 或者 View 置为 static

View 默认会持有一个 context 的引用,如果将其设置为 static,将会照成 view 在方法区中无法被快速回收,最终导致 Activity 内存泄漏。上图中的 ImvageView 会造成 ActivityB 无法被 GC 回收。

2)未解注册各种 Listener

在 Activity 中可能会注册各种系统监听器,比如广播。运行上述 ActivityC 然后按下返回键,控制台将会显示如下 log,提示有内存泄漏发生。

3)非静态 Handler 导致 Activity 泄漏

上述代码中的 Handler 也会造成 ActivityD 发生内存泄漏。一般需要将其置为 static,然后内部持有一个 Activity 的弱引用来避免内存泄漏。如下所示

4)第三库使用 Context

在项目中经常会使用各种第三方库,有些第三方库的初始化需要我们传入一个 context 对象。但是第三方库中很有可能一直持有此 context 的引用。上述代码中,将ActivityE 本身当做了一个 context 传递给了一个模拟的第三方库 ThirdParty 中。但是,在第三方库中将传入的 context 重新置为一个静态的 static 类型。这种情况是一个隐形的 Activity 泄漏,在我们自己的项目中很难查出。所以,在开发过程中尽量使用 Context.getApplicationContext,不要直接将 Activity 传递给其它组件

内存泄漏检测

在开发阶段可以直接使用 Android Studio 来查看 Activity 是否存在内存泄漏,并结合 MAT 来查看发生内存泄漏的具体对象。详细使用过程可以参考:Android Studio 和 MAT 结合使用来分析内存问题

LeakCanary

除了 Android Studio,另一检测内存泄漏的神器就是 LeakCannary。LeakCanary 是 Square 公司的一个开源库,通过它可以在 App 运行过程中检测内存泄漏。当内存泄漏发生时会生成发生泄漏对象的引用链,并通知程序开发人员。LeakCanary 主要分2大核心部分:

1)如何检测内存泄漏;

2)分析内存泄漏对象的引用链。

如何检测内存泄漏---JVM理论知识

Java 中的 WeakReference 是弱引用类型,每当发生 GC 时,它所持有的对象,如果没有被其它强引用持有,那么它所引用的对象就会被回收。比如以下代码

上述代码执行过后,打印如下

可以看出,BigObject 并没有被其它强引用所持有,所以在 GC 回收后,WeakReference 所持有的 BigObject 对象被回收了。

WeakReference 的构造函数可以传入一个 ReferenceQueue,当 WeakReference 指向的对象被垃圾回收器回收时,会把 WeakReference 放入 ReferenceQueue 中,如下所示

上述代码调用 WeakReference 的构造器时传入一个自定义的 ReferenceQueue,那么打印结果如下

可以看出,当 BigObject 被回收之后,WeakReference 会被添加到所传入的 ReferenceQueue 中。

在修改一下上述代码,模拟一个内存泄漏。如下代码所示

BigObject 是一个强引用,导致 new BigObject 的内存空间不会被 GC 回收。最终打印结果如下

LeakCanary 实现思路

LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReference 和 ReferenceQueue 实现的

\bullet 当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中。并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue;

\bullet 给包装后的 WeakReference 做一个标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录;

\bullet 主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set 中的记录也删除。

经过上面3步,还保留在 Set 中的就是:应当被 GC 回收,但实际还保留在内存中的对象,也就是发生泄漏的对象。

源码分析

在上面原理介绍的例子里,一个可回收的对象在 System.gc() 之后就应该被 GC 回收。可是,在 Android App 中,我们并不清楚系统何时会回收 Activity。按照正常流程,当 Activity 调用 onDestory 方法时就说明这个 Activity 就已经处于无用状态了。因此需要监听到每一个 Activity 的 onDestory 方法的调用

ActivityRefWatch

LeakCanary 中监听 Activity 生命周期是由 ActivityRefWatch 来负责的。主要是通过注册 Android 系统提供的 lifecycleCallbacks 来监听 Activity 生命周期方法的调用。如下所示

lifecycleCallbacks 的具体代码如下

可以看出当监听到 Activity 的 onDestory() 方法后,会将其传给 refWatcher 的 watch() 方法

RefWatcher 

RefWatcher 它是 LeakCanary 的一个核心类,用来检测一个对象是否会发生内存泄漏。主要实现是在 watch() 方法中,如下代码所示

图中1处生成一个随机的字符串 key,这个 key 就是用来标识 WeakReference 的,就相当于给 WeakReference 打了一个标签。图中2处将一个被检测对象包装的 WeakReference 中,并将其标识为步骤1中生成的 key。图中3处调用 ensureGoneAsync 开始执行检测操作,因此关键代码就是在 ensureGoneAsync 中。代码如下

通过 WatchExecutor 执行了一个重载的方法 ensureGone。ensureGone中实现了内存泄漏的检测

图中1处会遍历 ReferenceQueue 中的所有元素,并根据每个元素中的 key 相应的将集合 retainedKeys 中的元素也删除;图中2处判断集合 retainedKeys 是否还包含被检测对象的弱引用。如果包含,说明被检测对象并没有被回收,也就是发生了内存泄漏。图中3处生成 heap 堆信息,并生成内存泄漏的分析报告,上报给程序开发人员。

removeWeaklyReachableReferences() 方法如下

这个方法的主要目的就是从 retainedKeys 中移除已经被回收的 WeakReference 的标志。

gone(reference) 方法判断 reference 是否被回收,如下

实现很简单,只要在 retainedKeys 中不包含此 reference 就说明 WeakReference 引用的对象已经被回收。

LeakCanary 的实现原理其实比较简单,但是内部实现还有一些其它细节值得我们注意。

内存泄漏的检测时机

很显然这种内存泄漏的检测与分析是比较消耗性能的,因此为了尽量不影响 UI 线程的渲染,LeakCanary 也做了些优化操作。在 ensureGoneAsync 方法中调用 watchExecutor.execute() 方法来执行检测操作,如下所示

可以看出,实际是向主线程的 MessageQueue 中插入了一个 IdleHandler。IdleHandler 只会在主线程空闲时才会被 Looper 从队列中取出并执行。因此能够有效避免内存检测工作占用 UI 渲染时间。

通过 addIdleHandler,也经常用来做 App 的启动优化。比如在 Application 的 onCreate 方法中经常做第三方库的初始化工作。可以将优先级较低、暂时使用不到的第三方库的初始化操作放到 IdleHandler 中,从而加快 Application 的启动过程。

特殊机型适配

因为有些特殊机型系统本身就存在一些内存泄漏情况,导致 Activity 不被回收,所以在检测内存泄漏时,需要将这些情况排除在外。

在 LeakCanary 的初始化方法 Install 中,通过 excludedRefs 方法指定了一系列需要忽略的场景。这些场景都被枚举在 AndroidExcludedRefs 中。

这种统一规避特殊机型的方式也值得我们借鉴,因为国内的手机厂商实在是太多了。

LeakCanary 如何检测其它类

LeakCanary 默认只能检测 Activity 的泄漏,但是 RefWatcher 的 watch 方法传入的参数实际是 Object,所以理论上是可以检测任何类的。

LeakCanary 的 install() 方法会返回一个 RefWatcher 对象,我们只需要在 Application 中保存此 RefWatcher 对象,然后将需要被检测的对象传给 watch 方法即可。如上代码所示,testedObj 就是一个需要被检测内存泄漏的对象。

总结

本次主要介绍了 Android 内存泄漏优化的相关知识

\bullet 内存泄漏预防

这需要了解 JVM 发生内存泄漏的原因,并在平时开发阶段养成良好的编码规范。针对编码规范 Android Studio 可以安装又给阿里代码规范的插件,能够起到一定的代码检查效果。

\bullet 内存泄漏检测

内存泄漏检测工具有很多 Android Stuido 自带的 Profiler,以及 MAT 都是不错的选择。使用这些工具排查内存泄漏门槛稍高,并且全部是手动操作,略显麻烦。

此外,还介绍了一个自动检测内存泄漏的开源库---LeakCanary,主要包括它的实现原理以及部分源码实现细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1196652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D渲染原理及朴素JavaScript实现【不使用WebGL】

在网页中显示图像和其他平面形状非常容易。 然而,当涉及到显示 3D 形状时,事情就变得不那么容易了,因为 3D 几何比 2D 几何更复杂。 为此,你可以使用专用技术和库,例如 WebGL 和 Three.js。 但是,如果你只…

SSH 远程登录 WSL

更新ssh设置 sudo apt-get update sudo apt-get remove openssh-server sudo apt-get install openssh-server 编辑网络配置 sudo vi /etc/ssh/sshd_config (1)修改ssh服务监听端口和监听地址 (2)修改ssh服务允许使用用户名密码…

KEIL MDK 调试 无法 查看 外设 信息 原因及解决方法

MDK5.38版本有bug : 不能把STM32F4的官方SVD文件转换成SFR,而MDK5.38a版本没有此问题。

【操作系统内核】进程

【操作系统内核】进程 进程的组成 进程的运行,需要考虑 磁盘 > 内存 > CPU > 内核 > 进程切换 这个过程 首先,程序运行要将可执行文件加载到内存,所以进程要读取可执行文件(运行后可能还需要读取其他文件的数据),需…

JUC包下面的四大天王+线程池部分知识

一)Semphore:限流器用我就对了 Java中信号量Semphore是把操作系统原生的信号量封装了一下,本质就是一个计数器,描述了 可用资源的个数,主要涉及到两个操作 如果计数器为0了,继续Р操作,就会出现阻塞等待的情况 P操作:申…

徒步“三色”泸溪 共赏冬日胜景

(金笛 胡灵芝)11月11日,“中国体育彩票”2023年“走红军走过的路”徒步穿越系列活动(泸溪站)暨泸溪文旅推荐活动在泸溪县举行,来自全国各地千余名户外爱好者通过徒步的方式,传承红色基因&#x…

C语言--输入10个数字,要求输出其中值最大的元素和该数字是第几个数

今天小编带大家了解一下什么是“打擂台”算法。 一.思路分析 可以定义一个数组arr,长度为10,用来存放10个数字,设计一个函数Max,用来求两个数中的较大值, 定义一个临时变量tmparr[0],保存临时最大的值,下标…

邻接表储存图实现广度优先遍历(C++)

目录 基本要求: 邻接表的结构体: 图的邻接表创建: 图的广度优先遍历(BFS): 邻接表的打印输出: 完整代码: 测试数据: 结果运行: 通过给出的图的顶点和…

归并排序 merge Sort + 图解 + 递归 / 非递归

归并排序(merge sort)的主要思想是:将若干个有序序列逐步归并,最终归并为一个有序序列二路归并排序(2-way merge sort)是归并排序中最简单的排序方法 (1)二路归并排序的递归实现 // 二路归并排序的递归实现 void merge(vector&l…

EFCore: The ConnectionString property has not been initialized.

使用NuGet的程序包管理控制台执行命令“update-database”的时候报出该错误 经过检查发现是optionsBuilder.UseSqlServer(strConn);中的strConn没有写

数据库01-慢查询优化

目录 MySQL优化 慢查询 如何定位慢查询? 如何分析慢查询? MySQL优化 MySQL 优化是数据库管理和应用性能调优的一个重要方面。以下是一些常规性的 MySQL 优化经验和适用场景: 索引优化: 确保表的字段上有适当的索引&#xff0…

计算机毕业设计选题推荐-农产品销售微信小程序/安卓APP-项目实战

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

C语言全部关键字解析

前言 C语言具有以下关键字: 这些关键字如下: 关键字autobreakcasecharconstcontinuedefaultdodoubleelseenumexternfloatforgotoifintlongregisterreturnshortsignedsizeofstaticstructswitchtypedefunionunsignedvoidvolatilewhile 对于这些关键字,大…

牛客网刷题笔记131111 Python实现LRU+二叉树先中后序打印+SQL并列排序

从学校步入职场一年多,已经很久没刷过题了,为后续稍微做些提前的准备,还是重新开始刷刷题。 从未做过计划表,这回倒是做了个计划表,希望能坚持吧。 刷题比较随性且量级不大,今天就写了2个算法2个sql&#x…

第四节(2):修改WORD中表格数据的方案

《VBA信息获取与处理》教程(10178984)是我推出第六套教程,目前已经是第一版修订了。这套教程定位于最高级,是学完初级,中级后的教程。这部教程给大家讲解的内容有:跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互联网…

Qt界面设计时使各控件依据窗口缩放进行自适应填充的方法——使用布局、Spacer等控件

Qt界面设计时使各控件依据窗口缩放进行自适应填充的方法—使用布局、Spacer等控件 Chapter1 Qt界面设计时使各控件依据窗口缩放进行自适应填充的方法—使用布局、Spacer等控件Chapter2 Qt Creator中布局器详解01. 概述02. 开发环境03. 布局器概述04. 布局属性设置05. 弹簧条属性…

简单版本管理服务编写

说明: 制作android应用内更新的时候,经常会用到版本检查,下载,安装,这时候需要写一个版本管理服务。 本文说明了自己编写版本服务的简单经过。 解决方案: 该软件实现如下功能: 创建后台接口:版本软件上传…

基于PHP的设云尘资讯网站设计与实现

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下,你想解决的问…

保姆级自定义GPTs教程,无需任何代码!

11月10日,OpenAI正式宣布向所有ChatGPT Plus用户开放GPTs功能,一个人人都能开发自定义ChatGPT助手的时代降临。 GPTs支持无代码、可视化点击操作,这意味着即便你没有任何编程经验,只要有数据、脑洞大开的想法,就能开发…

探索微信小程序框架的精华——高质量的优秀选择

目录 引言: 1. 框架性能 2. 开发者工具支持 3. 文档和社区支持 4. 扩展能力 5. 使用率和稳定性 结语: 引言: 微信小程序作为一种轻量级、高效便捷的应用形式,已经在移动应用领域占据了重要地位。而其中,选择一个…