[关闭]
@946898963 2020-06-16T06:58:17.000000Z 字数 6175 阅读 2198

BitmapFactory.Option中的inBitmap

Bitmap


inBitmap是在BitmapFactory中的内部类Options的一个变量,简单而言,使用该变量可以复用旧的Bitmap的内存而不用重新分配以及销毁旧Bitmap,进而改善运行效率。

关于Bitmap的相关知识可以查看我写的Android中Bitmap的深入探讨总结

recycle

bitmap是很消耗内存的,当bitmap不用时我们需要回收

  1. bitmap.recycle();

以上代码可以回收bitmap在堆中的内存,回收之后,bitmap不得再重新使用。

Android 2.3(3.0) 前后。

在Android 3.0以前,Bitmap的存储分为两部分,像素数据存储在Native堆上,而Bitmap本身,或者说像素的引用,存储在Java堆上,Java虚拟机本身可以回收掉Java堆上的数据,但是对于Native堆上的数据,不受到Java虚拟机垃圾回收机制的影响,所以,当我们确定一个Bitmap不可用的时候,需要手动的调用recycle()方法去回收掉这部分Native内存(recycle方法是调用jni方法释放内存的),注意recycle()方法是不可逆的,当调用了这个方法后,Bitmap就会被标记为死亡状态,这时候再调用Bitmap的相关方法就会出错,所以只有当我们确定Bitmap不会再被使用的时候,才能调用这个方法。在Android 3.0以后,Bitmap的像素部分和Bitmap本身都是存储在Java堆上的,所以这部分的内存会由Java虚拟机的垃圾回收机制自动进行回收,我们没必要在手动的去调用recycle()方法去释放内存。

当调用recycle()方法后,Bitmap并不会被立即回收掉,只是会被标记为死亡状态,等待垃圾收集器将其进行回收。

建议阅读

管理Bitmap的内存使用

Bitmap为什么要调用recycle()方法来显示释放内存

inBitmap知识点

bitmap是很消耗内存的,当bitmap不用时我们需要回收

  1. bitmap.recycle();

以上代码可以回收bitmap在堆中的内存,回收之后,bitmap不得再重新使用。但是,当我们使用listview加载多张图片时,我们都知道,listview的Item有复用的特性,必然经常复用bitmap缓存,这时inBitmap的作用尤为重要。

inBitmap变量是在Android 3.0+版本加入到系统源码当中,也就意味着inBitmap参数只有在Android 3.0+版本及以上能够正常使用,当你的app版本低于3.0的时候,只能使用bitmap.recycle()进行Bitmap的回收操作;在Android 3.0+以上根据系统版本的不同,使用inBitmap的规则也不相同,具体区分如下:

  • 4.4之前的版本inBitmap只能够重用相同大小的Bitmap内存区域。简单而言,被重用的Bitmap需要与新的Bitmap规格完全一致,否则不能重用。

  • 4.4之后的版本系统不再限制旧Bitmap与新Bitmap的大小,只要保证旧Bitmap的大小是大于等于新Bitmap大小即可。

  • 除上述规则之外,旧Bitmap必须是mutable的,这点也很好理解,如果一个Bitmap不支持修改,那么其内存自然也重用不了,代码如下:

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inMutable = true;//如果为true, 那么该bitmap缓存区可以被修改,否则不可被修改

Ok,关于inBitmap的知识点理论上也就那么多。Google为了帮助我们更好的管理Bitmap,也出了一个视频,视频地址如下:Bitmap使用视频。并且附带上了一个使用inBitmap的Demo:Bitmap的使用Demo

下面贴视频中两张图更好的帮助一下理解:

使用inBitmap之前:

此处输入图片的描述

使用inBitmap之后:

此处输入图片的描述

inBitmap的疑问

针对上述的理解,这里有一个疑问需要去确认一下:

  • inBitmap的解码模式跟新Bitmap的不同是否能够重用成功

解决这个疑问可以查看Google官方的inBitmap Demo来回答问题:

  1. /**
  2. * candidate:旧的图片,targetOptions:新的图片的Options
  3. */
  4. private fun canUseForInBitmap(candidate: Bitmap, targetOptions: BitmapFactory.Options): Boolean {
  5. return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  6. // From Android 4.4 (KitKat) onward we can re-use if the byte size of
  7. // the new bitmap is smaller than the reusable bitmap candidate
  8. // allocation byte count.
  9. val width: Int = targetOptions.outWidth / targetOptions.inSampleSize
  10. val height: Int = targetOptions.outHeight / targetOptions.inSampleSize
  11. val byteCount: Int = width * height * getBytesPerPixel(candidate.config)
  12. byteCount <= candidate.allocationByteCount
  13. } else {
  14. // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
  15. candidate.width == targetOptions.outWidth
  16. && candidate.height == targetOptions.outHeight
  17. && targetOptions.inSampleSize == 1
  18. }
  19. }
  20. /**
  21. * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
  22. */
  23. private fun getBytesPerPixel(config: Bitmap.Config): Int {
  24. return when (config) {
  25. Bitmap.Config.ARGB_8888 -> 4
  26. Bitmap.Config.RGB_565, Bitmap.Config.ARGB_4444 -> 2
  27. Bitmap.Config.ALPHA_8 -> 1
  28. else -> 1
  29. }
  30. }

