Android---内存泄漏的优化
内存泄漏是一个隐形炸弹,其本身并不会造成程序异常,但是随着量的增长会导致其他各种并发症: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 实现的
当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中。并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue;
给包装后的 WeakReference 做一个标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录;
主动触发 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 内存泄漏优化的相关知识
内存泄漏预防
这需要了解 JVM 发生内存泄漏的原因,并在平时开发阶段养成良好的编码规范。针对编码规范 Android Studio 可以安装又给阿里代码规范的插件,能够起到一定的代码检查效果。
内存泄漏检测
内存泄漏检测工具有很多 Android Stuido 自带的 Profiler,以及 MAT 都是不错的选择。使用这些工具排查内存泄漏门槛稍高,并且全部是手动操作,略显麻烦。
此外,还介绍了一个自动检测内存泄漏的开源库---LeakCanary,主要包括它的实现原理以及部分源码实现细节。
相关文章:
Android---内存泄漏的优化
内存泄漏是一个隐形炸弹,其本身并不会造成程序异常,但是随着量的增长会导致其他各种并发症:OOM,UI 卡顿等。 为什么要将 Activity 单独做预防? 因为 Activity 承担了与用户交互的职责,因此内部需要持有大…...
C/S架构学习之基于UDP的本地通信(客户机)
基于UDP的本地通信(客户机):创建流程:一、创建数据报式套接字(socket函数): int sock_fd socket(AF_UNIX,SOCK_DGRAM,0);if(-1 sock_fd){perror("socket error");exit(-1);}二、创建…...
【性能测试】服务端中间件docker常用命令解析整理(详细)
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、搜索 docker …...
【探索Linux】—— 强大的命令行工具 P.14(进程间通信 | 匿名管道 | |进程池 | pipe() 函数 | mkfifo() 函数)
阅读导航 引言一、进程间通信概念二、进程间通信目的三、进程间通信分类四、管道1. 什么是管道2. 匿名管道(1)创建和关闭⭕pipe() 函数⭕创建匿名管道⭕关闭匿名管道 (2)通信方式(3)用法示例(4&…...
图论12-无向带权图及实现
文章目录 带权图1.1带权图的实现1.2 完整代码 带权图 1.1带权图的实现 在无向无权图的基础上,增加边的权。 使用TreeMap存储边的权重。 遍历输入文件,创建TreeMap adj存储每个节点。每个输入的adj节点链接新的TreeMap,存储相邻的边和权重 …...
每日一题(LeetCode)----数组--有序数组的平方
每日一题(LeetCode)----数组–有序数组的平方 1.题目([977. 有序数组的平方](https://leetcode.cn/problems/sqrtx/)) 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。…...
SpringCloud微服务:Eureka
目录 提供者与消费者 服务调用关系 eureka的作用 在Eureka架构中,微服务角色有两类 Eureka服务 提供者与消费者 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)服务消费者:一次业务中,调用其它微服务的服务。(调…...
19.删除链表的倒数第N个结点(LeetCode)
想法一 先用tail指针找尾,计算出节点个数,再根据倒数第N个指定删除 想法二 根据进阶的要求,只能遍历一遍链表,那刚刚想法一就做不到 首先,我们要在一遍内找到倒数第N个节点,所以我们设置slow和fast两个指…...
PyTorch技术和深度学习——三、深度学习快速入门
文章目录 1.线性回归1)介绍2)加载自由泳冠军数据集3)从0开始实现线性回归模型4)使用自动求导训练线性回归模型5)使用优化器训练线性回归模型 2.使用torch.nn模块构建线性回归模型1)使用torch.nn.Linear训练…...
360导航恶意修改浏览器启动页!我的chrome和IE均中招,如何解决?
0,关闭360等“安全”软件 1,按下组合键winR 2,输入regedit,回车 3,按下组合键ctrlF 4,输入http://hao.360.cn,查找下一个 5,查到一个注册表键值就删一个,一个不放过…...
RabbitMQ的高级特性
目录 数据导入 MQ的常见问题 消息可靠性问题 生产者确认机制 SpringAMQP实现生产者确认 消息持久化 消费者消息确认 失败重试机制 消费者失败消息处理策略 死信交换机 TTL 延时队列 安装插件 SpringAMQP使用插件 消息堆积问题 惰性队列 MQ的高可用 普通集群 …...
Java自学第10课:JavaBean和servlet基础
目录 目录 1 JavaBean (1)概念 (2)分类 (3)使用 2 servlet (1)代码结构 (2)常用接口 (3)如何开发 1 新建servlet 2 配置 1…...
AR打卡小程序:构建智能办公的新可能
【内容摘要】 随着技术的飞速发展,智能办公已不再是遥不可及的梦想。在这其中,AR打卡小程序以其独特的技术优势,正逐步成为新型办公生态的重要组成部分。本文将探讨AR打卡小程序的设计理念、技术实现以及未来的应用前景,并尝试深…...
Python环境安装、Pycharm开发工具安装(IDE)
Python下载 Python官网 Python安装 Python安装成功 Pycharm集成开发工具下载(IDE) PC集成开发工具 Pycharm集成开发工具安装(IDE) 安装完成 添加环境变量(前面勾选了Path不用配置) (1&…...
报时机器人的rasa shell执行流程分析
本文以报时机器人为载体,介绍了报时机器人的对话能力范围、配置文件功能和训练和运行命令,重点介绍了rasa shell命令启动后的程序执行过程。 一.报时机器人项目结构 1.对话能力范围 (1)能够识别欢迎语意图(greet)和拜拜意图(goodbye) (2)能够识别时间意…...
C#开发的OpenRA游戏之世界存在的属性UpdatesPlayerStatistics(2)
C#开发的OpenRA游戏之世界存在的属性UpdatesPlayerStatistics(2) 在文件OpenRA\mods\cnc\rules\ defaults.yaml里,可以看到这个配置,它的作用就是让这个单元可以被观察者查看到相关的信息。 UpdatesPlayerStatistics属性同样也是有两个类组成,一个叫做信息类UpdatesPlay…...
Ocelot:.NET开源API网关提供路由管理、服务发现、鉴权限流等功能
随着微服务的兴起,API网关越来越常见。API网关是连接应用程序和用户之间的桥梁,就像一个交通指挥员,负责处理所有进出应用的数据和请求,确保安全、高效、有序地流通。 今天给大家推荐一个.NET开源API网关。 01 项目简介 Ocelot…...
wsl [Ubuntu20.04.6] 安装 Hadoop
文章目录 1.安装WSL2.安装Java安装Hadoop3.3配置文件1.修改hadoop-env.sh2.修改core-site.xml3.修改hdfs-site.xml ssh启动 1.安装WSL 重启电脑 管理员打开powershell PS C:\windows\system32> wsl --list --online PS C:\windows\system32> wsl --install -d Ubuntu-2…...
2023华为ict网络赛道初赛(部分)试题
2023华为ict网络赛道初赛(部分)试题 10.在网络运维中,Telnet是用于连接远程设备的协议之一,那么以下哪一个设备不支持通过Telnet协议远程连接? PCACAPAR 12.openFlow交换机基于流表转发报文,每个流表项由…...
rabbitMq虚拟主机概念
虚拟主机是RabbitMQ中的一种逻辑隔离机制,用于将消息队列、交换机以及其他相关资源进行隔离。 在RabbitMQ中,交换机(Exchange)用于接收生产者发送的消息,并根据特定的路由规则将消息分发到相应的队列中。而虚拟主机则…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
