本文是 MAT 工具使用系列的第三篇,这个系列共三篇,详细介绍了如何使用 MAT 来分析内存问题,既可以是 Java 应用的内存问题,也可以是 Android 应用的内存问题:
技术复核(2026-10-13):这是依赖旧 Bitmap 实现的历史技巧,保留原截图。下文以 AOSP android-4.4.4_r2 核对
mBuffer,以 Android 8.0 / API 26 说明 native 像素的变化,并补充 GIMP 3.2.6 导入参数及现代调试入口。
在 MAT 里查图片相关的内存问题时,如果能知道“到底是哪张图”,排查起来会方便很多。这里的“原图”指 Bitmap 在运行时保存的像素,不能恢复下载前的 JPEG/PNG 文件、压缩参数或 EXIF。MAT 能帮我们找到是谁留着这个对象,再把图像和业务场景对应起来;单凭一个 Bitmap 很大,还不能判断它泄漏了。
本文介绍从旧 heap dump 导出像素的步骤。前提是快照确实包含像素数组;只有 Java 包装对象和 native 地址时,不能从不存在的数据还原图片。
Bitmap 对象如图:

导出Bitmap原始数据
在 MAT 的 Dominator Tree 中选中 Bitmap,查看 Inspector:

原图可以读到 mWidth = 1536、mHeight = 2560,以及 mBuffer、mNativeBitmap 和 mIsPremultiplied 等字段。宽高是像素尺寸,不能用屏幕 dp 尺寸或显示控件大小代替。
AOSP android-4.4.4_r2 的 Bitmap.java 确实有这个隐藏字段,节选如下:
1 | // native code only |
这是特定实现的内部字段,不能当成稳定 SDK 接口。该版本也存在 mBuffer == null 的 native-backed Bitmap;到 android-8.0.0_r1,实现使用 mNativePtr 和 NativeAllocationRegistry,不能再普遍套用 mBuffer 导出法。
若 mBuffer 确实指向 byte[],沿字段引用打开那个数组实例,再选择 Copy → Save Value to File。不要只选 Bitmap 包装对象、地址字符串或整张表;MAT 的导出规则对单个基本类型数组导出二进制,对其他对象可能只保存可打印值。

.data 是方便 GIMP 选择 raw 导入器的扩展名,不是像素格式转换。另存为 .raw 也不会自动改变通道顺序、位深或透明度。保留原始导出文件,并记录这些参数:
| 参数 | 需要确认的内容 |
|---|---|
| 宽、高 | Bitmap 的实际像素尺寸;本图为 1536×2560 |
| Config / 每像素字节数 | 如 ARGB_8888 为每像素 4 字节、每通道 8 位;RGB_565 等不能沿用同一参数 |
| 行间距 rowBytes | 首像素偏移、每行字节数和行尾填充 |
| 通道排列与字节序 | 原始字节是 RGBA、BGRA 还是其他排列;不能只看 ARGB_8888 名字猜顺序 |
| Alpha 与色彩空间 | 是否预乘 alpha,导入器如何解释像素和颜色 |
| 数组长度与有效范围 | 是否导出完整数组,是否包含复用后的多余容量 |
例如,在紧密排列、4 字节/像素且没有额外容量的前提下,本图需要 1536 × 2560 × 4 = 15,728,640 字节(15 MiB)。Inspector 的 Retained Size 包含对象保留关系,不能直接当作 raw 文件长度。数据不够或参数不清楚时,需要先核实导出内容和像素布局,否则无法可靠还原。
打开原始资源数据
Linux
这里用 GIMP 的 raw 数据导入器,下面是当时的界面:

图里展示的是旧参数对话框,350×350 应替换为前面 Bitmap 的实际宽高。以已确认是紧密 RGBA、每通道 8 位、无文件头的数据为例,在 GIMP 中选择 File → Open,必要时在 Select File Type 手动指定 Raw image data。GIMP 3.0 打开文件说明
GIMP 3.2.6 的 raw 导入器对应选项是:
- Pixel format:RGBA 8-bit,对应旧版截图的 RGB Alpha 用途。
- Data type:Unsigned Integer。Java
byte的有符号表示不等于像素应按有符号通道导入。 - Planar configuration:Contiguous,表示每个像素的通道交错存储。
- Width / Height:实际像素宽高;本图为 1536 / 2560。
- Offset:只有确认首像素就在无头导出文件的起始处时才填 0。
这些参数只适合上述布局。若数据是 BGRA,需要按真实通道重新排列;若 rowBytes 大于 width × 4,要先处理每行填充,不能把填充字节当下一行像素。缺少布局信息时,这一步无法可靠完成。
还要处理预乘 alpha。Android 的 copyPixelsToBuffer()会按原样复制像素,预乘数据不会自动变成普通 RGBA。若目标导入器按非预乘值解释,半透明边缘可能变暗。对 8 位预乘通道,alpha 大于 0 时可用 round(预乘通道 × 255 / alpha) 近似恢复非预乘值并限制到 0–255;alpha 为 0 时,原颜色无法从预乘值恢复。原截图的 mIsPremultiplied 是检查线索,仍需结合实际 Config 和版本确认。能访问运行中的 Bitmap 时,也可使用文末的 PNG 导出方法,省去手动配置。
Mac && Windows
我当时就建议,几个平台都用 GIMP,少学一套操作,也省得来回切换工具。它是免费软件,功能也够用。
GIMP 同样可在 macOS、Windows 使用;raw 数据的解释不由操作系统决定。GIMP 官方下载提供各平台入口;本次页面列出的稳定版为 3.2.6(2026-09-10)。
如果用 Photoshop,应选择 Photoshop Raw 导入,而不是相机 Camera Raw。对于已确认的紧密 RGBA8 数据,使用实际宽高、4 个通道、每通道 8 位、交错排列、无头时 Header = 0。原文“深度为 32 位”不准确:RGBA8 是每像素共 32 位,而 Photoshop 的 32 Bits/Channel 是每通道 32 位,RGB 就有每像素 96 位。Photoshop 位深说明 / Raw 格式参数
第四通道还要按工具的 alpha/透明度设置正确使用;行间距、通道顺序和预乘问题并不会因换成 Photoshop 就消失。
Android 8.0+:没有 mBuffer 时怎么排查
API 26 起 Bitmap 像素在 native heap,Config.HARDWARE 还涉及图形内存。官方版本说明中有完整变化。此时先在 Android Studio 的 heap dump 中查 Bitmap 包装对象、BitmapDrawable 和缓存的引用关系,再结合 native 分配或图片库的缓存统计定位;Java HPROF 不包含的像素不能凭地址补出来。
如果能在自己的调试代码中拿到一个仍有效的 Bitmap,可以让系统编码为 PNG,避免手工猜通道布局。下面是用于 API 26+ 调试场景的完整辅助类;调用方提供可写文件,并保证导出期间 Bitmap 不被回收或修改:
1 | import android.graphics.Bitmap; |
在后台线程调用,检查异常和编码结果;compress() 文档说明它可能耗时数秒。PNG 忽略这里的 quality 参数,但编码结果仍不等于最初的源文件;广色域、高位深内容也不能据此承诺逐字节还原。若只有离线 HPROF 而没有像素或可访问的运行中对象,应保留“只能分析持有关系,无法还原图像”的结论。
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远