这里可以看出,只要新的Bitmap的内存小于旧Bitmap的内存大小,即可进行复用的操作,那么跟解码模式没有必然的联系。

inBitmap的使用

示例代码,下面的例子主要用到了Bitmap的采样压缩,同时使用到了inBitmap内存复用。

  1. /**
  2. * 采样率压缩,这个和矩阵来实现缩放有点类似,但是有一个原则是“大图小用用采样,小图大用用矩阵”。
  3. * 也可以先用采样来压缩图片,这样内存小了,可是图的尺寸也小。如果要是用 Canvas 来绘制这张图时,再用矩阵放大
  4. * @param image
  5. * @param compressFormat
  6. * @param requestWidth 要求的宽度
  7. * @param requestHeight 要求的长度
  8. * @return
  9. */
  10. public static Bitmap compressbySample(Bitmap image, Bitmap.CompressFormat compressFormat, int requestWidth, int requestHeight){
  11. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  12. image.compress(compressFormat, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
  13. ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
  14. BitmapFactory.Options options = new BitmapFactory.Options();
  15. options.inPreferredConfig = Bitmap.Config.RGB_565;
  16. options.inPurgeable = true;
  17. options.inJustDecodeBounds = true;//只读取图片的头信息,不去解析真是的位图
  18. BitmapFactory.decodeStream(isBm,null,options);
  19. options.inSampleSize = calculateInSampleSize(options,requestWidth,requestHeight);
  20. //-------------inBitmap------------------
  21. options.inMutable = true;
  22. try{
  23. if (image != null && canUseForInBitmap(image, options)) {
  24. options.inBitmap = image;
  25. }
  26. }catch (OutOfMemoryError e){
  27. options.inBitmap = null;
  28. System.gc();
  29. }
  30. //---------------------------------------
  31. options.inJustDecodeBounds = false;//真正的解析位图
  32. isBm.reset();
  33. Bitmap compressBitmap;
  34. try{
  35. compressBitmap = BitmapFactory.decodeStream(isBm, null, options);//把ByteArrayInputStream数据生成图片
  36. }catch (OutOfMemoryError e){
  37. compressBitmap = null;
  38. System.gc();
  39. }
  40. return compressBitmap;
  41. }
  42. /**
  43. * 采样压缩比例
  44. * @param options
  45. * @param reqWidth 要求的宽度
  46. * @param reqHeight 要求的长度
  47. * @return
  48. */
  49. private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
  50. int originalWidth = options.outWidth;
  51. int originalHeight = options.outHeight;
  52. int inSampleSize = 1;
  53. if (originalHeight > reqHeight || originalWidth > reqHeight){
  54. // 计算出实际宽高和目标宽高的比率
  55. final int heightRatio = Math.round((float) originalHeight / (float) reqHeight);
  56. final int widthRatio = Math.round((float) originalWidth / (float) reqWidth);
  57. // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
  58. // 一定都会大于等于目标的宽和高。
  59. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
  60. }
  61. return inSampleSize;
  62. }
  63. //用来判断bitmap是否能够被复用。
  64. static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {
  65. if (candidate == null || !candidate.isMutable()) {
  66. return false;
  67. }
  68. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  69. // From Android 4.4 (KitKat) onward we can re-use if the byte size of
  70. // the new bitmap is smaller than the reusable bitmap candidate allocation byte count.
  71. int width = targetOptions.outWidth / targetOptions.inSampleSize;
  72. int height = targetOptions.outHeight / targetOptions.inSampleSize;
  73. int byteCount = width * height * getBytesPerPixel(targetOptions.inPreferredConfig);
  74. return byteCount <= candidate.getAllocationByteCount();
  75. }
  76. // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
  77. return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1;
  78. }
  79. /**
  80. * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
  81. */
  82. static int getBytesPerPixel(Bitmap.Config config) {
  83. if (config == Bitmap.Config.ARGB_8888) {
  84. return 4;
  85. } else if (config == Bitmap.Config.RGB_565) {
  86. return 2;
  87. } else if (config == Bitmap.Config.ARGB_4444) {
  88. return 2;
  89. } else if (config == Bitmap.Config.ALPHA_8) {
  90. return 1;
  91. }
  92. return 1;
  93. }

关于inBitmap的使用,可以根据谷歌的官方例子进行设计和开发,不过维护起来需要一定的工作量。当然在市面上成熟的图片框架中,如Glide内部也使用了inBitmap作为缓存复用的一种方式。总而言之,根据项目以及业务来选择实现方式即可,不必过分纠结。

建议阅读:BitmapFactory.Option

原文链接:关于Bitmap中的inBitmap变量的学习与使用

链接:图片加载<第六篇>:图片优化之inBitmap

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注