EN

Android 内存优化之三 - 打开 MAT 中的 Bitmap 原图

字数统计: 2.1k阅读时长: 8 min
2015/04/11
loading

本文是 MAT 工具使用系列的第三篇,这个系列共三篇,详细介绍了如何使用 MAT 来分析内存问题,既可以是 Java 应用的内存问题,也可以是 Android 应用的内存问题:

  1. Android 内存优化之一:MAT 使用入门
  2. Android内存优化之二:MAT使用进阶
  3. Android内存优化之三:打开MAT中的Bitmap原图

技术复核(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对象

导出Bitmap原始数据

在 MAT 的 Dominator Tree 中选中 Bitmap,查看 Inspector:

旧 Bitmap Inspector

原图可以读到 mWidth = 1536、mHeight = 2560,以及 mBuffer、mNativeBitmap 和 mIsPremultiplied 等字段。宽高是像素尺寸,不能用屏幕 dp 尺寸或显示控件大小代替。

AOSP android-4.4.4_r2 的 Bitmap.java 确实有这个隐藏字段,节选如下:

1
2
@SuppressWarnings("UnusedDeclaration") // native code only
public byte[] mBuffer;

这是特定实现的内部字段,不能当成稳定 SDK 接口。该版本也存在 mBuffer == null 的 native-backed Bitmap;到 android-8.0.0_r1,实现使用 mNativePtr 和 NativeAllocationRegistry,不能再普遍套用 mBuffer 导出法。

若 mBuffer 确实指向 byte[],沿字段引用打开那个数组实例,再选择 Copy → Save Value to File。不要只选 Bitmap 包装对象、地址字符串或整张表;MAT 的导出规则对单个基本类型数组导出二进制,对其他对象可能只保存可打印值。

Save Value to File

.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 数据导入器,下面是当时的界面:

旧 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import android.graphics.Bitmap;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public final class BitmapDebugExport {
private BitmapDebugExport() {}

public static void writePng(Bitmap bitmap, File target) throws IOException {
if (bitmap == null || bitmap.isRecycled()) {
throw new IllegalArgumentException("Bitmap is unavailable");
}
try (FileOutputStream output = new FileOutputStream(target)) {
if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)) {
throw new IOException("PNG encoding failed");
}
}
}
}

在后台线程调用,检查异常和编码结果;compress() 文档说明它可能耗时数秒。PNG 忽略这里的 quality 参数,但编码结果仍不等于最初的源文件;广色域、高位深内容也不能据此承诺逐字节还原。若只有离线 HPROF 而没有像素或可访问的运行中对象,应保留“只能分析持有关系,无法还原图像”的结论。

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 导出Bitmap原始数据
  2. 2. 打开原始资源数据
    1. 2.1. Linux
    2. 2.2. Mac && Windows
  3. 3. Android 8.0+:没有 mBuffer 时怎么排查
  • 关于我 && 博客