淺談BitmapFactory.Options

BitmapFactory.options

BitmapFactory.Options類是BitmapFactory對圖片進行解碼時使用的一個配置參數(shù)類侵蒙,其中定義了一系列的public成員變量,每個成員變量代表一個配置參數(shù)。

圖片解碼建議配置(inPreferredConfig)

  • 參數(shù)inpreferredconfig表示圖片解碼時使用的顏色模式,也就是圖片中每個像素顏色的表示方式

  • 圖片顏色:

    • 計算機表示一個顏色都需要將顏色對應(yīng)到一個顏色空間中的某個顏色值,常見的顏色空間為RGB,CMYK等.
    • JPEG格式支持RGB,CMYK,而PNG支持RGB,此外絕大數(shù)顯示器只支持RGB顏色的輸入,計算機顯示一張圖片時,如果圖片本身不是RGB顏色空間編碼,需將其轉(zhuǎn)化為RGB顏色空間的顏色后在顯示,所以非RGB顯示會有失真.
  • 顏色透明度:

    • 圖片包含顏色信息和透明信息,計算機中用一個單獨的透明通道表示(Alpha通道),JPEG格式圖片不支持透明度,PNG/GIT格式支持透明度.
  • Android顏色和透明度表示

    • Android通常用32位二進制表示一個像素顏色和透明度,即A,R,G,B四個通道,每個通道范圍為[0,0xFF]
  • inperferredConfig參數(shù)

    • BitmapFactory.Options類是BitmapFractory對圖片進行解碼時使用的配置參數(shù)類, 其中定義一系列public的成員變量(配置參數(shù)),inperferredConfig表示圖片解碼時使用的顏色模式:
    • inpreferredConfig參數(shù)有四個值:
      • ALPHA_8: 每個像素用占8位,存儲的是圖片的透明值,占1個字節(jié)
      • RGB_565:每個像素用占16位,分別為5-R,6-G,5-B通道,占2個字節(jié)
      • ARGB-4444:每個像素占16位,即每個通道用4位表示,占2個字節(jié)
      • ARGB_8888:每個像素占32位,每個通道用8位表示,占4個字節(jié)
  • 圖片解碼時,默認使用ARGB_8888模式:

    • Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
    • ARGB_4444已deprecated,在KITKAT(Android4.4)以上,會用ARGB_8888代替ARGB_4444
  • 使用inperferredConfig注意:

    • 如果inPreferredConfig不為null,解碼器會嘗試使用此參數(shù)指定的顏色模式來對圖片進行解碼酌毡,如果inPreferredConfig為null或者在解碼時無法滿足此參數(shù)指定的顏色模式,解碼器會自動根據(jù)原始圖片的特征以及當前設(shè)備的屏幕位深蕾管,選取合適的顏色模式來解碼枷踏,例如,如果圖片中包含透明度掰曾,那么對該圖片解碼時使用的配置就需要支持透明度旭蠕,默認會使用ARGB_8888來解碼。
    • 根據(jù)inperferredConfig的解析,就會發(fā)現(xiàn)如下bm1,bm2,bm3結(jié)果會一樣:
      
          InputStream stream = getAssets().open(file);
          Options op1 = new Options();
          op1.inPreferredConfig = Config.ALPHA_8;
          Bitmap bm1 = BitmapFactory.decodeStream(stream, null, op1);
          Options op2 = new Options();
          op2.inPreferredConfig = Config.RGB_565;
          Bitmap bm2 = BitmapFactory.decodeStream(stream, null, op2);
          Options op3 = new Options();
          op3.inPreferredConfig = Config.ARGB_8888;
          Bitmap bm3 = BitmapFactory.decodeStream(stream, null, op3);
      
  • 疑點: 1. 當出現(xiàn)不滿足情況時旷坦,使用的合適配置是如何選取的掏熬?

      1. 如果inPreferredConfig為null,解碼時使用的顏色模式會根據(jù)圖片源文件的類型進行選取秒梅,如果圖片文件的顏色模式為CMYK旗芬,或RGB565,則選取RGB_565捆蜀。如果是其他類型疮丛,則選取ARGB_8888辆琅。
      1. 如果inPreferredConfig指定的選項在解碼時無法滿足,并不會再根據(jù)圖片文件的類型來選取合適的選項这刷,而是直接使用ARGB_8888選項來解碼婉烟。例如,圖片源文件為RGB566編碼的BMP圖片暇屋,使用ALPHA_8選項來解碼時屬于不滿足的情況似袁,這時會選取ARGB_8888選項來解碼,而不是選取RGB565咐刨。和inPreferredConfig為null時選取的“合適的”選項并不相同昙衅。
  • 疑點: 2. 什么情況下使用什么樣的配置會出現(xiàn)不滿足的情況?

    • 所有情況下ARGB_8888配置都可以滿足
    • 所有情況下ALPHA_8配置都不滿足
    • 絕大多數(shù)情況下RGB565選項都不滿足

