Bitmap基本概念及在Android4.4系統上使用BitmapFactory的注意事項

來源:互聯網
上載者:User

標籤:

本文首先總結一下Bitmap的相關概念,然後通過一個實際的問題來分析設定BitmapFactory.options的注意事項,以減少不必要的記憶體佔用率,避免發生OOM。

一、 Bitmap的使用trick

盡量不要使用setImageBitmap或setImageResource 或BitmapFactory.decodeResource來設定一張大圖, 因為這些函數在完成decode後,最終都是通過java層的createBitmap來完成的, 需要消耗更多記憶體。因此,改用先通過BitmapFactory.decodeStream方法,建立出一個bitmap,再將其設為ImageView的 source,decodeStream最大的秘密在於其直接調用 JNI >> nativeDecodeAsset() 來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間。  

如果在讀取時加片的Config參數,可以更有效減少載入的記憶體,從而有效阻止拋出out of Memory異常.另外,decodeStream直接拿的圖片來讀取位元組碼了,不會根據機器的各種解析度來自動適應,使用了decodeStream之後,需要在hdpi和mdpi,ldpi中配置相應的圖片資源, 否則在不同解析度機器上都是同樣大小(像素點數量),顯示出來的大小就不對了。    

BitmapFactory.Options.inPreferredConfig 

     * ALPHA_8:數字為8,圖形參數應該由一個位元組來表示,應該是一種8位的位元影像 
     * ARGB_4444:4+4+4+4=16,圖形的參數應該由兩個位元組來表示,應該是一種16位的位元影像. 
     * ARGB_8888:8+8+8+8=32,圖形的參數應該由四個位元組來表示,應該是一種32位的位元影像. 
     * RGB_565:5+6+5=16,圖形的參數應該由兩個位元組來表示,應該是一種16位的位元影像. 
     *  
     * ALPHA_8,ARGB_4444,ARGB_8888都是透明的位元影像,也就是所字母A代表透明。 
     * ARGB_4444:意味著有四個參數,即A,R,G,B,每一個參數由4bit表示. 
     * ARGB_8888:意味著有四個參數,即A,R,G,B,每一個參數由8bit來表示. 
     * RGB_565:意味著有三個參數,R,G,B,三個參數分別佔5bit,6bit,5bit. 
     *  
     *  
     * BitmapFactory.Options.inPurgeable; 
     *  
     * 如果 inPurgeable 設為True的話表示使用BitmapFactory建立的Bitmap 
     * 用於儲存Pixel的記憶體空間在系統記憶體不足時可以被回收, 
     * 在應用需要再次訪問Bitmap的Pixel時(如繪製Bitmap或是調用getPixel), 
     * 系統會再次調用BitmapFactory decoder重建Bitmap的Pixel數組。  
     * 為了能夠重新解碼映像,bitmap要能夠訪問儲存Bitmap的未經處理資料。 
     *  
     * 在inPurgeable為false時表示建立的Bitmap的Pixel記憶體空間不能被回收, 
     * 這樣BitmapFactory在不停decodeByteArray建立新的Bitmap對象, 
     * 不同裝置的記憶體不同,因此能夠同時建立的Bitmap個數可能有所不同, 
     * 200個bitmap足以使大部分的裝置重新OutOfMemory錯誤。 
     * 當isPurgable設為true時,系統中記憶體不足時, 
     * 可以回收部分Bitmap佔據的記憶體空間,這時一般不會出現OutOfMemory 錯誤。 
下面給出一段讀取Bitmap的代碼:

 

[java] view plaincopy 
  1. public Bitmap readBitmap(Context context, int resId) {    
  2.        BitmapFactory.Options opts = new BitmapFactory.Options();    
  3.        opts.inPreferredConfig = Config.RGB_565;    
  4.        opts.inPurgeable = true;    
  5.        opts.inInputShareable = true;    
  6.        InputStream is = context.getResources().openRawResource(resId);    
  7.        return BitmapFactory.decodeStream(is, null, opts);    
  8.    }   

二、在Android4.4系統上使用BitmapFactory.options的注意事項
前段時間將手機的Android系統升級到4.4之後,發現之前開發的App運行起來非常的卡,嚴重影響了使用者體驗。後來發現跟Bitmap.decodeByteArray的底層實現有關。本文將對問題原因進行總結,希望大家寫代碼時能留意一下,因為這種問題一旦遇到,要花很多時間才能發現原因。

 

我們的代碼能根據螢幕的密度對圖片進行縮放,因此我們使用最大的圖片資源,這樣的話對於任何的手機螢幕,都會對映像進行壓縮,不會造成視覺上的問題。圖片解碼前需要對BitmapFactory.Options進行設定,部分代碼如下:

 

[java] view plaincopy 
  1. BitmapFactory.Options options = new BitmapFactory.Options();  
  2. DisplayMetrics displayMetrics = context.getResources.getDisplayMetrics();  
  3. ......  
  4. options.inTargetDensity = displayMetrics.densityDpi;  
  5. options.inScaled = true;  
  6. //getBitmapDensity()用於設定圖片將要被顯示的密度。  
  7. options.inDensity = getBitmapDensity();  
  8. ......  
  9. Bitmap bitmap = getBitmapFromPath(loadPath, options);  

options.inTargetDensity表示的是目標Bitmap即將被畫到螢幕上的像素密度(每英寸有多少個像素)。這個屬性往往會和options.inDensity和options.inScaled一起來覺得目標bitmap是否需要進行縮放。若果這個值為0,則BitmapFactory.decodeResource(Resources, int)和BitmapFactory.decodeResource(Resources, int, android.graphics.BitmapFactory.Options)decodeResourceStream(Resources, TypedValue, InputStream, Rect, BitmapFactory.Options) 將inTargetDensity用DisplayMetrics.densityDpi來設定,其它函數則不會對bitmap進行任何縮放。
options.inDensity表示的是bitmap所使用的像素密度。如果這個值和options.inTargetDensity不一致,則會對映像進行縮放。 如果被設定成0,則 decodeResource(Resources, int), decodeResource(Resources, int, android.graphics.BitmapFactory.Options), 和decodeResourceStream(Resources, TypedValue, InputStream, Rect, BitmapFactory.Options)將用螢幕密度值來設定這個參數,其它函數將不進行縮放。

 

圖片的縮放倍數是根據inTargetDensity/inDensity來計算得到的。
  我們使用的圖片是640 * 1136,編碼格式ARGB_8888,則大小為 640*1136*4=291K。手機螢幕密度為480,則options.inTargtetDensity為480;inDensity被設定成160. 安照以上的設定,bitmap的大小將被放大9倍,圖片編碼後的大小應為640*1136*4=26M。我們的App中總共載入了3張這樣的圖片,故運行起來非常的卡。
 但為何Android4.4之前的版本沒有這樣的問題,為此我們分析了Bitmap.decodeByteArray()的源碼。

 

[java] view plaincopy 
  1. /** 
  2.   * Decode an immutable bitmap from the specified byte array. 
  3.   * 
  4.   * @param data byte array of compressed image data 
  5.   * @param offset offset into imageData for where the decoder should begin 
  6.   *               parsing. 
  7.   * @param length the number of bytes, beginning at offset, to parse 
  8.   * @param opts null-ok; Options that control downsampling and whether the 
  9.   *             image should be completely decoded, or just is size returned. 
  10.   * @return The decoded bitmap, or null if the image data could not be 
  11.   *         decoded, or, if opts is non-null, if opts requested only the 
  12.   *         size be returned (in opts.outWidth and opts.outHeight) 
  13.   */  
  14.  public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {  
  15.      if ((offset | length) < 0 || data.length < offset + length) {  
  16.          throw new ArrayIndexOutOfBoundsException();  
  17.      }  
  18.   
  19.      Bitmap bm;  
  20.   
  21.      Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");  
  22.      try {  
  23.          bm = nativeDecodeByteArray(data, offset, length, opts);  
  24.   
  25.          if (bm == null && opts != null && opts.inBitmap != null) {  
  26.              throw new IllegalArgumentException("Problem decoding into existing bitmap");  
  27.          }  
  28.          setDensityFromOptions(bm, opts);  
  29.      } finally {  
  30.          Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);  
  31.      }  
  32.   
  33.      return bm;  
  34.  }  


我們發現,該函數會調用本地函數 nativeDecodeByteArray(byte[] data, int offset, int length, Options opts)來解析圖片。
android4.4以前的BitmapFactory.cpp中nativeDecodeByteArray調用doDecode函數時不會根據density進行縮放處理(沒有查到所有的4.4以前的所有代碼,以4.2為例):

 

willscale這個參數決定了它是否能被縮放,由於沒有傳入scale值到doDecode中,scale一直使用預設值1.0f,所以willScale根據預設值計算將始終為false,即bitmap不會被縮放。

android4.4平台nativeDecodeByteArray對doDecode的調用方式沒有改變,但改變了doDecode函數的實現,特別是對willScale的計算方式進行了修改

其中全域變數gOptions_scaledFieldID為java檔案中BitmapFactory.Options的inScale變數在native層的id,


 

因為scale = (float) targetDensity / density;所以縮放倍速由inTargetDensity和inDensity兩個值確定。

可知在native層,當scale不等於1.0時會對圖片進行縮放,長寬縮放方式如下:

 



好了,就寫到這兒,大家可以查查以前寫的代碼,如果有根據螢幕密度載入Bitmap的部分,請將App在4.4的系統上跑跑看,觀察記憶體佔用的情況。解決上面的問題辦法其實也很簡單,大家可以想想看。

Bitmap基本概念及在Android4.4系統上使用BitmapFactory的注意事項

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.