Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Bitmap深入介紹(二)--- 優化技術

Android Bitmap深入介紹(二)--- 優化技術

編輯:關於Android編程

這一篇主要介紹Bitmap相關的一些優化技術,包括加載圖片,圖片內存管理,圖片緩存。

加載圖片

圖片縮放

我們在加載圖片的時候,經常會遇到OOM的問題,也許我們測試的時候圖片比較小,但是實際上使用的圖片可能

會很大,我最好的方式就是在加載的時候就把圖片縮小。Options提供了inJustDecodeBounds來先獲取圖片的大小,

如下代碼:


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; // 圖片的mimeType

options.outHeight和options.outWidth會獲取圖片的寬和高,通過獲取到圖片的寬和高,就可以使用options的inSample來縮放圖片,

在加載圖片顯示到屏幕的時候,我們最好跟屏幕的密度一致,所以可以通過inSample來設置最終要縮放到多少,另外一方面我們有時候只需要縮略圖,也需要進行縮放。下面是Android提供的代碼:


int calculateInSampleSize(BitmapFactory.Options options,  int reqWidth, int reqHeight){

    final int height = options.outHeight;

    final int width  = options.outWidth;

    int inSampleSize = 1 ;

    if(reqWidth < width || reqHeight  reqWidth && halfHeight / inSampleSize > reqHeight){

            inSampleSize *= 2;

        }

    }

}

通過這個方法得到inSampleSize,然後通過將options的inJustDecodeBounds設置為false就可以利用BitmapFactory加載圖片了,得到的圖片是經過縮放了的。

Bitmap.Config

除了根據屏幕的情況,圖片在屏幕中顯示的大小來縮放圖片,另外也可以通過options的inPreferredConfig設置圖片的Config,使用RGB_565的圖片肯定比RGB_8888占用的內存要小的。

PNG or JPG

另外還需要考慮的一個問題是圖片的格式,使用png圖片還是jpg圖片,jpg壓縮率要高意味著解碼的時候消耗的時間肯能會更高,它沒有alpha通道,但是對於內置在apk裡面的圖片,如果圖片小,那麼apk的大小也會變小。另外如果圖片的色值豐富的話,用可能有好點,色值單調可能用png好點(比如我們的icon)。

圖片內存管理

在前面一篇有介紹過Android中圖片的存儲相關知識,在Android 2.3以及之前圖片會存儲在native內存中,Android推薦在Bitmap不再使用的時候,使用recycle方法回收Bitmap,因為圖片存儲在native內存當中,所以需要手動回收,另外也可以使用引用計數的方式在Bitmap的計數為0時,調用Bitmap的recycle方法。

下面是Android提供的一段引用計數的代碼:


private int mCacheRefCount = 0;
private int mDisplayRefCount = 0;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
public void setIsDisplayed(boolean isDisplayed) {
    synchronized (this) {
        if (isDisplayed) {
            mDisplayRefCount++;
            mHasBeenDisplayed = true;
        } else {
            mDisplayRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}

// Notify the drawable that the cache state has changed.
// Keep a count to determine when the drawable is no longer being cached.
public void setIsCached(boolean isCached) {
    synchronized (this) {
        if (isCached) {
            mCacheRefCount++;
        } else {
            mCacheRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}

private synchronized void checkState() {
    // If the drawable cache and display ref counts = 0, and this drawable
    // has been displayed, then recycle.
    if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
            && hasValidBitmap()) {
        getBitmap().recycle();
    }
}

private synchronized boolean hasValidBitmap() {
    Bitmap bitmap = getBitmap();
    return bitmap != null && !bitmap.isRecycled();
}

另外一方面也可以考慮使用inPurgeable,inPurgeable讓Android在需要的時候可以回收像素,減少OOM。需要重新繪制的時候,又重新解碼。其實這會導致更多的計算消耗。

在3.0之後開始放到Java層內存中,在3.0之後也增加了inBitmap。inBitmap的使用方式如下:


Bitmap inBitmap ; //已經使用了的Bitmap

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

options.inBitmap = inBitmap ;

Bitmap newBitmap = BitmapFactory.Options.decodeFile(filename, options);

需要注意的時候在4.4之前不支持不同大小的圖片使用inBitmap,4.4才開始支持不同的大小,只要inBitmap比需要新加載的圖片更大。另外inBitmap其實可以與下一節介紹的緩存一起使用,可以使用緩存了的圖片作為inBitmap來加載新的圖片。這裡也有例子。

緩存方式

如果都是每次使用BitmapFactory從sdcard讀取Bitmap,那麼將會非常浪費時間,因為從磁盤讀取圖片是非常慢的,而且有的圖片需要經常使用,如果把圖片緩存在內存當中將能夠很好地節省圖片讀取的時間。這樣圖片緩存就出現了。

LruCache

LruCache是一個非常適合緩存圖片的類,它是基於LinkedHashMap實現的。把最近經常使用的對象保存在LinkedHashMap裡面,把最近沒使用的從LinkedHashMap中移除。LinkedHashMap是一個LinkedList和HashMap一起實現的。需要注意一點的是在以前經常使用SoftReference或WeakReference來引用Bitmap來緩存,但是在2.3後,Android虛擬機的垃圾回收機制回收Soft和Weak引用更加積極了,也就是說它會很快就回收軟引用和弱引用對象。

Android可以通過Runtime.getRuntime().getMaxMemory()獲取最大內存,Android提供了一段小代碼來使用LruCache:


    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };

DiskLruCache

我們的圖片可能是從網絡中下載下來的,但是我們的應用中可能也沒辦法把圖片全部放進內存,我們需要把圖片保存在Disk中,另外一方面我們也不想要每次都從網絡中下載圖片。LruDiskCache就提供了一種磁盤緩存。當然如果圖片訪問非常頻繁,使用ContentProvider的話將會更好。

Ashmem

除了一般的緩存方式,強大的Fresco在5.0之前利用ashmem來緩存圖片,將圖片保存在ashmem當中,這樣就不會占用太多Java堆內存而導致出現OOM的情況。下面是一段簡單的將圖片存在ashmem中並且讀取出來的例子:


   private void testBitmapMemoryFile(){
//        Log.i(LOGTAG,""+bitmap.getConfig().name());
       InputStream is = getResources().openRawResource(R.raw.test4);
       byte[] imgArr = new byte[10 * 1024 * 1024];
       int imgBytes = 0;
       try {
           while (is.available() > 0) {
               int bytes = is.read(imgArr, imgBytes, 1024);
               imgBytes += bytes;
               Log.i(LOGTAG, "bytes read:" + bytes);
            }

            is.close();
            Log.i(LOGTAG, "bytes : " + imgBytes);
     ?      memoryFile = new MemoryFile(null, imgBytes);
            OutputStream os = memoryFile.getOutputStream();
    ?       os.write(imgArr, 0, imgBytes);
            imgArr = null;
            os.flush();
    ?       os.close();
    ?       
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPurgeable = true;
            fd = getMemoryFileFd(memoryFile,null,options);

            if (fd == null) {
               memoryFile.close();
               Log.e(LOGTAG, "fd read error");
               return;
            }
            Log.i(LOGTAG, "fd read ok");
            bitmap =  BitmapFactory.decodeFileDescriptor(fd);

            iv.setImageBitmap(bitmap);
            memoryFile.close();
        } catch (IOException e) {
           e.printStackTrace();
        }
    }

上面的代碼是將一張raw目錄下面的圖片保存到AshmemFile當中,然後再利用BitmapFactory讀取圖片。並且使用inPurgeable標志。AshmemFile和inPurgeable合起來使用。

配置改變時緩存

Activity可能會經常遇到旋轉屏幕的情況,會被重新加載,另外在跳轉的時候也可能會被finish掉,返回來又得重新加載。這種時候如果對於已經加載了的東西都全部重新加載那會非常耗費時間,這種時候保存Cache,不重新加載將會是一個非常的方式,比如在Fragment中使用了Cache,可以使用setRetainInstance(true),避免重新創建Fragment,這樣也能避免加載Fragment中的Cache。

總結

這篇文章主要介紹了圖片加載過程中的相關配置,比如利用Options的參數配置輸出圖片的大小,Bitmap.Config配置解碼圖片像素格式,以及如何使用PNG和JPG圖片。另外介紹了圖片緩存(LRUCache和AshmemFile)以及圖片存儲管理。


參考

https://developer.android.com/training/displaying-bitmaps/index.html

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