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)不滿足情況時旷坦,使用的合適配置是如何選取的掏熬?
- 如果inPreferredConfig為null,解碼時使用的顏色模式會根據(jù)圖片源文件的類型進行選取秒梅,如果圖片文件的顏色模式為CMYK旗芬,或RGB565,則選取RGB_565捆蜀。如果是其他類型疮丛,則選取ARGB_8888辆琅。
- 如果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()
將inTargetDensity
用DisplayMetrics.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);