【问题标题】:How to actually see a Bitmap taken from an Android heap dump如何实际查看从 Android 堆转储中获取的位图
【发布时间】:2026-02-15 20:40:01
【问题描述】:

在跟踪我的应用程序中严重的内存问题的过程中,我查看了我的应用程序的几个堆转储,大多数时候我有一个我不知道的巨大位图。

它需要 9.4MB,即 9,830,400 字节,或者实际上是 1280x1920 的图像,每像素 4 字节。

我检查了 Eclipse MAT,它确实是一个字节 [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] 是绿色,等等?

【问题讨论】:

  • 不就是原生渲染缓存吗?您是否在启用硬件加速的情况下运行?
  • 是的,我确实有硬件加速。但这并不能解释为什么我的位图被放大 200% 以及为什么这个巨大的位图被分配在堆中,恕我直言。
  • 你有没有找到它是什么图像?我也有一个占用 1.1 MB 并且似乎来自 android 预加载资源的流氓图像。当我使用 GIMP 查看字节缓冲区的预览时,我看到了一个大小为 1044x270 的蓝色径向渐变图像。
  • 是的,它是我的drawables中的一张图片。检查这个问题的接受答案,有一个 C 程序可以将从 MAT 找到的字节 blob 转换为位图文件。

标签: android bitmap eclipse-mat


【解决方案1】:

只需将输入到图像并使用文件输入流/数据流将其转换为位图对象。还添加日志以查看每个使用的图像的数据。

【讨论】:

    【解决方案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(假设原始文件有问题,因为代码没有出错)
    • 我记不清了,我只是使用 Eclipse 的“将值复制到文件”从 MAT,然后针对这个文件运行我的 C 程序。
    【解决方案3】:

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

    某些设备可以配置为在按下开始按钮时将当前屏幕转储到文件系统。也许这会发生在你身上。

    【讨论】:

    • 非常感谢,但我不明白这与我的问题有什么关系,以及它如何帮助我将从堆转储中获取的字节数组转换为可查看的图像格式。
    【解决方案4】:

    查看这里以获得更简单的答案:MAT (Eclipse Memory Analyzer) - how to view bitmaps from memory dump

    TL;DR - 安装 GIMP 并将图像加载为原始 RGB Alpha

    【讨论】:

      【解决方案5】:

      我发现从最新版本的 Android Studio 开始(撰写本文时为 2.2.2),您可以直接查看位图文件:

      1. 打开“Android 监视器”选项卡(位于左下方),然后打开内存选项卡。
      2. 按下“转储 Java 堆”按钮

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

      4. 选择Bitmap类名……

      1. 选择每个位图实例

      然后右键单击它,选择View Bitmap

      【讨论】: