1. 程式人生 > >Android Bitmap圖片處理,防止記憶體溢位

Android Bitmap圖片處理,防止記憶體溢位

在android 中載入一張圖片,如果圖片過大就有可能會出現記憶體溢位,特別是在載入資料過多的時候,像ListView 和GridView等重複列表中,因此在處理Android圖片防止記憶體溢位就顯得特別的重要,也是很多面試中經常問到的問題,面試官通常都會讓你談談如何就行記憶體優化,那麼圖片載入優化就是一個非常重要的內容。首先我們來看一張圖片載入到記憶體中所佔據的記憶體大小的計算方法。

*在載入圖片是需要的記憶體是怎麼計算的?

一張圖片佔用記憶體=圖片長度圖片寬度單位畫素佔用的位元組數。

其中單位畫素佔用的位元組數是有圖片的引數決定的,在android中通過BitmapFactory.Options的inPreferredConfig變數決定的。inPreferredConfig為Bitmap.Config型別:
如下:

Bitmap.Config
1. ALPHA_8

Each pixel is stored as a single translucency (alpha) channel.
This is very useful to efficiently store masks for instance. No color information is stored. With this configuration, each pixel requires 1 byte of memory.
此時圖片只有alpha值,沒有RGB值,一個畫素佔用一個位元組

  1. ARGB_4444

This field is deprecated. Because of the poor quality of this configuration, it is advised to use ARGB_8888instead.
這種格式的圖片,看起來質量太差,已經不推薦使用。
Each pixel is stored on 2 bytes. The three RGB color channels and the alpha channel (translucency) are stored with a 4 bits precision (16 possible values.) This configuration is mostly useful if the application needs to store translucency information but also needs to save memory. It is recommended to use ARGB_8888 instead of this configuration.
一個畫素佔用2個位元組,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各佔4個bites,共16bites,即2個位元組

  1. ARGB_8888

Each pixel is stored on 4 bytes. Each channel (RGB and alpha for translucency) is stored with 8 bits of precision (256 possible values.) This configuration is very flexible and offers the best quality. It should be used whenever possible
一個畫素佔用4個位元組,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各佔8個bites,共32bites,即4個位元組
這是一種高質量的圖片格式,電腦上普通採用的格式。它也是Android手機上一個BitMap

  1. RGB_565

Each pixel is stored on 2 bytes and only the RGB channels are encoded: red is stored with 5 bits of precision (32 possible values), green is stored with 6 bits of precision (64 possible values) and blue is stored with 5 bits of precision. This configuration can produce slight visual artifacts depending on the configuration of the source. For instance, without dithering, the result might show a greenish tint. To get better results dithering should be applied. This configuration may be useful when using opaque bitmaps that do not require high color fidelity.
一個畫素佔用2個位元組,沒有alpha(A)值,即不支援透明和半透明,Red(R)值佔5個bites ,Green(G)值佔6個bites ,Blue(B)值佔5個bites,共16bites,即2個位元組.對於沒有透明和半透明顏色的圖片來說,該格式的圖片能夠達到比較的呈現效果,相對於ARGB_8888來說也能減少一半的記憶體開銷。因此它是一個不錯的選擇。另外我們通過android.content.res.Resources來取得一個張圖片時,它也是以該格式來構建BitMap的
從Android4.0開始,該選項無效。即使設定為該值,系統任然會採用 ARGB_8888來構造

Bitmap 原理:
每一張圖片顯示在螢幕上之前都必須先加入記憶體處理,然後輸出到螢幕上。那麼我們又需要顯示這個圖片,又不能在完全確認它加入記憶體是安全(不會outOfMemory)的的時候怎麼辦呢,android提供了一個方法,就是讀取圖片的資訊時不讓其加入記憶體,我們讀取圖片資訊後進行一些尺寸上的壓縮處理後再次加入記憶體顯示就安全了。(為了避免java.lang.OutOfMemory 的異常,我們需要在真正解析圖片之前檢查它的尺寸(除非你能確定這個資料來源提供了準確無誤的圖片且不會導致佔用過多的記憶體)。)

為了避免記憶體洩露,因此在讀取圖片的時候就要進行壓縮,通常的做法就是先獲取圖片的尺寸,然後再按照尺寸和螢幕尺寸比例進行壓縮:如下:

BitmapFactory.Options optins=new BitmapFactory.Options();  
options.inJustDecodeBounds=true; //表示不讓其加入記憶體 只是獲取圖片的一些型別和寬高資訊
BitmapFactory.decodeResources(getResources(),R.id.image,options);  //解析圖片資源的來源
int imgHeight=options.outHeight;  //獲得圖片高度
int imgWidth=options.outWidth;    //獲得圖片寬度
String imageType=options.outMimeType;  //圖片型別

接下來就來載入一個尺寸縮小的版本到記憶體中:

為了告訴解碼器載入一個縮小版本的圖片到記憶體中,需要在BitmapFactory.options中設定inSampleSize的值。例如,一個解析度為2048*1536的圖片,如果設定inSampleSize=4,那麼會產出一個大約512*384大小的bitmap,載入這張圖片需要的記憶體大概為0.75M(512*384*4/1024/1024),如果載入原圖,大約會要12M記憶體(2048*1536*4/1024/1024)(前提是Bitmap的配置是ARGB_8888),下面貼出程式碼: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;          
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

注: 設定inSampleSize為2的冪是因為解碼器最終還是會對非2的冪的資料進行向下處理,獲取到最靠近2的冪的數,可以參考inSampleSize的文件

接下來就實戰一下,首先建立一個專案,LoadImage: Activity程式碼如下:

public  int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        options.inJustDecodeBounds = true;
        options.inPreferredConfig= Bitmap.Config.ARGB_8888;
        BitmapFactory.decodeResource(getResources(),R.drawable.abc,options);
        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;
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

這個程式碼很簡單,上面基本已經講過,就是按照我們控制元件要顯示的寬高來計算壓縮的比例,得到inSampleSize;

接下來的程式碼更簡單:

private Bitmap loadImage() {
        BitmapFactory.Options options = new BitmapFactory.Options();
            int j = calculateInSampleSize(options, 200, 250);
            options.inSampleSize = j;
            options.inJustDecodeBounds = false;
            int outWidth = options.outWidth;
            int outHeight = options.outHeight;
            Log.d("MainActivity", "outH:" + outHeight + ":outW:" + outWidth);
        return BitmapFactory.decodeResource(getResources(), R.drawable.abc, options);
    }

這個最關鍵的一步就是 options.inJustDecodeBounds = false; 表示讓圖片真正加入記憶體了,但這已經按比例壓縮了,所以不用擔心記憶體溢位。

最後我們設定顯示就行了

private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_view);
        imageView = (ImageView) findViewById(R.id.image_view);
        Bitmap bitmap = loadImage();
    }

接下來貼出佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:scaleType="center"
        android:src="@drawable/myimage"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical"/>
</LinearLayout>

是不是很簡單,如果用android studio 的同學可以直接看到載入的記憶體大小,你把傳入的顯示控制元件要求的寬高設定為不同,其記憶體需求也是不同的。具體記憶體可能並不是嚴格的圖片載入記憶體的大小所需要的那麼大,這是因為即使是一個空應用也要消耗記憶體才能跑起來,不過大致的記憶體還是很接近圖片需要的記憶體的。

好了,Bitmap最簡單的用法就到這裡,下一篇文章減少更高階的用法。