1. 程式人生 > >BitmapFactory解析與Bitmap的記憶體優化

BitmapFactory解析與Bitmap的記憶體優化

1、BitmapFactory解析Bitmap的原理

BitmapFactory提供的解析Bitmap的靜態工廠方法有以下五種:

Bitmap decodeFile(...)
Bitmap decodeResource(...)
Bitmap decodeByteArray(...)
Bitmap decodeStream(...)
Bitmap decodeFileDescriptor(...)

其中常用的三個:decodeFile、decodeResource、decodeStream。decodeFile和decodeResource其實最終都是呼叫decodeStream方法來解析Bitmap,decodeStream的內部則是呼叫兩個native方法解析Bitmap的:

nativeDecodeAsset()
nativeDecodeStream()

這兩個native方法只是對應decodeFile和decodeResource、decodeStream來解析的,像decodeByteArray、decodeFileDescriptor也有專門的native方法負責解析Bitmap。

接下來就是看看這兩個方法在解析Bitmap時究竟有什麼區別decodeFile、decodeResource,檢視後發現它們呼叫路徑如下:

decodeFile->decodeStreamdecodeResource->decodeResourceStream->decodeStream

decodeResource在解析時多呼叫了一個decodeResourceStream方法,而這個decodeResourceStream方法程式碼如下:

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {
        if (opts == null) {
            opts = new Options();
        }
        if (opts.inDensity == 0
&& value != null) { final int density = value.density; if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } else if (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; } } if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }

它主要是對Options進行處理了,在得到opts.inDensity屬性的前提下,如果我們沒有對該屬性設定值,那麼將opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;賦定這個預設的Density值,這個預設值為160,為標準的dpi比例,即在Density=160的裝置上1dp=1px,這個方法中還有這麼一行

opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

opts.inTargetDensity進行了賦值,該值為當前裝置的densityDpi值,所以說在decodeResourceStream方法中主要做了兩件事:

1、對opts.inDensity賦值,沒有則賦預設值1602、對opts.inTargetDensity賦值,沒有則賦當前裝置的densityDpi值

之後重點來了,之後引數將傳入decodeStream方法,該方法中在呼叫native方法進行解析Bitmap後會呼叫這個方法setDensityFromOptions(bm, opts);

private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
        if (outputBitmap == null || opts == null) return;
        final int density = opts.inDensity;
        if (density != 0) {
            outputBitmap.setDensity(density);
            final int targetDensity = opts.inTargetDensity;
            if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
                return;
            }
            byte[] np = outputBitmap.getNinePatchChunk();
            final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
            if (opts.inScaled || isNinePatch) {
                outputBitmap.setDensity(targetDensity);
            }
        } else if (opts.inBitmap != null) {
            // bitmap was reused, ensure density is reset
            outputBitmap.setDensity(Bitmap.getDefaultDensity());
        }
    }

該方法主要就是把剛剛賦值過的兩個屬性inDensity和inTargetDensity給Bitmap進行賦值,不過並不是直接賦給Bitmap就完了,中間有個判斷,當inDensity的值與inTargetDensity或與裝置的螢幕Density不相等時,則將應用inTargetDensity的值,如果相等則應用inDensity的值。

所以總結來說,setDensityFromOptions方法就是把inTargetDensity的值賦給Bitmap,不過前提是opts.inScaled = true;

進過上面的分析,可以得出這樣一個結論:

在不配置Options的情況下:

1、decodeFile、decodeStream在解析時不會對Bitmap進行一系列的螢幕適配,解析出來的將是原始大小的圖

2、decodeResource在解析時會對Bitmap根據當前裝置螢幕畫素密度densityDpi的值進行縮放適配操作,使得解析出來的Bitmap與當前裝置的解析度匹配,達到一個最佳的顯示效果,並且Bitmap的大小將比原始的大

1.1、關於Density、解析度、-hdpi等res目錄之間的關係

DensityDpi 解析度 res Density
160dpi 320×533 mdpi 1
240dpi 480×800 hdpi 1.5
320dpi 720×1280 xhdpi 2
480dpi 1080×1920 xxhdpi 3
560dpi 1440×2560 xxxhdpi 3.5

dp與px的換算公式為:

px = dp * Density

1.2、DisplayMetrics::densityDpi與density的區別

getResources().getDisplayMetrics().densityDpi——表示螢幕的畫素密度

getResources().getDisplayMetrics().density——1dp等於多少個畫素(px)

舉個栗子:在螢幕密度為160的裝置下,1dp=1px。在螢幕密度為320的裝置下,1dp=2px。所以這就為什麼在安卓中佈局建議使用dp為單位,因為可以根據當前裝置的螢幕密度動態的調整進行適配

2、Bitmap的優化策略

2.1、BitmapFactory.Options的屬性解析

BitmapFactory.Options中有以下屬性:

inBitmap——在解析Bitmap時重用該Bitmap,不過必須等大的Bitmap而且inMutable須為true
inMutable——配置Bitmap是否可以更改,比如:在Bitmap上隔幾個畫素加一條線段
inJustDecodeBounds——為true僅返回Bitmap的寬高等屬性
inSampleSize——須>=1,表示Bitmap的壓縮比例,如:inSampleSize=4,將返回一個是原始圖的1/16大小的Bitmap
inPreferredConfig——Bitmap.Config.ARGB_8888等
inDither——是否抖動,預設為false
inPremultiplied——預設為true,一般不改變它的值
inDensity——Bitmap的畫素密度
inTargetDensity——Bitmap最終的畫素密度
inScreenDensity——當前螢幕的畫素密度
inScaled——是否支援縮放,預設為true,當設定了這個,Bitmap將會以inTargetDensity的值進行縮放
inPurgeable——當儲存Pixel的記憶體空間在系統記憶體不足時是否可以被回收
inInputShareable——inPurgeable為true情況下才生效,是否可以共享一個InputStream
inPreferQualityOverSpeed——為true則優先保證Bitmap質量其次是解碼速度
outWidth——返回的Bitmap的寬
outHeight——返回的Bitmap的高
inTempStorage——解碼時的臨時空間,建議16*1024

