我們項目中經常會加載圖片,有時候如果加載圖片過多的話,小則導致程序很卡,重則OOM導致App掛了,今天翻譯https://developer.android.com/training/displaying-bitmaps/index.html,學習Google高效加載大圖片的方法。
圖片有各種形狀和大小,但在大多數情況下,這些圖片都會大於我們程序所需要的大小。比如說系統圖片庫裡展示的圖片大都是用手機攝像頭拍出來的,這些圖片的分辨率會比我們手機屏幕的分辨率高得多。大家應該知道,我們編寫的應用程序都是有一定內存限制的,程序占用了過高的內存就容易出現OOM(OutOfMemory)異常。我們可以通過下面的代碼看出每個應用程序最高可用內存是多少。
[java] view plaincopyprint?
- int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024/1024);
現在大部分手機都是32M,既然知道了每個app分配的內存,所以就要計算好加載多少圖片而不導致出現OOM,所以要計算每張圖片所占用的內存是多少。
Android中計算一張圖片所占內存大小方法:圖片長*寬*所占像素字節數,而像素字節數Android中也就四種,
1:ALPHA_8 占1個字節
2:ARGB_4444 占2個字節
3:ARGB_8888 占4個字節
4:RGB_565 占2個字節
注:ARGB指的是一種色彩模式,裡面A代表Alpha,R表示red,G表示green,B表示blue,其實所有的可見色都是紅綠藍組成的,所以紅綠藍又稱為三原色。 A R G B
透明度 紅色 綠色 藍色
而這些字節數是可以通過bitmap對象去設置的,bitmap.setConfig(Bitmap.Config.ARGB_4444);如果這是個定值,那麼要改變一個張圖片的大小,就只能改寬或者高了,如果只改高,寬不變的話,就會造成圖片變形,因此一般都是一起改動,所以圖片要縮放,而縮放時根據屏幕的寬和高來縮放的,因為Android設備很多,每個屏幕的寬和高也不一樣,這樣就能達到加載大圖片避免OOM。
[java] view plaincopyprint?
- BitmapFactory這個類提供了多個解析方法(decodeByteArray, decodeFile, decodeResource等)用於創建Bitmap對象,我們應該根據圖片的來源選擇合適的方法。比如SD卡中的圖片可以使用decodeFile方法,網絡上的圖片可以使用decodeStream方法,資源文件中的圖片可以使用decodeResource方法。這些方法會嘗試為已經構建的bitmap分配內存,這時就會很容易導致OOM出現。為此每一種解析方法都提供了一個可選的BitmapFactory.Options參數,將這個參數的inJustDecodeBounds屬性設置為true就可以讓解析方法禁止為bitmap分配內存,返回值也不再是一個Bitmap對象,而是null。雖然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值。這個技巧讓我們可以在加載圖片之前就獲取到圖片的長寬值和MIME類型,從而根據情況對圖片進行壓縮。如下代碼所示: [java] view plaincopyprint?
- 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; 比如一張480*800的圖片直接加載到內存中,如果圖片很多那就很容易造成OOM,那就必須壓縮,比如按1/8進行壓縮,壓縮後得到的60*100,如果從服務器獲取的圖片規格不一樣,那壓縮後現在在ImageView上肯定不一樣,因為ImageView寬和高肯定是設置成wrap_content。
現在寫個Demo縮放圖片代碼如下
[java] view plaincopyprint?
- package com.jackie.bitmapdemo;
-
- import android.app.Activity;
- import android.content.res.Resources;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Bundle;
- import android.widget.ImageView;
-
- public class MainActivity extends Activity {
- private ImageView imageView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- imageView = (ImageView) findViewById(R.id.iv);
- Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.b, 60, 100);
- iv.setImageBitmap(bitmap);
- }
-
- public static int calculateInSampleSize(BitmapFactory.Options options,
- int reqWidth, int reqHeight) {
- // 源圖片的高度和寬度
- final int height = options.outHeight;
- final int width = options.outWidth;
- int inSampleSize = 1;
- if (height > reqHeight || width > reqWidth) {
- // 計算出實際寬高和目標寬高的比率
- final int heightRatio = Math.round((float) height / (float) reqHeight);
- final int widthRatio = Math.round((float) width / (float) reqWidth);
- //計算縮放比例
- inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
- }
- return inSampleSize;
- }
- public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
- int reqWidth, int reqHeight) {
- // 第一次解析將inJustDecodeBounds設置為true,來獲取圖片大小
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeResource(res, resId, options);
- // 調用上面定義的方法計算inSampleSize值
- options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
- // 使用獲取到的inSampleSize值再次解析圖片
- options.inJustDecodeBounds = false;
- return BitmapFactory.decodeResource(res, resId, options);
- }
-
- } 計算縮放比例一般有2種做法:
1:根據屏幕寬和高來縮放圖片
2:根據要縮放後的寬和高來縮放,上面的例子就是根據這種。