優(yōu)化Bitmap的內(nèi)存使用(inBitmap)

  • 在Android 2.2 (API level 8)以及之前定鸟,當垃圾回收發(fā)生時而涉,應(yīng)用的線程是會被暫停的,這會導(dǎo)致一個延遲滯后联予,并降低系統(tǒng)效率啼县。 從Android 2.3開始,添加了并發(fā)垃圾回收的機制沸久, 這意味著在一個Bitmap不再被引用之后季眷,它所占用的內(nèi)存會被立即回收

  • 在Android 2.3.3 (API level 10)以及之前, 一個Bitmap的像素級數(shù)據(jù)(pixel data)是存放在Native內(nèi)存空間中的卷胯。 這些數(shù)據(jù)與Bitmap本身所占內(nèi)存是隔離的子刮,Bitmap本身被存放在Dalvik堆中。我們無法預(yù)測在Native內(nèi)存中的像素級數(shù)據(jù)何時會被釋放窑睁,這意味著程序容易超過它的內(nèi)存限制并且崩潰挺峡。 自Android 3.0 (API Level 11)開始, 像素級數(shù)據(jù)則是與Bitmap本身一起存放在Dalvik堆中担钮。

  • 在Android 2.3.3 (API level 10) 以及更低版本上橱赠,推薦使用recycle()方法。 如果在應(yīng)用中顯示了大量的Bitmap數(shù)據(jù)裳朋,我們很可能會遇到OutOfMemoryError的錯誤病线。 recycle()方法可以使得程序更快的釋放內(nèi)存。

    Caution:只有當我們確定這個Bitmap不再需要用到的時候才應(yīng)該使用recycle()鲤嫡。在執(zhí)行recycle()方法之后送挑,如果嘗試繪制這個Bitmap, 我們將得到"Canvas: trying to use a recycled bitmap"的錯誤提示暖眼。

  • 從Android 3.0 (API Level 11)開始惕耕,引進了BitmapFactory.Options.inBitmap字段。如果這個值被設(shè)置了诫肠,decode方法會在加載內(nèi)容的時候去reuse已經(jīng)存在的bitmap. 這意味著bitmap的內(nèi)存是被reused的司澎,這樣可以提升性能, 并且減少了內(nèi)存的allocation與de-allocation.

    • reused的bitmap必須和原數(shù)據(jù)內(nèi)容大小一致, 并且是JPEG 或者 PNG 的格式 (或者是某個resource 與 stream).
    • reused的bitmap的configuration值如果有設(shè)置欺缘,則會覆蓋掉inPreferredConfig值.
    • 你應(yīng)該總是使用decode方法返回的bitmap, 因為你不可以假設(shè)reusing的bitmap是可用的(例如,大小不對).
    
        private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { 
          // inBitmap only works with mutable bitmaps, so force the decoder to 
          // return mutable bitmaps. 
          options.inMutable = true; 
          if (cache != null) { 
            // Try to find a bitmap to use for inBitmap. 
            Bitmap inBitmap = cache.getBitmapFromReusableSet(options); 
            if (inBitmap != null) { 
              // If a suitable bitmap has been found, 
              // set it as the value of inBitmap. 
              options.inBitmap = inBitmap; 
            } 
          }
        }
    
        static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { 
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 
            // From Android 4.4 (KitKat) onward we can re-use 
            // if the byte size of the new bitmap is smaller than 
            // the reusable bitmap candidate 
            // allocation byte count. 
            int width = targetOptions.outWidth / targetOptions.inSampleSize; 
            int height = targetOptions.outHeight / targetOptions.inSampleSize; 
            int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); 
            return byteCount <= candidate.getAllocationByteCount(); 
          } 
          // On earlier versions, 
          // the dimensions must match exactly and the inSampleSize must be 1 
          return candidate.getWidth() == targetOptions.outWidth 
              && candidate.getHeight() == targetOptions.outHeight 
              && targetOptions.inSampleSize == 1;
        }
    

高效加載大圖片(inJustDecodeBounds / inSmapleSize)

  • 如果設(shè)置為true,將不返回bitmap, 但是Bitmap的outWidth,outHeight等屬性將會賦值,允許調(diào)用查詢Bitmap,而不需要為Bitmap分配內(nèi)存.
  • 例如加載一張很大的位圖, 如果直接解碼會造成OOM,做法是:
    • 1.先拿到位圖的尺寸后,進行放縮后再加載位圖

      
          BitmapFactory.Options options = new BitmapFactory.Options();
          options.inJustDecodeBounds = true;
          BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
          int imageHeight = options.outHeight;
          int imageWidth = options.outWidth;
          String imageType = options.outMimeType;
      
    • 2.計算inSampleSize

      
          public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
              // Raw height and width of image
              final int height = options.outHeight;
              final int width = options.outWidth;
              int inSampleSize = 1;
          
              if (height > reqHeight || width > reqWidth) {
          
                  final int halfHeight = height / 2;
                  final int halfWidth = 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;
          }
      

      設(shè)置inSampleSize為2的冪是因為解碼器最終還是會對非2的冪的數(shù)進行向下處理挤安,獲取到最靠近2的冪的數(shù)谚殊。詳情參考inSampleSize的文檔
      * 3.放縮后再加載小位圖:

      
          public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
              int reqWidth, int reqHeight) {
      
              // First decode with inJustDecodeBounds=true to check dimensions
              final BitmapFactory.Options options = new BitmapFactory.Options();
              options.inJustDecodeBounds = true;
              BitmapFactory.decodeResource(res, resId, options);
          
              // Calculate inSampleSize
              options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
          
              // Decode bitmap with inSampleSize set
              options.inJustDecodeBounds = false;
              return BitmapFactory.decodeResource(res, resId, options);
          }
      

inPremultiplied

  • 如果設(shè)置了true(默認是true),那么返回的圖片RGB都會預(yù)乘透明通道A后的顏色
  • 系統(tǒng)View或者Canvas繪制圖片,不建議設(shè)置為fase,否則會拋出異常,這是因為系統(tǒng)會假定所有圖像都預(yù)乘A通道的已簡化繪制時間.
  • 設(shè)置inPremultiplied的同時,設(shè)置inScale會導(dǎo)致繪制的顏色不正確.

inDither

設(shè)置是否抖動處理圖片.

inMutable

如果設(shè)置為true,將返回一個mutable的bitmap,可用于修改BitmapFactory加載而來的bitmap.

  • BitmapFactory.decodeResource(Resources res, int id)獲取到的bitmap是mutable的蛤铜,而BitmapFactory.decodeFile(String path)獲取到的是immutable的
  • 可以使用Bitmap copy(Config config, boolean isMutable)獲取mutable位圖用于修改位圖pixels.

inDesity

  • 設(shè)置位圖的像素密度嫩絮,即每英寸有多少個像素
  • 如果inScale設(shè)置了,同時inDensity的值和inTargetDensity不同時围肥,這個時候圖片將縮放位inTartgetDensity指定的值.
  • 如果設(shè)置為0剿干,則BitmapFactory.decodeResource(Resources,int)BitmapFactory.decodeResource(Resources, int,BitmapFactory.Options)BitmapFactory.decodeResourceStream()inTargetDensityDisplayMetrics.densityDpi來設(shè)置穆刻,其它函數(shù)則不會對bitmap進行任何縮放置尔。

inTargetDensity:

  • 設(shè)置繪制位圖的屏幕密度,與inScale和inDesity一起使用,來對位圖進行放縮.
  • 如果設(shè)置為0, BitmapFactory.decodeResource(Resources,int), BitmapFactory.decodeResource(Resources, int, BitmapFactory.Options),BitmapFactory.decodeResourceStream()將按照DisplayMetrics的density處理.

inScreenDensity

  • 表示正在使用的實際屏幕的像素密度.純粹用于運行在兼容性代碼中的應(yīng)用程序,其中inTargetDensity實際上是看到的應(yīng)用程序的密度,而非真正的屏幕密度.
  • inDesity, inTargetDensity,inScreenDensity這三個參數(shù)主是確定是否需要對bitmap進行縮放處理氢伟,如果縮放榜轿,縮放后的W和H應(yīng)該是多少,縮放比例主要是通過:InTargetDenisity/inDensity作為縮放比例腐芍。

