一、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)同步加載和異步加載接口的設計