如何从Android堆转储中正确查看位图?

7
在跟踪我的应用程序中的严重内存问题时,我查看了几个来自我的应用程序的堆转储文件,并且大部分时间我都有一个我不知道的巨大位图。
它占用9.4MB,或9,830,400字节,实际上是一个1280x1920像素,每像素4个字节的图像。
我在Eclipse MAT中检查,它确实是一个byte[9830400],它只有一个传入引用,即android.graphics.Bitmap。
我想将其转储到文件中并尝试查看它。我不能理解它是从哪里来的。我所有可绘制内容中最大的图像是一个640x960 png,仅占用不到3MB。
我尝试使用Eclipse"将值复制到文件",但我认为它只是将缓冲区打印到文件中,我不知道任何可以读取字节流并将其显示为每像素4字节图像的图像软件。
有什么建议吗?
这是我尝试过的:将字节数组转储到文件中,在/sdcard/img中推送它,然后加载这样的活动:
@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        final File inputFile = new File("/sdcard/img");
        final FileInputStream isr = new FileInputStream(inputFile);
        final Bitmap bmp = BitmapFactory.decodeStream(isr);
        ImageView iv = new ImageView(this);
        iv.setImageBitmap(bmp);
        setContentView(iv);
        Log.d("ImageTest", "Image was inflated");
    } catch (final FileNotFoundException e) {
        Log.d("ImageTest", "Image was not inflated");
    }
}

我看不到任何东西。
你知道图片是如何编码的吗?假设它被存储在byte[] buffer中。 buffer[0]代表红色,buffer[1]代表绿色,以此类推。

这不就是本地渲染缓存吗?你是否启用了硬件加速? - Peterdk
是的,我确实有硬件加速。但这并不能解释为什么我的位图被放大了200%,以及为什么这个巨大的位图被分配在堆中,依我看来。 - Benoit Duffez
你有没有找到那个图片是什么?我也有一个这样的流氓图像,占用了1.1MB的空间,似乎来自预装的安卓资源。当我使用GIMP查看字节缓冲区的预览时,我看到了一个尺寸为1044x270的蓝色径向渐变图像。 - c05mic
是的,它是我drawables中的一张图片。请查看这个问题的被接受答案,其中有一个C程序可以将从MAT中找到的字节块转换为位图文件。 - Benoit Duffez
5个回答

3

2

好的--经过多次尝试,我终于从这个字节数组中得到了一些东西。我编写了这个简单的C程序来将字节数组转换为Windows位图文件。如果有人感兴趣,我会放置代码。
我使用VisualC 6.0和gcc 3.4.4进行编译,它应该可以在任何操作系统上工作(已在Windows、Linux和MacOS X上进行测试)。

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>

/* Types */
typedef unsigned char byte;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef int int32_t;

/* Constants */
#define RMASK 0x00ff0000
#define GMASK 0x0000ff00
#define BMASK 0x000000ff
#define AMASK 0xff000000

/* Structures */
struct bmpfile_magic {
  unsigned char magic[2];
};

struct bmpfile_header {
  uint32_t filesz;
  uint16_t creator1;
  uint16_t creator2;
  uint32_t bmp_offset;
};

struct bmpfile_dibheader {
  uint32_t header_sz;
  uint32_t width;
  uint32_t height;
  uint16_t nplanes;
  uint16_t bitspp;
  uint32_t compress_type;
  uint32_t bmp_bytesz;
  int32_t hres;
  int32_t vres;
  uint32_t ncolors;
  uint32_t nimpcolors;

  uint32_t rmask, gmask, bmask, amask;
  uint32_t colorspace_type;
  byte colorspace[0x24];
  uint32_t rgamma, ggamma, bgamma;
};

/* Displays usage info and exits */
void usage(char *cmd) {
    printf("Usage:\t%s <img_src> <img_dest.bmp> <width> <height>\n"
        "\timg_src:\timage byte buffer obtained from Eclipse MAT, using 'copy > save value to file' while selecting the byte[] buffer corresponding to an android.graphics.Bitmap\n"
        "\timg_dest:\tpath to target *.bmp file\n"
        "\twidth:\t\tpicture width, obtained in Eclipse MAT, selecting the android.graphics.Bitmap object and seeing the object member values\n"
        "\theight:\t\tpicture height\n\n", cmd);
    exit(1);
}