2.2、優化策略

1、BitmapConfig的配置

2、使用decodeFile、decodeResource、decodeStream進行解析Bitmap時,配置inDensity和inTargetDensity,兩者應該相等,值可以等於螢幕畫素密度*0.75f

3、使用inJustDecodeBounds預判斷Bitmap的大小及使用inSampleSize進行壓縮

4、對Density>240的裝置進行Bitmap的適配(縮放Density)

5、2.3版本inNativeAlloc的使用

6、4.4以下版本inPurgeable、inInputShareable的使用

7、Bitmap的回收

針對上面方案,把Bitmap解碼的程式碼封裝成了一個工具類,如下:

  1. package com.myframe.utils;
  2. import android.content.Context;
  3. import android.graphics.Bitmap;
  4. import android.graphics.BitmapFactory;
  5. import android.os.Build;
  6. import android.util.TypedValue;
  7. import java.io.FileInputStream;
  8. import java.io.FileNotFoundException;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.lang.reflect.Field;
  12. /**
  13. * Bitmap解碼轉換工具
  14. */
  15. publicclassBitmapDecodeUtil{
  16. privatestaticfinalint DEFAULT_DENSITY =240;
  17. privatestaticfinalfloat SCALE_FACTOR =0.75f;
  18. privatestaticfinalBitmap.Config DEFAULT_BITMAP_CONFIG =Bitmap.Config.RGB_565;
  19. /**
  20. * 獲取優化的BitmapFactory.Options
  21. * @param context
  22. * @return
  23. */
  24. privatestaticBitmapFactory.Options getBitmapOptions(Context context){
  25. BitmapFactory.Options options =newBitmapFactory.Options();
  26. options.inScaled =true;
  27. options.inPreferredConfig = DEFAULT_BITMAP_CONFIG;
  28. options.inPurgeable =true;
  29. options.inInputShareable =true;
  30. options.inJustDecodeBounds =false;
  31. if(Build.VERSION.SDK_INT <=Build.VERSION_CODES.GINGERBREAD_MR1){
  32. Field field =null;
  33. try{
  34. field =BitmapFactory.Options.class.getDeclaredField("inNativeAlloc");
  35. field.setAccessible(true);
  36. field.setBoolean(options,true);
  37. }catch(NoSuchFieldException e){
  38. e.printStackTrace();
  39. }catch(IllegalAccessException e){
  40. e.printStackTrace();
  41. }
  42. }
  43. int displayDensityDpi = context.getResources().getDisplayMetrics().densityDpi;
  44. float displayDensity = context.getResources().getDisplayMetrics().density;
  45. if(displayDensityDpi > DEFAULT_DENSITY && displayDensity >1.5f){
  46. int density =(int)(displayDensityDpi * SCALE_FACTOR);
  47. options.inDensity = density;
  48. options.inTargetDensity = density;
  49. }
  50. return options;
  51. }
  52. /**
  53. * 通過資源ID解碼Bitmap
  54. * @param context
  55. * @param resId
  56. * @return
  57. */
  58. publicstaticBitmap decodeBitmap(Context context,int resId){
  59. checkParam(context);
  60. returnBitmapFactory.decodeResource(context.getResources(), resId, getBitmapOptions(context));
  61. }
  62. /**
  63. * 通過資源路徑解碼Bitmap
  64. * @param context
  65. * @param pathName
  66. * @return
  67. */
  68. publicstaticBitmap decodeBitmap(Context context,String pathName){
  69. checkParam(context);
  70. returnBitmapFactory.decodeFile(pathName, getBitmapOptions(context));
  71. }
  72. /**
  73. * 通過資源輸入流解碼Bitmap
  74. * @param context
  75. * @param is
  76. * @return
  77. */
  78. publicstaticBitmap decodeBitmap(Context context,InputStream is){
  79. checkParam(context);
  80. checkParam(is);
  81. returnBitmapFactory.decodeStream(is,null, getBitmapOptions(context));
  82. }
  83. /**
  84. * 通過資源ID獲取壓縮Bitmap
  85. * @param context
  86. * @param resId
  87. * @param maxWidth
  88. * @param maxHeight
  89. * @return
  90. */
  91. publicstaticBitmap compressBitmap(Context context,int resId,int maxWidth,int maxHeight){
  92. checkParam(context);
  93. finalTypedValue value =newTypedValue();
  94. InputStream is =null;
  95. try{
  96. is = context.getResources().openRawResource(resId, value);
  97. return compressBitmap(context, is, maxWidth, maxHeight);
  98. }catch(Exception e){
  99. e.printStackTrace();
  100. }finally{
  101. if(is !=null){
  102. try{
  103. is.close();
  104. }catch(IOException e){
  105. e.printStackTrace();
  106. }
  107. }
  108. }
  109. returnnull;
  110. }
  111. /**
  112. * 通過資源路徑獲取壓縮Bitmap
  113. * @param context
  114. * @param pathName
  115. * @param maxWidth
  116. * @param maxHeight
  117. * @return
  118. */
  119. publicstaticBitmap compressBitmap(Context context,String pathName,int