上一篇文章提到了查看memory dump文件中的Bitmap对象相应的图像,进一步我们可以批量的将dump文件中所有的Bitmap对象转化为png格式的图像文件。MAT不仅提供了一组Eclipse extension points,还提供了从dump文件读取数据的API。利用MAT提供的API,我们可以写一个Java程序批量导出dump文件中所有的Bitmap。

MAT扩展API接口

利用ISnapshot接口可以从heap dump中解析数据,ISnapshot代表了整个heap dump,提供了各种方法从中读取object和class,获取objects的大小等。我们可以通过SnapshotFactory获取ISnapshot实例。IObject接口代表一个Java对象,IClass,IInstance和IArray都继承了IObject。IClass代表class对象,可通过getObjectIds()方法获得该class的所有实例ID。IInstance代表非array对象,可通过resloveValue()方法获得成员变量的值。IArray代表array对象,分为IPrimitiveArray和IObjectArray,可分别通过getValueArray()方法获得primitive array值或getReferenceArray()方法获得address array。

上述层次对象模型很直观并容易理解,但由于heap dump文件中有时会有非常多的对象,MAT不在ISnapshot实例中保存整个层次模型,而是为每个对象分配一个ID,并利用ID为索引从ISnapshot接口获得对象的具体信息,如class,size,referenced objects等。大都需要遍历对象的计算量大的工作都是通过对象ID而不是层次模型来实现,比如计算retained size,path to GC root等。只有当需要获得一个object的所有信息时,比如成员变量名称和变量值,才需要利用层次对象模型里的各种类。我们可以通过mapIdToAddress()和mapAddressToId()方式实现对象的ID和地址的互相转换。

android bitmap导出工具实现

首先需要获得MAT源码的核心模块,可以从MAT SVN repository下载全部源码并提取核心部分,或者直接参考ekabanovjoebowbeer项目,这两个项目已经将MAT源码核心模块抽取出来,只要修改自定义的部分就可以了。

下面给出从dump文件批量导出bitmap的源码。基本思路如下:

  • 通过SnapshotFactory获得与dump文件对应的ISnapshot对象
  • 通过类名android.graphics.Bitmap获得IClass对象
  • 通过IClass的getObjectIds()方法获得所有Bitmap对象ID集合
  • 通过ISnapshot的getObject()方法获得与Bitmap对象ID对应的IObject对象
  • 通过IObject的resloveValue()方法获得Bitmap对象的mHeight,mWidth和mBuffer成员变量值
  • 调用Java的BufferedImage,ImageIO等API将获得的数据保存为png文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public static void main(String[] args) throws Exception {
String arg = args[args.length - 1];
String fileName = arg;
IProgressListener listener = new ConsoleProgressListener(System.out);
SnapshotFactory sf = new SnapshotFactory();
ISnapshot snapshot = sf.openSnapshot(new File(fileName),
new HashMap<String, String>(), listener);
System.out.println(snapshot.getSnapshotInfo());
System.out.println();
String[] classNames = {
"android.graphics.Bitmap"
};
for (String name : classNames) {
Collection<IClass> classes = snapshot.getClassesByName(name, false);
if (classes == null || classes.isEmpty()) {
System.out.println(String.format(
"Cannot find class %s in heap dump", name));
continue;
}
assert classes.size() == 1;
IClass clazz = classes.iterator().next();
int[] objIds = clazz.getObjectIds();
long minRetainedSize = snapshot.getMinRetainedSize(objIds, listener);
System.out.println(String.format("%s instances = %d, retained size >= %d", clazz.getName(), objIds.length, minRetainedSize));
for(int i = 0; i < objIds.length; i++)
{
IObject bmp = snapshot.getObject(objIds[i]);
String address = Long.toHexString(snapshot.mapIdToAddress(objIds[i]));
int height = ((Integer)bmp.resolveValue("mHeight")).intValue();
int width = ((Integer)bmp.resolveValue("mWidth")).intValue();
byte[] buffer;
PrimitiveArrayImpl array = (PrimitiveArrayImpl)bmp.resolveValue("mBuffer");
if((height<=0) || (width<=0))
{
System.out.println(String.format("Bitmap address=%s has bad height %d or width %d!", address, height, width));
continue;
}
if(array == null)
{
System.out.println(String.format("Bitmap address=%s has null buffer value!", address));
continue;
}
buffer = (byte[])array.getValueArray();
int[] rgba = new int[width*height];
for(int j = 0; j < width*height; j++)
{
rgba[j] = ((buffer[j*4]<<16) | (buffer[j*4+1]<<8) | (buffer[j*4+2]) | (buffer[j*4+3]<<24));
}
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
image.setRGB(0, 0, width, height, rgba, 0, width);
try{
File outputfile = new File("bmp_" + address + ".png");
ImageIO.write(image, "png", outputfile);
}catch(IOException e){
e.printStackTrace();
}
System.out.println(String.format("id=%d, address=%s, height=%d, width=%d, size=%d", objIds[i], address, height, width, buffer.length));
}
}
}

编译后得到dump_android_bitmap.jar,通过如下命名即可将file.hprof文件中所有的bitmap对象导出为png格式图像文件

1
java -jar dump_android_bitmap.jar file.hprof

可以用Bitmap对象的地址作为文件名的一部分,以方便反查某一图像在MAT中对应的对象,并分析该对象的引用链。可以利用MAT提供的OQL(Object Query Language)查询dump文件中某个类的所有实例,或根据对象地址查询某个对象实例。

  • 查询所有mBuffer不为空的Bitmap对象
1
select * from android.graphics.Bitmap where mBuffer!=null
  • 查询特定对象地址对应的对象实例
1
select distinct * from objects 0x41b00e90,0x41ac0970

参考

http://www.slideshare.net/AJohnson1/extending-eclipse-memory-analyzer
https://wiki.eclipse.org/MemoryAnalyzer/Reading_Data_from_Heap_Dumps

查看Android memory dump文件中的Bitmap

在Android应用开发时一般使用Eclipse Memory Analyzer(MAT)分析堆内存占用情况,而Bitmap一般是占用内存最大的对象,所以有时我们希望知道MAT工具中显示的Bitmap对象对应哪个图像(本地资源文件或网络下载)。

在MAT中选定要查看的Bitmap,如下图所示,在Inspector窗口查看Bitmap对象的Attributes,其中mBuffer为Bitmap像素数据,mHeight和mWidth为Bitmap的高度和宽度。右键mBuffer并在弹出的菜单中选择”Copy”->”Save Value To File”,将这个array的值保存到文件中,并用.data作为扩展名。
MAT中的Bitmap

对于Linux用户,可以使用GIMP或ImageMagick工具将原始数据转换成png格式图像。方法如下:

  • 使用GIMP软件:打开GIMP,”File”->”Open”,选择刚才保存的.data文件,弹出”Load Image from Raw Data”对话框,设置合适的参数,设置”Image type”为“RGB Alpha”(大部分android resource是这种image type,若有其它格式,还要尝试其它的类型),设置width和height为mWidth和mHeight的值。这样GIMP界面就会显示出图片了。
  • 使用ImageMagick工具:将导出的.data文件扩展名改为.rgba
1
$ convert -size 'width'x'height' -depth 8 filename.rgba filename.png

对于Windows或MAC用户,可以使用Photoshop将原始数据转换成png格式图像。方法如下:

  • 将导出的.data文件扩展名改为.raw。“文件”->“打开”,选择文件格式“Camera Raw”,设置宽度和高度,将通道数设为4。这样Photoshop界面就会显示出图片了。

查看dump文件中unreachable的对象

一个Java对象没有引用后,该对象占的堆内存不是立即被回收的,回收的时机是JAVA虚拟机的GC算法来决定的,所以memory dump时产成的文件中会包含unreachable的对象。一般来说,如果MAT工具中看到的live对象的总大小比dump文件大小小很多,我们有必要查看一下哪些unreachable的对象占用了内存。MAT默认情况下在分析dump文件时会将unreachable的对象剔除,可以通过打开如下设置让MAT分析dump文件时保留unreachable对象:”Window”->”Preference”->”General configration for memory analyzer”->”keep unreachable objects”。

参考

http://stackoverflow.com/questions/12709603/mat-eclipse-memory-analyzer-how-to-view-bitmaps-from-memory-dump
https://wiki.eclipse.org/MemoryAnalyzer/FAQ#How_to_analyse_unreachable_objects