【Android開發經驗】Bitmap高效顯示系列——如何有效載入大尺寸Bitmap,androidbitmap
轉載請註明出處:http://blog.csdn.net/zhaokaiqiang1992
Bitmap的處理在Android開發中一直是一個大問題,因為稍不注意,Bitmap就能夠吃掉我們的所有記憶體,然後崩潰退出!但是,只要我們掌握了Bitmap的一些常見處理技巧,就可以有效避免這個問題,從此不再懼怕Bitmap。
圖片有不同的形狀與大小。在大多數情況下它們的實際大小都比需要呈現出來的要大很多。例如,系統的Gallery程式會顯示那些你使用裝置camera拍攝的圖片,但是那些圖片的解析度通常都比你的裝置螢幕解析度要高很多。
考慮到程式是在有限的記憶體下工作,理想情況是你只需要在記憶體中載入一個低解析度的版本即可。這個低解析度的版本應該是與你的UI大小所匹配的,這樣才便於顯示。一個高解析度的圖片不會提供任何可見的好處,卻會佔用寶貴的的記憶體資源,並且會在快速滑動圖片時導致附加的效率問題。
這一課會介紹如何通過載入一個縮小版本的圖片到記憶體中去載入一個大的bitmaps,從而避免超出程式的記憶體限制。
1.讀取位元影像的尺寸與類型
BitmapFactory 類提供了一些decode的方法 (decodeByteArray(), decodeFile(), decodeResource(), etc.) 用來從不同的資源中建立一個Bitmap. 根據你的圖片資料來源來選擇合適的decode方法. 那些方法在構造位元影像的時候會嘗試分配記憶體,因此會容易導致OutOfMemory的異常。每一種decode方法都提供了通過 B itmapFactory.Options 來設定一些附加的標記來指定decode的選項。設定 inJustDecodeBounds 屬性為true可以在decoding的時候避免記憶體的分配,它會返回一個null的bitmap,但是 outWidth, outHeight 與 outMimeType 還是可以擷取。這個技術可以允許你在構造bitmap之前優先讀圖片的尺寸與類型。
通過下面的方式就可以在不載入記憶體的基礎之上,擷取到Bitmap的寬高和類型等資訊
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;
為了避免java.lang.OutOfMemory 的異常,我們需要在真正decode圖片之前檢查它的尺寸,除非你確定這個資料來源提供了準確無誤的圖片且不會導致佔用過多的記憶體。
2.載入一個按比例縮小的版本到記憶體中
通過上面的步驟我們已經知道了圖片的尺寸,那些資料可以用來決定是應該載入整個圖片到記憶體中還是載入一個縮小的版本。有下面一些因素需要考慮:
☞評估載入完整圖片所需要耗費的記憶體。
☞程式在載入這張圖片時會涉及到其他記憶體需求。
☞呈現這張圖片的組件的尺寸大小。
☞螢幕大小與當前裝置的螢幕密度。
例如,如果把一個原圖是1024*768 pixel的圖片顯示到ImageView為128*96 pixel的縮圖就沒有必要把整張圖片都載入到記憶體中。
為了告訴decoder去載入一個低版本的圖片到記憶體,需要在你的BitmapFactory.Options 中設定 inSampleSize 為 true 。For example, 一個解析度為2048x1536 的圖片,如果設定 inSampleSize 為4,那麼會產出一個大概為512x384的bitmap。載入這張小的圖片僅僅使用大概0.75MB,如果是載入全圖那麼大概要花費12MB(前提都是bitmap的配置是 ARGB_8888). 下面有一段根據靶心圖表片大小來計算Sample圖片大小的Sample Code:
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;}
設定inSampleSize為2的冪是因為decoder最終還是會對非2的冪的數進行向下處理,擷取到最靠近2的冪的數。
為了使用這個方法,首先需要設定 inJustDecodeBounds 為 true, 把options的值傳遞過來,然後使用 inSampleSize 的值並設定 inJustDecodeBounds 為 false 來重新Decode一遍。
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);}
使用上面這個方法可以簡單的載入一個任意大小的圖片並顯示為100*100 pixel的縮圖形式。像下面示範的一樣:
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
我以一張1920*1200的圖為例,向你展示這樣做之後產生的效果。
下面是測試代碼:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView img = (ImageView) findViewById(R.id.img); long startTime = System.currentTimeMillis();// img.setImageBitmap(BitmapLoadUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.mylove, 100, 100)); img.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.mylove)); Log.d("TAG", "alloca time = " + (System.currentTimeMillis() - startTime)); }
我們先看一下載入原圖消耗的時間和記憶體,記憶體配置190ms,記憶體佔用44.92M,因為我的Smartisan1記憶體充足,所以沒問題,如果是比較老的機型,可能就直接崩掉了!
下面我們再看使用小尺寸的運行結果,記憶體配置花費89ms,記憶體佔用10.39M,相比之前記憶體少佔用34.53M,所以說使用小尺寸的Bitmap時非常有必要的!