本文正在参与「金石计划」
大家好,我是 shixin。
经过上一篇文章 # 自研的内存剖析利器开源了!Android Bitmap Monitor 助你定位不合理的图片运用 咱们了解了好用的图片内存剖析东西 AndroidBitmapMonitor,现在咱们来了解下它的原理。
这篇文章主要包含三部分:
- 为什么要重视图片占用的内存
- 许多人可能会觉得,都现已 2023 年了,大多数设备及 APP 都是 64 位的,为什么还需求重视内存?这一部分做一个简略的回答
- 图片内存监控、剖析常见计划
- 这一部分咱们来探讨下现在社区里常用的一些图片内存监控计划,及优缺陷。
- 介绍新计划的功用和原理
为什么要重视图片占用的内存
之所以要重视图片内存,是因为内存是 Android 功用优化的重要目标,而图片通常是 app 内存运用的大头。
通常来说,内存运用不当会有这些问题:
- 溃散
- 后台存活时间短
- 卡顿
溃散是指虚拟内存不足导致的使用 crash,包含 Java 内存不足、Native 内存不足等原因。许多同学做内存优化时往往只重视 Java 内存,但随着 Android 官方对体系的优化(比方在 8.0 今后将图片数据保存在 Native 内存中)和内存泄漏排查东西的完善,app 的Java 内存问题越来越少, 留传的扎手问题常常是 Native 内存问题。
后台存活时间短是指在内存不足时的 low memory killer 机制会依据进程优先级和内存运用状况强制关闭进程。如果使用在后台、且内存运用较高,很简略被体系强制关闭。
卡顿是指内存颤动引发的频繁垃圾收回,垃圾收回一方面会抢占主线程和烘托线程的 CPU 、另一方面也可能会触发阻塞式 GC 直接导致卡顿。
这几点是优化内存的必要性。而图片因为其动辄占用几 MB 的内存,经常成为内存过高的首恶。比方在分辨率为 3200 x 1440 的手机上,一张撑满全屏的图片就要占用 17Mb(3200 x 1440 x 4)。
图片运用的内存如此之大,导致线上常常会呈现这种问题:
- 服务端下发的图片尺度比实际要展现的大太多,导致内存运用过多甚至溃散
- Bitmap 创立后没有及时收回,导致重复进入退出页面后内存不断上涨
- 快速滑动列表时一下子加载过多图片,导致内存飙升、卡顿
随着 app 的复杂度提高,这些问题呈现的可能性越来越高。因而咱们有必要重视图片内存状况,把握有用的监控、剖析手法,然后在 app 遇到图片内存时能够及时的解决。
好了,这一节咱们了解了为什么要重视图片占用的内存,下一节来看下常见的图片内存剖析计划。
图片内存剖析常见计划
图片内存剖析,是指获取到 app 在某个时间段内创立的图片总数、占用内存大小和创立仓库,然后定位到导致内存反常的代码。
现在常见的图片内存剖析计划有这几种:
如上图所示,主要有 HPROF 剖析、Java Hook 和编译时修正字节码三种方法。
HPROF 剖析
咱们在开发期间或许复现问题时,能够经过 hprof dump 的方法获取 Java 目标的堆快照,然后找到其间的 Bitmap 目标。
因为 Android 中图片要加载出来终究需求创立 Bitmap 目标,所以经过 Java 的 Bitmap 目标的长宽咱们就能够估算出图片的大约尺度。
这种方法的长处是简略方便,经过 Android Studio 或许 MAT 就能够完结;但缺陷是只能用于 debug 包,别的常常有许多 Bitmap 目标的引证链是通用的路径,导致无法定位到导致问题的代码(Android Studio 的 Bitmap Preview 功用只能支撑 8.0 以下体系)。
Java Hook
Java hook 是指经过 YAHFA、epic 等框架,在运行时替换图片创立的相关代码进口函数,然后完成阻拦 Bitmap 创立。
以 YAHFA 为例,要阻拦 ImageView.setImageBitmap 函数,能够创立一个这样的署理类:
在上面的代码中,经过 className methodName 和 methodSig 能够声明要阻拦的具体方法,然后在 hook 函数中,能够履行咱们的阻拦逻辑,比方记录 Bitmap 的尺度信息。
这种方法的长处是完成简略,能够拿到的信息较多;缺陷是不行稳定,因为底层原理是替换 ArtMethod 的 entryPoint(进口点),因为不同 Android 版别上 ArtMethod 中的结构有变化,因而寻找 entryPoint 的进程需求兼容不同版别,简略呈现兼容性问题,只能在线下运用。
编译时修正字节码
在线上要监控图片内存,常用的方法是在编译时经过 AspectJ/ASM/JavaAssit 等方法阻拦 Bitmap 创立的代码,在其间统计 Bitmap 的长宽、创立仓库等信息。
和 JavaHook 不同的在于,编译时修正字节码是修正 APP 中的代码,而不是修正体系的代码,因而稳定性得到了保证。
这种方法的长处是能够获取到比较全面的信息;缺陷是需求阻拦的代码比较多,需求兼容不同版别的 API,本钱较高,一起获取到的仓库常常是图片加载库的仓库,无法直接定位到事务代码。
下面是图片创立相关的 API,能够看到触及的方法许多:
这一节咱们了解了常见的图片内存剖析计划的优缺陷和运用场景。
接下来咱们来一看一种愈加完善的新计划 Android Bitmap Monitor。
新计划是什么样的和完成原理
Android Bitmap Monitor 便是今日要介绍的新计划,这一节咱们来看下它的功用和完成原理。
Android Bitmap Monitor 是一个开源的 Android 图片内存剖析东西,能够协助开发者快速发现使用内加载的图片是否合理,比方占用内存大小是否合适、是否存在泄漏、缓存是否及时清理、是否加载了当前并不需求的图片等等。
github.com/shixinzhang…
支撑这些功用:
- 支撑获取内存中的图片数量及占用内存
- 供给 Bitmap 创立仓库及线程
- 支撑导出 Bitmap 图片,在仓库无法看出问题时能够用来定位图片所属事务
- 支撑动态开关,能够统计具体事务的数据
接下来咱们来看下它的三个核心功用的完成原理:
怎样阻拦到图片创立的
首要,AndroidBitmapMonitor 经过 inline-hook 的方法阻拦了 Java Bitmap 目标创立的统一进口,这就避免了前面说到的了运行时 epic hook 和编译时 AOP 阻拦的问题–需求兼容不同的图片创立代码。它阻拦的哪个进口呢?这需求咱们了解下不同版别的 Bitmap 创立流程。
咱们知道,为了减少图片内存对使用稳定性的影响,Android 官方对图片的像素数据保存方法做了多次修正,现在的状况是:
- Android 8.0 以前及 Android 3.0 今后,像素数据保存在 Java 堆内存中
- Android 8.0 开端,像素数据保存在 Native 内存中
这样修正的结果便是,Java 层 Bitmap 目标只保存了长宽和是否收回的信息,没有保存像素数据,因而经过 Bitmap 目标无法获取到图片的真实数据,这也是前面说到的几种计划的统一问题。
但是,不管上层是经过什么方法创立的图片,终究都会履行到 Native 层的 Bitmap.cpp 的 Bitmap_creator 函数,在其间创立 Java 层的 Bitmap 目标并保存像素数据。
因而,咱们能够经过 hook 这个函数,就能够阻拦到图片创立的信息,比方宽高和仓库信息。
对应的代码方位:github.com/shixinzhang…
如何统计到未被收回的图片
知道了创立的图片信息是第一步,更重要的是知道哪些图片没有被及时收回。
经常遇到的图片泄漏问题:手动 decode 的 Bitmap 没有及时调用 recycle,导致重复进入页面内存不断上涨,终究导致功用反常。
在 Android 不同版别上,Bitmap 目标的开释流程有所不同:
- Android 8.0 及以前版别:调用 Java Bitmap 的 recycle 方法经过 JNI 调用仅仅开释了引证,图片的像素数据所占内存需求等待 GC 履行时才开释
- 从 8.0 开端,会直接开释掉 native 内存
两者的共同点是在履行后 Java Bitmap 的mRecycled 状况会变为 true。因而咱们能够经过轮训 Bitmap 目标的 mRecycled 特点来判别这个图片是否被收回,完成方法如下图所示:
经过前面的图片创立流程监控咱们拿到了当前创立的所有图片数据,然后能够经过一个线程守时轮训当前拿到的图片目标状况,当发现图片引证被收回或图片目标的 mRecycled 为 true 时,从记录中移除这个图片数据,最后得到的便是没有被收回的图片。
对应的代码方位:github.com/shixinzhang…
图片复原怎样完成的
上一节对比不同计划时,咱们说到有时候图片创立是经过图片库统一完结的,这种状况下获取到的仓库无法看出事务代码。
这种状况下咱们就需求经过图片内容来判别到底是哪里的事务有问题。
可能有小伙伴知道,Android Studio 的 Bitmap Preview 功用是支撑查看图片内容的,但很可惜只支撑 Android 8.0 以前的设备。这是因为它的完成原理是经过 HPROF 中 Bitmap 目标的 mBuffer 数据,因而只支撑 8.0 以前的手机。
AndroidBitmapMonitor 完成了全版别的图片复原功用,底子差异就在于,是从 Native 层做的像素数据获取。
NDK 的 bitmap.h 供给了 AndroidBitmap_lockPixels 函数,经过它咱们能够获取到图片的像素数据:
咱们知道,图片本质上便是像素点的集合:
遍历像素数据,然后按照通用图片的格局(比方 BMP、PNG)输出为文件,就能够获取到图片的完整内容。
对应的代码方位: github.com/shixinzhang…
总结
好了,到这儿咱们就了解了图片内存剖析新计划 AndroidBitmapMonitor 的完成原理。
AndroidBitmapMonitor 能够为咱们供给具体的图片创立信息,基于它能够完成的功用有这些:
- 大图报警
- 图片泄漏监控
- 图片重复解码等等
源码地址:github.com/shixinzhang…
好了,这篇文章到这儿就完毕了,感谢你的阅览,愿你平安顺利。
如果对你有协助,欢迎评论点赞转发,你的支撑是我最大的动力❤️
引荐阅览:
两年创业的得与失
简历怎样投效率最高
七年迈安卓的九十月小结
六年安卓开发的技术回顾和展望
两位阿里 P10 的成长阅历,让我学到这几点