Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android大圖加載內存優化(如何防止OutOfMemory)

Android大圖加載內存優化(如何防止OutOfMemory)

編輯:關於Android編程

一、簡介

移動設備不斷發展的今天,有的人認為內存已經足夠大了,不用再管什麼內存優化,Java是虛擬機可以幫我維護內存。其實內存空間資源還是很寶貴的,不管手機內存有多大,系統分配給單個應用的內存空間還是很有限的大致有16M,64M,128M等。在Android中加載大圖會非常消耗系統資源,16M的圖片大致可以存儲3張1024X1536質量為ARGB_8888的圖片,這裡邊還不包含其它Object所占的資源。軟件在系統上運行,環境是很復雜的,可能測試的時候有限的測試次數上沒有發現內存洩漏問題,但是在成千上萬的用戶使用過程中,總會有各種內存洩露問題。

二、圖片所占內存空間的計算規則

大圖加載首先要清楚圖片的質量與所占內存的計算規則:Bitmap.Config

 

Bitmap.Config

介紹(每個像素點的構成)

1pix所占空間

1byte = 8位

1024*1024圖片大小

(分辨率)

ALPHA_8

只有透明度,沒有顏色,那麼一個像素點占8位。

1byte

1M

RGB_565

即R=5,G=6,B=5,沒有透明度,那麼一個像素點占5+6+5=16位

2byte

2M

ARGB_8888

由4個8位組成,即A=8,R=8,G=8,B=8,那麼一個像素點占8+8+8+8=32位

4byte

4M

ARGB_4444

由4個4位組成,即A=4,R=4,G=4,B=4,那麼一個像素點占4+4+4+4=16位

2byte

2M


 

三、加載和顯示的常見策略

1.直接加載並顯示:

直接把圖片解碼並顯示加載到控件上,常用的setImageResource方法就是直接加載圖片到ImageView上邊展示,這種方法是加載原圖的方式;

2.加載到內存做縮放:

解碼到內存或者Bitmap,判斷Bitmap的大小判斷是否需要二次處理,縮放到指定的大小再顯示;

3.降低質量加載:

解碼的時候設置Bitmap.inPreferredConfig ,嘗試加載不同質量的位圖到內存;

4.預加載的方式:

就是先獲取將要加載的圖片的大小,預計算內存的占用和是否我們需要這樣分辨率的圖片。
 

四、大圖的加載優化

采用BitmapFactory加載圖片,它提供了decodeResource 和decodeFile兩個方法給我們解碼Resource目錄和本地SDCard目錄下的圖片,提供了強大的BitmapFactory.Options 給我在解碼圖片的過程中的配置。

1.使用decodeResource方法加載Drawable下邊的圖片

 

圖片分辨率為:8176 x 2368 顏色模型是:ARGB_8888, 完全加載到內存所需資源是:73.6M左右,意味著巨大多數的Android手機都會瞬間內存洩漏。

加載圖片時先獲取圖片的大小和圖片的格式:將inJustDecodeBounds設置為true,不解碼圖片到內存,只讀取圖片的基本信息。

 

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;

這時候可以獲取到圖片的大小和格式,然後根據需要加載的模型計算所占內存空間的大小:

 

/**
 * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
 */
public static int getBytesPerPixel(Bitmap.Config config) {
    if (config == Bitmap.Config.ARGB_8888) {
        return 4;
    } else if (config == Bitmap.Config.RGB_565) {
        return 2;
    } else if (config == Bitmap.Config.ARGB_4444) {
        return 2;
    } else if (config == Bitmap.Config.ALPHA_8) {
        return 1;
    }
    return 1;
}

根據圖片的寬和高獲取圖片大小:

 

 

/**
 * get the image size in the RAM
 *
 * @param imageW
 * @param imageH
 * @return
 */
public static long getBitmapSizeInMemory(int imageW, int imageH) {
    return imageH * imageW * getBytesPerPixel(Bitmap.Config.ARGB_8888);
}

這時候可以增加一些策略,比如只加載控件大小的圖片,或者判斷一下當前的內存使用情況,在視情況加載圖片:這時候要把inJustDecodeBounds設置為false,這裡我們演示一下按照指定分辨率加載圖片的方式:

 

 

