一、Bitmap的高效加載
BitmapFactory類提供了四種方法:
decodeFile、decodeResource、decodeStream和decodeByteArray,分别用于支援從檔案系統、資源、輸入流以及位元組數組加載一個Bitmap對象。
如何高效加載Bitmap,那就是采用BitmapFactory.Options來加載所需的圖檔,通過BitmapFactory.Options可以按一定的采樣率來加載縮小後的圖檔,可以在一定程度上避免OOM。
1、通過采樣率加載圖檔步驟:
(1)将BitmapFactory.Options的inJustDecodeBounds參數設定為true并加載圖檔
(2)從BitmapFactory.Options中取出圖檔的原始寬高資訊,他們對應于outWidth和outHeight參數
(3)根據采樣率的規則并結合目标View的所需大小計算出采樣率inSampleSize
(4)将BitmapFactory.Options的inJustDecodeBounds參數設為false,然後重新加載圖檔
示例代碼:
public static Bitmap decodeSampleBitmapFromResource(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);
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 halfHeight = height / 2;
final int halfWidth = width / 2;
while((halfHeight / inSampleSize) >= reqHeight &&(halfWidth / inSampleSize) >= reqWidth){
inSampleSize *= 2;
}
}
return inSampleSize;
}
二、Android中的緩存政策
緩存政策主要包含緩存的添加、擷取和删除三類操作。常用的緩存算法是LRU,他的核心思想是當緩存滿時,會移除那些最近很少使用的緩存對象。
采用LRU緩存的有兩種:LruCache和DiskLruCache,LruCache用于實作記憶體緩存,而DiskLruCache則是儲存設備緩存。
1、LruCache
LruCache是一個泛型類,内部采用一個LinkedHashMap以強引用的方式存儲外界的緩存對象,提供了get和put方法來完成緩存的擷取和添加操作,當緩存滿時,LruCache會移除較早使用的緩存對象,然後添加新的緩存對象。這裡需要大家明白強引用、弱引用和軟引用的差別。
(1)強引用:直接的對象引用
(2)弱引用:當一個對象隻有軟引用存在時,系統記憶體不足時此對象會被gc回收
(3)弱引用:當一個對象隻有弱引用存在時,此對象随時會被gc回收
下面代碼展示了LruCache的初始化過程:
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key,Bitmap bitmap){
return bitmap.getRowBytes()*bitmap.getHeight() / 1204;
}
}
2、DiskLruCache
DiskLruCache用于實作儲存設備緩存,即磁盤緩存,通過将緩存對象寫入檔案系統進而實作緩存的效果。DiskLruCache得到了Android官方文檔的推薦,但他不屬于AndroidSDK的一部分。
(1)DiskLruCache的建立
public static DiskLruCache open(File directory,int appVersion,int valueCount,long maxSize)
open有四個參數,第一個表示磁盤緩存在檔案系統中的存儲路徑,第二個參數表示應用版本号,第三個參數表示單個節點所對應的資料的個數
下面是DiskLruCache的建立過程
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
File diskCacheDie = getDiskCacheDir(mContext,"bitmap");
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
(2)DiskLruCache的緩存添加,示例代碼:
String key = hashKeyFromUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null){
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url,outputStream)){
editor.commit();
}else{
editor.abort();
}
mDiskLruCache.flush();
}
private String hashKeyFormUrl(String url){
String cacheKey;
try{
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
}catch(NoSuchAlgorithmException e){
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes){
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < bytes.length; i ++){
String hex = Integer.toHexString(0xFF & bytes[i]);
if(hex.length() == 1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
有了檔案輸出流,接下來從網絡下載下傳圖檔時,圖檔就可以通過這個流寫入到檔案系統中,代碼如下
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(urlConnection.getInputStream(),IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
int b;
while((b = in.read()) != -1 ){
out.write(b);
}
return true;
}catch(IOException e){
Log.e(TAG,"downloadBitmap failed"+e);
}finally{
if(urlConnection != null){
urlConnection.disconnect();
}
MyUtils.close(out);
MyUtils.close(in);
}
return false;
}
(3)DiskLruCache的緩存查找
和緩存的添加過程類似,緩存查找過程需要将url轉換為key,然後通過DiskLruCache的get方法得到一個Snapshot對象,接着在通過Snapshot對象即可得到緩存的檔案輸入流。代碼如下
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);
}
}
三、ImageLoader的實作
一般來說,一個優秀的ImageLoader應具備如下功能:
圖檔的同步加載、圖檔的異步加載、圖檔壓縮、記憶體緩存、磁盤緩存、網絡拉取
記憶體緩存和磁盤緩存是ImageLoader的核心,也是意義所在,通過這兩級緩存極大地提高了程式的的效率并且有效地降低了對用于所造成的流量消耗,隻有當兩級緩存都不可用時才需要從網絡拉取圖檔
代碼已上傳github:位址:
(1)圖檔壓縮功能實作
(2)記憶體緩存和磁盤緩存的實作:這裡使用LruCache和DiskLruCache來完成記憶體和磁盤緩存,在ImageLoader初始化時,建立LruCache和DiskLruCache。
(3)同步加載和異步加載接口的設計