inScale

  • 當inScale設(shè)置為true時,且inDenstiy和inTargetDensity也不為0時,位圖將在加載時(解碼)時放縮去匹配inTargetDensity,在繪制到canvas時不會依賴圖像系統(tǒng)放縮.
  • BitmapRegionDecoder會忽略這個標記.
  • 此標記默認為true,如果需要非放縮的位圖,可以設(shè)置為false,9-patch圖片會忽略這標記而自動放縮適配.
  • 如果inPremultipled設(shè)置為false,并且圖片有A通道,設(shè)置這個標記為true,會導(dǎo)致位圖出現(xiàn)不正確的顏色.

inTargetDensity,inScale,inDesity之間的關(guān)系:

說三者之間的關(guān)系前,先談下系統(tǒng)位圖放縮規(guī)則,做個試驗(使用小米3作為測試機):將一張144*144的ic_lanucher.png(系統(tǒng)默認在xxhdpi包下)分別放置在hdpi,xhdpi,xxhdpi三個文件夾,打印出位圖的大小.


   Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.ic_launcher);
   Log.d(TAG, "size:" + bitmap.getByteCount());

   hdpi包size:331776
   xhdpi包size:186624
   xxhdpi包size:82944

我們知道一張144*144的 ic_lanucher.png所占的實際內(nèi)存為 144*144*4=82944字節(jié),那么為什么同一張圖片放在不同包下表現(xiàn)不一樣的大小?

屏幕密度與Drawable目錄有著如下的關(guān)系:

目錄 屏幕密度
drawable-ldpi 120dpi
drawable-mdpi 160dpi
drawable-hdpi 240dpi
drawable-xhdpi 320dpi
drawable-xxhdpi 480dpi

當使用decodeResuore()解碼drawable目錄下的圖片時, 會根據(jù)手機的屏幕密度,到對應(yīng)的文件夾中查找圖片,如果圖片存在于其他目錄,則會對該圖片進行放縮處理在顯示,放縮處理的規(guī)則:

scale= 設(shè)備屏幕密度/drawable目錄設(shè)定的屏幕密度

圖片內(nèi)存=int(圖片長度*scale+0.5f)* int(圖片寬度*scale)*單位像素占字節(jié)數(shù)

由于實驗使用的小米3,屏幕密度為480,則當圖片放入在hdpi時:scale= 480/240;
圖片放入xhdpi:scale=480/320;
圖片放入xxhdpi時:scale= 480/480;

說完系統(tǒng)加載位圖使用的放縮規(guī)則后,再來說說這三個標記之間的關(guān)系:

inDesity: 位圖使用的像素密度
inTargetDesity: 設(shè)備的屏幕密度
inScale: 是否需要放縮位圖

清楚這三者的含義,就可以在加載圖片時,根據(jù)圖片在不同設(shè)備上的使用,可以放縮來加載位圖:

放縮規(guī)則 scale= inTargetDensity/inDesity;


    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inScaled = true;
    options.inDensity = getBitmapDensity();
    options.inTargetDensity =Resources.getSystem().getDisplayMetrics().densityDpi ;
    Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher, options);

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猪勇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颠蕴,老刑警劉巖泣刹,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異犀被,居然都是意外死亡椅您,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門寡键,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掀泳,“玉大人,你說我怎么就攤上這事西轩≡倍妫” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵藕畔,是天一觀的道長马僻。 經(jīng)常有香客問我,道長注服,這世上最難降的妖魔是什么韭邓? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任措近,我火速辦了婚禮,結(jié)果婚禮上女淑,老公的妹妹穿的比我還像新娘瞭郑。我一直安慰自己,他們只是感情好鸭你,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布屈张。 她就那樣靜靜地躺著,像睡著了一般苇本。 火紅的嫁衣襯著肌膚如雪袜茧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天瓣窄,我揣著相機與錄音笛厦,去河邊找鬼。 笑死俺夕,一個胖子當著我的面吹牛裳凸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播劝贸,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼姨谷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了映九?” 一聲冷哼從身側(cè)響起梦湘,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎件甥,沒想到半個月后捌议,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡引有,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年瓣颅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片譬正。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡宫补,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曾我,到底是詐尸還是另有隱情粉怕,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布您单,位于F島的核電站斋荞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏虐秦。R本人自食惡果不足惜平酿,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一凤优、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜈彼,春花似錦筑辨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至还绘,卻和暖如春楚昭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拍顷。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工抚太, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昔案。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓尿贫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親踏揣。 傳聞我的和親對象是個殘疾皇子庆亡,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容