Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Bitmap的加載與Cache(一)

Bitmap的加載與Cache(一)

編輯:關於Android編程

如何有效的加載一個bitmap,由於Bitmap的特殊性以及Android對單個應用所施加的內存限制,比如16MB,這就導致加載Bitmap的時候很容易出現內存溢出。

因此,如何高效的加載bitmap是一個很重要也很容易被開發者忽略的問題。

Bitmap的高效加載:

如何加載一張圖片呢?BitmapFactory類提供了四類方法:decodedFile,decodedResource,decodedStream和decodedByteArray,分別用於支持文件系統,資源,輸入流以及字節數組中加載出一個Bitmap對象,其中decodedFile和decodedResource又間接調用了decodedStream方法,這四類方法最終是在Android的底層實現的,對應著BitmapFactory類的幾個方法。

高效加載Bitmap的核心就是采用BitmapFactory.Options來加載所需尺寸的圖片。這裡加色通過ImageView來顯示圖片,很多時候ImageView並沒有圖片的原始尺寸那麼大,這個時候把整個圖片加載進來後再射給ImageView,這顯然是沒有必要的,因為ImageView並沒有辦法顯示原始的圖片。通過Options參數就可以按一定的采樣率來加載縮小後的圖片,將縮小後的圖片在ImageView中顯示,這樣就會降低內存占用從而在一定程度上避免OOM,提高加載時的性能。其縮放圖片主要是用到了Options的inSampleSize參數,即采樣率,當inSampleSize為1時,采樣後的圖片大小為圖片的原始大小,當inSampleSize大於1時,比如2,那麼采樣後的圖片其寬/高均為原圖大小的1/2,而像素數為原圖的1/4,其占有的內存大小也為原圖的1/4

拿一張1024*1024像素的圖片來說,嘉定采用ARGB8888格式存儲,那麼他占有的內存為1024*1024*4,即4MB,如果inSampleSize為2,那麼采樣後的圖片其內存占用只有512*512*4即1MB。可以發現采樣率inSampleSize必須是大於1的整數,圖片才會有縮小的效果,並且采樣率同時作用於寬/高,這將導致縮放後的圖片大小以采樣率的2次方形式遞減。即inSampleSize=4是,那麼縮放比例就是1/16。有一種 特殊情況,就是當inSampleSize小雨1時,其作用相當於1,即無縮放效果。

通過采樣率即可有效的加載圖片,那麼到底如何獲取采樣率呢?

(1)將BitmapFactory.Options的inJustDecodeBounds參數設為true並加載圖片(只會解析 圖片的原始寬/高信息,並不會去真正加載圖片)

(2)從BitmapFactory.Options中取出圖片的原始寬高信息。他們對英語outWitdh和outHeight參數

(3) 根據采樣率的規則並結合目標View的所需大小計算出采樣率inSampleSize

(4)將itmapFactory.Options的inJustDecodeBounds參數誰為false,然後重新加載圖片。

public static Bitmap decodeSanpledBitmapFromResource(Resources res,

int resId, int reqWidth, int reqHeight) {

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

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(res, resId, options);

options.inSampleSize = calculateInSampleSize(options, reqWidth,

reqHeight);

options.inJustDecodeBounds = false;

return BitmapFactory.decodeResource(res, resId, options);

}

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 halfHieght = height / 2;

final int halfWidth = width / 2;

while ((halfHieght / inSampleSize) >= reqHeight

&& (halfWidth / inSampleSize >= reqWidth)) {

inSampleSize *= 2;

}

}

return inSampleSize;

}

除了BitmapFactory的decodedResource方法,其他三個decode系列的方法也是支持采樣加載的,並且處理方式也是類似的,但是decodeStream方法稍微特殊,後面會講解。

Android中的緩存策略:

緩存策略在Android中有著廣泛的使用場景,尤其在圖片加載這個場景下,緩存策略變得非常重要。

目前常用的一種緩存算法是LRU,LRU是近期最少使用算法,他的核心思想是當緩存滿時,會優先淘汰那些近期最少使用的緩存對象。采用LRU算法的緩存由兩種:LruCache和DiskLruCache,前者用於實現內存緩存,而後者則充當了存儲設備緩存,通過二者的結合就可以很方便的實現一個很高使用價值的ImageLoader。

LruCache:

LruCache是3.1所提供的一個緩存類,通過v4兼容包可以兼容到早起的版本,為了能夠兼容至2.2版本,在使用LruCache的時候建議采用v4兼容包中提供的LruCache,而不要直接使用3.1提供的LruCache

LruCache是一個泛型類,他內部采用一個LinkedHashMap以強引用的方法存儲外界的緩存對象,其提供了get和put方法來完成緩存的獲取和添加操作。接下來簡答說一個幾個引用:

強引用: 直接的對象引用

軟引用: 當一個對象只有軟引用存在時,系統內存不足時此對象會被gc回收;

弱引用: 當一個對象只有弱引用存在時,此對象會隨時被gc回收。

另外LruCache是線程安全的,因為他使用了LinkedHashMap:

public class LruCache {

private final LinkedHashMap map;

功欲善其實,必先利器,接下來我們就看看LruCache的使用,首先典型的初始化:

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

int cacheSize = maxMemory / 8;

mMemoryCache = new LruCache(cacheSize) {

@Override

protected int sizeOf(String key, Bitmap bitmap) {

// TODO Auto-generated method stub

return bitmap.getRowBytes() * bitmap.getHeight() / 1024;

}

};

只需要提供緩存的總容量大小並重寫sizeOf方法即可。sizeOf方法的作用是計算緩存對象的大小,這裡的大小的單位需要和總容量的單位保持一致。對於上面代碼,總容量的大小為當前進程可用內存的1/8,單位為KB,sizeOf方法則完成了Bitmap對象的大小計算。

一些特殊情況,還需要重寫LruCache的entryRemoved方法,LruCache移除舊緩存時會調用這個方法,因此可以在這個方法中完成一些資源回收(如果需要的話)

除了創建以外還有緩存的獲取和添加。

mMemoryCache.get(key);

mMemoryCache.put(key, value);

DiskLruCache:

DiskLruCache用於實現存儲設備緩存,即磁盤緩存。他通過對緩存對象寫入文件系統從而實現緩存效果。其源碼地址:

下載地址

DiskLruCache的創建:

DiskLruCache並不能通過構造方法來創建,他提供了open方法用於創建自身,如下所示:

public static DiskLruCache open(File directory,int appVersion,int valueCount,long manSize)

第一個參數表示磁盤緩存在文件系統中的存儲路徑,緩存路徑可以選擇SD卡上的緩存目錄,具體是指/sdcard/Android/data/package_name/cache目錄,其中package_name表示當前應用包名,當應用被卸載後,此目錄會一並被刪除。當然也可以選擇SD卡上的其他指定目錄,還可以選擇data下的當前應用目錄,具體可根據需要靈活設定。如果應用卸載後就希望刪除緩存目錄,那麼就選擇SD卡上的緩存目錄,否則就選擇SD卡上的其他目錄。

第二個參數表示應用的版本號,一般設為1即可。當版本號發生變化時DiskLruCache會清空之前所有的緩存文件,而這個特性在實際開發中作用並不大,很多情況下既是應用版本號發生了變化緩存文件仍然是有效的,因此這個參數設為1比較好。

第三個參數表示單個節點所對應的數據的個數,一般設為1即可,

第四個參數表示緩存的總大小,比如50MB,當緩存大小超出這個設定值後,DiskLruCache會清楚一些緩存從而保證總大小不大於這個設定值:

private static final long DISK_CACHE_SIZE=1024*1024*50;

File diskCacheDir=getDiskCacheDir(mContext,"bitmap");

if(!diskCacheDir.exists()){

diskCacheDir.mkdir();

}

mDiskLruCache=DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);

DiskLruCache的緩存添加:

DiskLruCache的緩存添加的操作是通過Editor完成的。Editor表示一個緩存對象的編輯對象,這裡仍以圖片緩存舉例子,首先需要獲取圖片url所對應的key,然後根據key就可以通過edit()來獲取Editor對象,如果這個緩存正在被編輯,那麼會返回null,即DiskLruCache不允許同時編輯一個緩沖對象,之所以要把url轉換為key,是因為圖片的url中很可能有特殊字符,這將影響url在Android中直接使用,一般采用url的md5值作為key:

private String hashKeyFormUrl(String url) {

String cacheKey;

try {

final MessageDigest mDigest = MessageDigest.getInstance("MD5");

mDigest.update(url.getBytes());

cacheKey = bytesToHexString(mDigest.digest());

} catch (Exception e) {

// TODO: handle exception

cacheKey = String.valueOf(url.hashCode());

}

return cacheKey;

}

private String bytesToHexString(byte[] digest) {

// TODO Auto-generated method stub

StringBuilder sb = new StringBuilder();

for (int i = 0; i < digest.length; i++) {

String hex = Integer.toHexString(0xFF & digest[i]);

if (hex.length() == 1) {

sb.append('0');

}

sb.append(hex);

}

return sb.toString();

}

將圖片的url轉化為key以後,就可以獲取Editor對象了,對於這個key來說,如果當前不存在其他Editor對象,那麼edit()就會返回一個新的Editor對象,通過他就可以得到一個文件輸出流,需要注意的是,由於前面在DiskLruCache的open方法中設置了一個子節點只能有一個數據,因此下面的DISK_CACHE_INDEX常量直接設為0即可:

String key=hashKeyFormUrl(url);

DiskLruCache.Editor editor=mDiskLruCache.edit(key);

if(editor!=null){

OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);

}

有了文件輸出流,接下來就是,從網絡下載圖片時,圖片就可以通過這個文件輸出流寫入到文件系統上:

public boolean downloadUrlToStream(String urlString,

OutputStream outputStream) {

HttpURLConnection urlConnection = null;

BufferedOutputStream out = null;

BufferedInputStream in = null;

try {

final URL url = new URL(urlString);

urlConnection = (HttpURLConnection) url.openConnection();

in = new BufferedInputStream(outputStream, IO_BUFFER_SIZE);

int b;

while ((b = in.read()) != -1) {

out.write(b);

}

} catch (Exception e) {

// TODO: handle exception

} finally {

if (urlConnection != null) {

urlConnection.disconnect();

}

MyUtils.close(out);

MyUtils.close(in);

}

return false;

}

經過上面的步驟,其實並沒有真正的將圖片寫入文件系統,還必須通過Editor的commit()來提交寫入操作,如果圖片下載 過程發生了異常,那麼還可以通過Editor的abort()來回退整個操作:

OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);

if(downloadUrlToStream(url,outputStream)){

editor.commit();

}else{

editor.abort();

}

mDiskLruCache.flush();

經過上面的步驟,圖片就已經被正確的寫入到文件系統中了,接下來圖片的獲取操作就不需要請求網絡了。

DiskLruCache的緩存查找:

和緩存添加過程類似,緩存找找過程也需要將url轉換為key,然後通過DiskLruCache的get方法得到一個Snapshot對象,接著再通過Snapshot對象即可得到緩存的文件輸入流,有了文件輸入流,自然就可以得到Bitmap對象了,為了避免加載圖片過程中導致的OOM問題,一般不建議直接加載原始圖片。但是BitmapFactory.Options的方式壓縮圖片對FileInputStream的縮放存在問題,原因是,FileInputStream是一種 有序的文件流,而兩次decodeStream調用印象了文件流的位置屬性,導致了第二次decodeStream時得到的是null。為了解決這個問題,可以通過文件流來得到他所對應的文件描述符,然後再通過BitmapFactory.decodeFileDescriptor方法來加載一張縮放後的圖片,代碼如下:

Bitmap bitmap = null;

String key = hashKeyFormUrl(url);

DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);

if (snapShot != null) {

FileInputStream fileInputStream = (FileInputStream) snapShot

.getInputStream(DISK_CACHE_INDEX);

FileDescriptor fileDescriptor = fileInputStream.getFD();

bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(

fileDescriptor, reqWidth, reqHeight);

if(bitmap!=null){

addBitmapToMemoryCache(key,bitmap);

}

}

好了,就說到這裡,主要介紹了一些緩存策略和高效加載bitmap的方式,後續會繼續對於ImageLoader進行學習並解析。

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