Android 图片压缩内存泄漏问题

最近在做 Android 相关的 YUV 图像压缩处理时,发现某些设备有内存泄露问题,这里作个简单梳理。

libjpeg

开源的 JPG 编解码库主要是 libjpeg,其他的诸如 libjpeg-turbomozjpeg 也是在它基础上 fork 的版本,增加了 SIMD 等支持,并且 API 也是兼容的。

libjpeg 压缩的核心流程如下:

下面我们从 Android 的相关源码着手分析。

YuvImage 的压缩

YuvImage.compressToJpeg() 实际上调用的是 YuvToJpegEncoder

但是,Android 8.0 的实现居然没释放资源:

bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width, int height, int* offsets, int jpegQuality) {
    ...
    if (setjmp(sk_err.fJmpBuf)) {
        return false;
    }
    jpeg_create_compress(&cinfo);
    cinfo.dest = &sk_wstream;
    setJpegCompressStruct(&cinfo, width, height, jpegQuality);
    jpeg_start_compress(&cinfo, TRUE);
    compress(&cinfo, (uint8_t*) inYuv, offsets);
    jpeg_finish_compress(&cinfo);
    return true;
}

Android 9.0 的实现才补上了资源释放逻辑:

bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width, int height, int* offsets, int jpegQuality) {
    ...
    if (setjmp(err.jmp)) {
        jpeg_destroy_compress(&cinfo);
        return false;
    }
    jpeg_create_compress(&cinfo);
    cinfo.dest = &sk_wstream;
    setJpegCompressStruct(&cinfo, width, height, jpegQuality);
    jpeg_start_compress(&cinfo, TRUE);
    compress(&cinfo, (uint8_t*) inYuv, offsets);
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    return true;
}

Bitmap 的压缩

但是,我们之前 Bitmap.compress() 用了这么多年也没发现内存泄漏。

同样分析源码,发现最终调用的是 SkImageDecoder_libjpeg.cpp

我们找个比较老的 5.0 的源码看下:

virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
    ...
    jpeg_create_compress(&cinfo);     
    ...
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
    ...
    jpeg_start_compress(&cinfo, TRUE);
    ...
    while (cinfo.next_scanline < cinfo.image_height) {
        ...
        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
        ...
    }
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    return true;
}

可以看到他最后也是有释放资源,所以没有内存泄露问题。

参考