/* C entry point */
int main(int argc, char **argv) {
    FILE *in, *out;
    char *file_in, *file_out;
    int w, h, W, H;
    byte r, g, b, a, *image;
    struct bmpfile_magic magic;
    struct bmpfile_header header;
    struct bmpfile_dibheader dibheader;

    /* Parse command line */
    if (argc < 5) {
        usage(argv[0]);
    }
    file_in = argv[1];
    file_out = argv[2];
    W = atoi(argv[3]);
    H = atoi(argv[4]);
    in = fopen(file_in, "rb");
    out = fopen(file_out, "wb");

    /* Check parameters */
    if (in == NULL || out == NULL || W == 0 || H == 0) {
        usage(argv[0]);
    }

    /* Init BMP headers */
    magic.magic[0] = 'B';
    magic.magic[1] = 'M';

    header.filesz = W * H * 4 + sizeof(magic) + sizeof(header) + sizeof(dibheader);
    header.creator1 = 0;
    header.creator2 = 0;
    header.bmp_offset = sizeof(magic) + sizeof(header) + sizeof(dibheader);

    dibheader.header_sz = sizeof(dibheader);
    dibheader.width = W;
    dibheader.height = H;
    dibheader.nplanes = 1;
    dibheader.bitspp = 32;
    dibheader.compress_type = 3;
    dibheader.bmp_bytesz = W * H * 4;
    dibheader.hres = 2835;
    dibheader.vres = 2835;
    dibheader.ncolors = 0;
    dibheader.nimpcolors = 0;
    dibheader.rmask = RMASK;
    dibheader.gmask = BMASK;
    dibheader.bmask = GMASK;
    dibheader.amask = AMASK;
    dibheader.colorspace_type = 0x57696e20;
    memset(&dibheader.colorspace, 0, sizeof(dibheader.colorspace));
    dibheader.rgamma = dibheader.bgamma = dibheader.ggamma = 0;

    /* Read picture data */
    image = (byte*) malloc(4*W*H);
    if (image == NULL) {
        printf("Could not allocate a %d-byte buffer.\n", 4*W*H);
        exit(1);
    }
    fread(image, 4*W*H, sizeof(byte), in);
    fclose(in);

    /* Write header */
    fwrite(&magic, sizeof(magic), 1, out);
    fwrite(&header, sizeof(header), 1, out);
    fwrite(&dibheader, sizeof(dibheader), 1, out);

    /* Convert the byte array to BMP format */
    for (h = H-1; h >= 0; h--) {
        for (w = 0; w < W; w++) {
            r = *(image + w*4 + 4 * W * h);
            b = *(image + w*4 + 4 * W * h + 1);
            g = *(image + w*4 + 4 * W * h + 2);
            a = *(image + w*4 + 4 * W * h + 3);

            fwrite(&b, 1, 1, out);
            fwrite(&g, 1, 1, out);
            fwrite(&r, 1, 1, out);
            fwrite(&a, 1, 1, out);
        }
    }

    free(image);
    fclose(out);
}

因此,使用这个工具,我能够识别用于生成这个1280x1920位图的图片。


我想知道你是否以任何特殊方式在Mac OSX上保存了字节数组的内容?我能够编译您的代码并运行它来处理我从MAT中保存的文件的内容,但是Mac的预览图像查看器无法读取生成的BMP文件(假设原始文件存在问题,因为代码没有出错)。 - Roberto Andrade
我记不清了,我只是使用Eclipse的MAT中的“将值复制到文件”功能,并针对此文件运行了我的C程序。 - Benoit Duffez

2
我发现从最新版本的Android Studio(写作时为2.2.2)开始,您可以直接查看位图文件:
  1. 打开“Android Monitor”选项卡(在左下角),然后选择“Memory”选项卡。
  2. 点击“Dump Java Heap”按钮

  3. 选择当前快照的“Bitmap”类名称,选择每个位图实例并查看哪些图像消耗的内存比预期多。 (屏幕4和5)

  4. 选择 Bitmap 类名...

enter image description here

选择每个位图实例。

enter image description here

并在其上右键单击,选择“查看位图”。

enter image description here


0

您可以启用 USB 连接并将文件复制到另一台具有更多工具的计算机上进行调查。

当按下启动按钮时,某些设备可以配置为将当前屏幕转储到文件系统。也许这种情况发生在您身上。


非常感谢,但我不明白这与我的问题有什么关系,也不知道它如何帮助我将从堆转储中获取的字节数组转换为可查看的图像格式。 - Benoit Duffez

0

只需使用fileinput stream/datastream将输入转换为位图对象,并为使用的每个图像添加日志以查看数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接