/**
 * Load bitmap from resources
 *
 * @param res        resource
 * @param drawableId resource image id
 * @param imgH       destination image height
 * @param imgW       destination image width
 * @return
 */
public static Bitmap loadHugeBitmapFromDrawable(Resources resources, int drawableId, int imgH, int imgW) {
    Log.d(TAG, imgH: + imgH +  imgW: + imgW);

    BitmapFactory.Options options = new BitmapFactory.Options();

    //preload set inJustDecodeBounds true, this will load bitmap into memory
    options.inJustDecodeBounds = true;
    //options.inPreferredConfig = Bitmap.Config.ARGB_8888;//default is Bitmap.Config.ARGB_8888
    BitmapFactory.decodeResource(resources, drawableId, options);

    //get the image information include: height and width
    int height = options.outHeight;
    int width = options.outWidth;
    String mimeType = options.outMimeType;

    Log.d(TAG, width: + width +  height: + height +  mimeType: + mimeType);

    //get sample size
    int sampleSize = getScaleInSampleSize(width, height, imgW, imgH);
    options.inSampleSize = sampleSize;
    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;

    Log.d(TAG, memory size: + getBitmapSizeInMemory(width / sampleSize, height / sampleSize));
    Bitmap bitmap = BitmapFactory.decodeResource(resources, drawableId, options);
    Log.d(TAG, w= + bitmap.getWidth() +  h= + bitmap.getHeight() +  bitmap size: + bitmap.getRowBytes() * bitmap.getHeight());
    return bitmap;
}

 

 

2.使用decodeFile方法加載SDCard下邊的圖片

圖片信息同上。

 

/**
 * load the bitmap from SDCard with the imgW and imgH
 *
 * @param imgPath  resource path
 * @param imgH     result image height
 * @param imgW     result image width
 * @return result bitmap
 */
public static Bitmap loadHugeBitmapFromSDCard(String imgPath, int imgH, int imgW) {
    Log.d(TAG, imgH: + imgH +  imgW: + imgW);

    BitmapFactory.Options options = new BitmapFactory.Options();

    //preload set inJustDecodeBounds true, this will load bitmap into memory
    options.inJustDecodeBounds = true;
    //options.inPreferredConfig = Bitmap.Config.ARGB_8888;//default is Bitmap.Config.ARGB_8888
    BitmapFactory.decodeFile(imgPath, options);

    //get the image information include: height and width
    int height = options.outHeight;
    int width = options.outWidth;
    String mimeType = options.outMimeType;

    Log.d(TAG, width: + width +  height: + height +  mimeType: + mimeType);

    //get sample size
    int sampleSize = getScaleInSampleSize(width, height, imgW, imgH);
    options.inSampleSize = sampleSize;
    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;

    Log.d(TAG, memory size: + getBitmapSizeInMemory(width / sampleSize, height / sampleSize));
    Bitmap bitmap = BitmapFactory.decodeFile(imgPath, options);
    Log.d(TAG, w= + bitmap.getWidth() +  h= + bitmap.getHeight() +  bitmap size: + bitmap.getRowBytes() * bitmap.getHeight());

    return bitmap;
}

 

兩種方式的原理完全一樣。

3.其中inSampleSize的計算方式和注意點

根據官方文檔我們可以了解到,inSampleSize必須是2的指數倍,如果不是2的指數倍會自動轉位教下的2的指數倍的數,下邊我提供一個計算方法:

 

/**
     * get the scale sample size
     *
     * @param resW resource width
     * @param resH resource height
     * @param desW result width
     * @param desH result height
     * @return
     */
    public static int getScaleInSampleSize(int resW, int resH, int desW, int desH) {
        int scaleW = resW / desW;
        int scaleH = resH / desH;
        int largeScale = scaleH > scaleW ? scaleH : scaleW;

        int sampleSize = 1;
        while (sampleSize < largeScale) {
            sampleSize *= 2;
        }

        Log.d(TAG, sampleSize: + sampleSize);

        return sampleSize;
    }

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved