1. 程式人生 > >Android Bitmap深入介紹(一)---基礎

Android Bitmap深入介紹(一)---基礎

在Android應用開發中,我們經常需要跟圖片打交道,而圖片一個很麻煩的問題是佔用記憶體非常大,經常導致OOM,瞭解Bitmap相關資訊,不同sdk版本中Android圖片處理的變化,以及一些優化處理的方式對我們平時開發中對圖片的會非常有幫助。

這篇先介紹Bitmap基礎內容,關於畫素,儲存資訊,以及載入。

畫素

Bitmap的儲存可以說包括兩個部分,畫素以及長,寬,顏色等描述資訊。畫素是Bitmap最佔用記憶體的地方,長寬和畫素位數是用來描述圖片的,可以通過這些資訊計算出圖片的畫素佔用的記憶體大小。具體到Bitmap的API是下面這幾個介面:

public final int
getWidth() public final int getHeight() public final Config getConfig()

Config是一個列舉型別,表示圖片畫素型別,總共有下面幾種型別:ALPHA_8 (1),RGB_565 (3),ARGB_4444 (4),ARGB_8888 (5);。表示每一個畫素圖片組成。實際上下面兩種方式獲取的數值是相等的:

int b = 1;
switch (bitmap.getConfig()) {
    case ALPHA_8:
        b = 1;
        break;
    case
ARGB_4444: b = 2; break; case ARGB_8888: b = 4; break; } int bytes1 = bitmap.getWidth() * bitmap.getHeight() * b; int bytes2 = bitmap.getByteCount(); //從api12才有的介面

這是由Bitmap相關引數可以計算出Bitmap所佔用的畫素數,實際上我們放入drawable裡面的圖片都是已經知道了圖片的長寬以及畫素組成的,但是直接在Android外面算出的圖片畫素數量與通過上面的程式碼計算會有出入的。因為Android對圖片做了縮放,這個跟你將圖片放入的drawable位置相關。

我們都知道android資源目錄中會有drawable-hdpi, drawable-xhdpi,drawable-xxhdpi等目錄。這裡每個目錄都會對應一個density。下面看BitmapFactory.decodeResource方法載入Bitmap的例子:

BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test4, options);
Log.i(LOGTAG, "options: " + options.inDensity + "," + options.inTargetDensity);

decodeResource就是Android內部對Resource的載入方式,這裡就不從原始碼上面一步一步介紹了,它最終會呼叫decodeResourceStream方法,直接看decodeResourceStream:

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
        InputStream is, Rect pad, Options opts) {
    if (opts == null) {
        opts = new Options();
    }
    if (opts.inDensity == 0 && value != null) {
        final int density = value.density;
        if (density == TypedValue.DENSITY_DEFAULT) {
            opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
        } else if (density != TypedValue.DENSITY_NONE) {
            opts.inDensity = density;
        }
    }
    if (opts.inTargetDensity == 0 && res != null) {
        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
    }
    return decodeStream(is, pad, opts);
}

options.inDensity表示圖片自身預設的畫素密度,TypedValue會有一個density,對應著圖片來自於哪個drawable目錄,因為每一個drawable目錄(drawable-hdpi,drawable-xhdpi,drawable-xxhdpi)都對應著一種螢幕,而螢幕就有density,TypedValue的density對應著DisplayMetrics的densityDpi,densityDpi表示每英尺的畫素數。options.inTargetDensity是當前手機螢幕對應的densityDpi,最終的畫素數是:

bytes = 原始圖片寬*(options.inTargetDensity/options.inDensity)*原始圖片長*(options.inTargetDensity/options.inDensity)*每個畫素點位數

儲存與傳輸

Android圖片在不同的sdk版本中儲存的地方是不一樣的。在2.3及2.3以前,圖片畫素是儲存在native記憶體中。Android記憶體分為Java堆和native記憶體。Android會限制每個應用能使用的最大記憶體。但是Android對記憶體的限制是Java堆和native記憶體的和,把畫素資料存放在native區,虛擬機器無法自動進行垃圾回收,必須手動使用bitmap.recycle()導致很容易記憶體洩漏。因為Android的裝置monitor也只能夠看到Java堆的記憶體變化,這樣其實也不方便除錯Bitmap記憶體。比如在應用中新建立一個圖片,根本無法在monitor中看到記憶體變化。

從3.0開始Android將圖片儲存在Java堆中,新載入一張圖片的時候,也能夠立刻從monitor反映出來。另外Java的垃圾回收機制也能夠自動回收。然後在4.0後,圖片又有了一些變化,那就是在parcel傳輸的時候,當圖片很大時,它會使用ashmem來進行圖片的傳輸,具體可以看我這篇文章Android4.0之後Parcel傳輸Bitmap原始碼分析。在6.0的時候,圖片的儲存又有了很大的變化,底層已經明顯增加了將圖片儲存ashmem的介面了,具體可以可以看我這篇文章Android6.0 Bitmap儲存以及Parcel傳輸原始碼分析

BitmapFactory

BitmapFactory是用來載入圖片的,這個類主要分為三種圖片的載入,先把它的API拿出來看一下:


    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) 
    public static Bitmap decodeResource(Resources res, int id, Options opts) 
    public static Bitmap decodeResource(Resources res, int id) 

    public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
    public static Bitmap decodeByteArray(byte[] data, int offset, int length) {

    public static Bitmap decodeFile(String pathName, Options opts) 
    public static Bitmap decodeFile(String pathName)
    public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) 
    public static Bitmap decodeStream(InputStream is) 

    public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
    public static Bitmap decodeFileDescriptor(FileDescriptor fd)

我們直接看BitmapFactory提供的nativeDecode介面:

    private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
            Rect padding, Options opts);
    private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
            Rect padding, Options opts);
    private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
    private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
            int length, Options opts);

BitmapFactory對File的decode都會轉換為InputStream採用nativeDecodeStream來decode,對Resource的decode會採用decodeAsset,而如果FileDesciptor可以轉換為native的fd,會通過nativeDecodeFileDescriptor來decode,另外ByteArray會直接採用nativeDecodeByteArray來decode。需要注意的是,對Resource的decode,BitmapFactory會設定Option的相關引數,最終進行相應的縮放,圖片的大小會跟原圖有所區別。 具體的內容建議去看看BitmapFactory,瞭解每種方式的區別,才能夠更好地使用介面,選擇的時候採用更有效率的方法。

BitmapFactory.Options

下面看一下Options類,我們在載入的時候,可以通過這個引數對圖片進行一些處理,前面已經說了inDensity和inTargetDensity。下面看看其他的引數。

inPurgeable

這個引數的用途是當需要使用Bitmap的時候再載入Bitmap,不需要的時候回收Bitmap。在4.1中,使用inPurgeable,載入圖片後記憶體基本不會增高,而不使用inPurgeable載入圖片後記憶體會有明顯的增加。

inSampleSize

這是表示取樣大小,長和寬會對應乘以1/inSampleSize。用於將圖片縮小加載出來的,以免站佔用太大記憶體,適合縮圖。

inJustDecodeBounds

這個設定了true後,用於獲取圖片的寬度長度資訊。下面是個例子:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
// options.outWidth 和 options.outHeight就能夠獲取結果

inPreferredConfig

用於配置圖片解碼方式,對應的型別Bitmap.Config。如果非null,則會使用它來解碼圖片

inBitmap

在Android 3.0開始引入了inBitmap設定,通過設定這個引數,在圖片載入的時候可以使用之前已經建立了的Bitmap,以便節省記憶體,避免再次建立一個Bitmap。在Android4.4,新增了允許inBitmap設定的圖片與需要載入的圖片的大小不同的情況,只要inBitmap的圖片比當前需要載入的圖片大就好了。

總結

這篇先主要介紹了Bitmap相關基本的資訊,Bitmap,BitmapFactory和Options類,以及bitmap的儲存。