其他分享
首页 > 其他分享> > Android Bitmap的使用及优化

Android Bitmap的使用及优化

作者:互联网

Bitmap内存模型

无论是 Api 26 前还是之后的回收实现,释放 Native 层的 Bitmap 对象的思想都是去监听 Java 层的 Bitmap 是否被释放,一旦当 Java 层的 Bitmap 对象被释放则立即去释放 Native 层的 Bitmap 。只不过 Api 26 以前是基于 Java 的 GC 机制,而 Api 26 后是注册 native 的 Finalizer 方法,更详细的分析可查看: 图形图像处理 - 我们所不知道的 Bitmap

BitmapFactory.Options

BitmapFactory.Options 是 BitmapFactory 从不同的输入源中创建 Bitmap 对象的控制参数。

@Nullable
public static Bitmap decodeFile(@NonNull String pathName) {
    Bitmap bitmap;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(pathName, options);
    options.inJustDecodeBounds = false;
    options.inSampleSize = 1;
    Bitmap inBitmap = AndroidBitmapPool.getInstance().get(options.outWidth, options.outHeight, options.inPreferredConfig);
    try {
        // 判断是否可以使用 inBitmap,因为 inBitmap 在不同 Android 版本存在一些不同的限制
        if (inBitmap != null && Util.canUseInBitmap(inBitmap, options)) {
            // 复用需要把可修改的开关打开
            options.inMutable = true;
            options.inBitmap = inBitmap;
        } else {
            AndroidBitmapPool.getInstance().putBitmap(inBitmap);
        }
        bitmap = BitmapFactory.decodeFile(pathName, options);
        // 检查是否复用成功
        if (bitmap == options.inBitmap) {
            Log.i(TAG, "decodeFile: inBitmap reuse successfully");
        }
    } catch (Exception e) {
        Log.e(TAG, "decodeFile", e);
        bitmap = BitmapFactory.decodeFile(pathName);
    }
    return bitmap;
}

public static boolean canUseInBitmap(@NonNull Bitmap inBitmap, @NonNull BitmapFactory.Options options) {
    //{@link android.graphics.BitmapFactory.Options.inBitmap} prior to KITKAT has some constraints
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        int width = options.outWidth / options.inSampleSize;
        int height = options.outHeight / options.inSampleSize;
        int byteCount = width * height * getBytesPerPixel(inBitmap.getConfig());
        int inBitmapByteCount = getBitmapByteSize(inBitmap);
        return inBitmapByteCount >= byteCount;
    }

    return options.inSampleSize == 1 && options.outWidth == inBitmap.getWidth() && options.outHeight == inBitmap.getHeight();
}

  inSampleSize测试实例

val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(IMAGE_PATH, options)
Log.i(TAG, "width = ${options.outWidth}, height = ${options.outHeight}, mimeType = ${options.outMimeType}")
val imageWidth = options.outWidth
val imageHeight = options.outHeight
options.inJustDecodeBounds = false
for (i in 1 until 6) {
    options.inSampleSize = i
    val bitmap = BitmapFactory.decodeFile(IMAGE_PATH, options)
    Log.i(TAG, "bitmap width = ${bitmap.width}, height = ${bitmap.height}, width for inSampleSize = ${imageWidth / bitmap.width}, height for inSampleSize = ${imageHeight / bitmap.height}")
}
/*
width = 4000, height = 3000, mimeType = image/jpeg
bitmap width = 4000, height = 3000, width for inSampleSize = 1, height for inSampleSize = 1
bitmap width = 2000, height = 1500, width for inSampleSize = 2, height for inSampleSize = 2
bitmap width = 1333, height = 1000, width for inSampleSize = 3, height for inSampleSize = 3
bitmap width = 1000, height = 750, width for inSampleSize = 4, height for inSampleSize = 4
bitmap width = 800, height = 600, width for inSampleSize = 5, height for inSampleSize = 5
*/

  确定 inSampleSize 的大小

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    // Raw height and width of image
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1

    if (height > reqHeight || width > reqWidth) {

        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }

    return inSampleSize
}

ALPHA_8 -> 1个字节
RGB_565 -> 2个字节(每个像素需要16个bit来表示)
ARGB_4444 -> 4个字节
RGBA_F16 -> 8个字节
ARGB_8888 -> 4个字节

文件夹 density
drawable 0
ldpi 120
mdpi 160
hdpi 240
xhdpi 320
xxhdpi 480
xxxhdpi 640

  将图片放入默认 drawable 文件夹(不指定分辨率),则最终会使用默认的 Density(DisplayMetrics.DENSITY_DEFAULT=160)

Bitmap 内存占用计算

计算公式:

(width / inSampleSize * inTargetDensity / inDensity) * (height / inSampleSize * inTargetDensity / inDensity) * bytesPerPixel

  其中bytesPerPixel的值根据解码图片传入的 Bitmap.Config 决定,可参考inPreferredConfig,如果不是 drawable 文件夹下的资源的话,计算公式中 inTargetDensity / inDensity 当作1来处理,也就是不需要理会inTargetDensityinDensity导致的缩放影响。
  对于 bitmap 的内存占用大小,可以通过getByteCount方法获取。在 Api 19 (Build.VERSION_CODES#KITKAT)及以后,新增了一个方法getAllocationByteCount,其表示分配给 bitmap 的内存大小,这个值大于等于getByteCount的数值。一般情况下,二者的返回值相当,当 bitmap 复用的时候,则可能大于getByteCount的值。

支持解码的图片格式

注:对于 BitmapRegionDecoder 只支持 JPEG 和 PNG 格式的图片

Format Encoder Decoder Details File Types Container Formats
BMP YES BMP (.bmp)
GIF YES GIF (.gif)
JPEG YES YES Base+progressive JPEG (.jpg)
PNG YES YES PNG (.png)
WebP Android 4.0+ Lossless: Android 10+ Transparency: Android 4.2.1+ Android 4.0+ Lossless: Android 4.2.1+ Transparency: Android 4.2.1+ Lossless encoding can be achieved on Android 10 using a quality of 100. WebP (.webp)
HEIF Android 8.0+ HEIF (.heic; .heif)

Bitmap 内存优化

   Bitmap 在应用中一般是导致 OOM 的几大原因之一,如何减少解码图片导致的 OOM 及 Bitmap 的创建回收导致的内存抖动就显得尤为重要。Bitmap 内存优化一般有以下几个手段:

参考链接

标签:inSampleSize,bitmap,height,width,Android,Bitmap,优化,options
来源: https://www.cnblogs.com/ZhaoxiCheung/p/12041421.html