二:開閉原則(Open Close Principle)
/**
* 圖片加載類
*/
public class ImageLoader {
// 圖片緩存
ImageCache mImageCache = new ImageCache() ;
// 線程池,線程數量爲CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
// 加載圖片
public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
public Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection)
url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
public class ImageCache {
// 圖片LRU緩存
LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
private void initImageCache() {
// 計算可使用的最大內存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取四分之一的可用內存作爲緩存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap) ;
}
public Bitmap get(String url) {
return mImageCache.get(url) ;
}
}
public class DiskCache {
// 爲了簡單起見臨時寫個路徑,在開發中請避免這種寫法 !
static String cacheDir = "sdcard/cache/";
// 從緩存中獲取圖片
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
// 將圖片緩存到內存中
public void put(String url, Bitmap bmp) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bmp.compress(CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} final ly {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
因爲需要將圖片緩存到SD卡中,所以,ImageLoader代碼有所更新,具體代碼如下:public class ImageLoader {
// 內存緩存
ImageCache mImageCache = new ImageCache();
// SD卡緩存
DiskCache mDiskCache = new DiskCache();
// 是否使用SD卡緩存
boolean isUseDiskCache = false;
// 線程池,線程數量爲CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
public void displayImage(final String url, final ImageView imageView) {
// 判斷使用哪種緩存
Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url)
: mImageCache.get (url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 沒有緩存,則提交給線程池進行下載
}
public void useDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache ;
}
}
新增了一個DiskCache類和往ImageLoader類中加入了少量代碼就添加了SD卡緩存的功能,用戶可以通過useDiskCache方法來對使用哪種緩存進行設置,例如:
ImageLoader imageLoader = new ImageLoader() ;
// 使用SD卡緩存
imageLoader.useDiskCache(true);
// 使用內存緩存
imageLoader.useDiskCache(false);
但是!!!有些明顯的問題,就是使用內存緩存時用戶就不能使用SD卡緩存,類似的,使用SD卡緩存時用戶就不能使用內存緩存。用戶需要這兩種策略的綜合,首先緩存優先使用內存緩存,如果內存緩存沒有圖片再使用SD卡緩存,如果SD卡中也沒有圖片最後才從網絡上獲取,這纔是最好的緩存策略。
/**
* 雙緩存。獲取圖片時先從內存緩存中獲取,如果內存中沒有緩存該圖片,再從SD卡中獲取。
* 緩存圖片也是在內存和SD卡中都緩存一份
*/
public class DoubleCache {
ImageCache mMemoryCache = new ImageCache();
DiskCache mDiskCache = new DiskCache();
// 先從內存緩存中獲取圖片,如果沒有,再從SD卡中獲取
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
// 將圖片緩存到內存和SD卡中
public void put(String url, Bitmap bmp) {
mMemoryCache.put(url, bmp);
mDiskCache.put(url, bmp);
}
}
再看看最新的ImageLoader類吧,代碼更新也不多:
public class ImageLoader {
// 內存緩存
ImageCache mImageCache = new ImageCache();
// SD卡緩存
DiskCache mDiskCache = new DiskCache();
// 雙緩存
DoubleCache mDoubleCache = new DoubleCache() ;
// 使用SD卡緩存
boolean isUseDiskCache = false;
// 使用雙緩存
boolean isUseDoubleCache = false;
// 線程池,線程數量爲CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
public void displayImage(final String url, final ImageView imageView) {
Bitmap bmp = null;
if (isUseDoubleCache) {
bmp = mDoubleCache.get(url);
} else if (isUseDiskCache) {
bmp = mDiskCache.get(url);
} else {
bmp = mImageCache.get(url);
}
if ( bmp != null ) {
imageView.setImageBitmap(bmp);
}
// 沒有緩存,則提交給線程池進行異步下載圖片
}
public void useDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache ;
}
public void useDoubleCache(boolean useDoubleCache) {
isUseDoubleCache = useDoubleCache ;
}
}
這樣做固然有些得意,問題依舊有:每次在程序中加入新的緩存實現時都需要修改ImageLoader類,然後通過一個布爾變量來讓用戶使用哪種緩存,因此,就使得在ImageLoader中存在各種if-else判斷,通過這些判斷來確定使用哪種緩存。隨着這些邏輯的引入,代碼變得越來越複雜、脆弱,如果小民一不小心寫錯了某個if條件(條件太多,這是很容易出現的),那就需要更多的時間來排除。整個ImageLoader類也會變得越來越臃腫。最重要的是用戶不能自己實現緩存注入到ImageLoader中,可擴展性可是框架的最重要特性之一。
public class ImageLoader {
// 圖片緩存
ImageCache mImageCache = new MemoryCache();
// 線程池,線程數量爲CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
// 注入緩存實現
public void setImageCache(ImageCache cache) {
mImageCache = cache;
}
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = mImageCache.get(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 圖片沒緩存,提交到線程池中下載圖片
submitLoadRequest(imageUrl, imageView);
}
private void submitLoadRequest(final String imageUrl,
final ImageView imageView) {
imageView.setTag(imageUrl);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(imageUrl)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(imageUrl, bitmap);
}
});
}
public Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection)
url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
ImageCache.java
public interface ImageCache {
public Bitmap get(String url);
public void put(String url, Bitmap bmp);
}
// 內存緩存MemoryCache類
public class MemoryCache implements ImageCache {
private LruCache<String, Bitmap> mMemeryCache;
public MemoryCache() {
// 初始化LRU緩存
}
@Override
public Bitmap get(String url) {
return mMemeryCache.get(url);
}
@Override
public void put(String url, Bitmap bmp) {
mMemeryCache.put(url, bmp);
}
}
// SD卡緩存DiskCache類
public class DiskCache implements ImageCache {
@Override
public Bitmap get(String url) {
return null/* 從本地文件中獲取該圖片 */;
}
@Override
public void put(String url, Bitmap bmp) {
// 將Bitmap寫入文件中
}
}
// 雙緩存DoubleCache類
public class DoubleCache implements ImageCache{
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new DiskCache();
// 先從內存緩存中獲取圖片,如果沒有,再從SD卡中獲取
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
// 將圖片緩存到內存和SD卡中
public void put(String url, Bitmap bmp) {
mMemoryCache.put(url, bmp);
mDiskCache.put(url, bmp);
}
}
ImageLoader類中增加了一個setImageCache(ImageCache cache)函數,用戶可以通過該函數設置緩存實現,也就是通常說的依賴注入。下面就看看是如何設置緩存實現的:
ImageLoader imageLoader = new ImageLoader() ;
// 使用內存緩存
imageLoader.setImageCache(new MemoryCache());
// 使用SD卡緩存
imageLoader.setImageCache(new DiskCache());
// 使用雙緩存
imageLoader.setImageCache(new DoubleCache());
// 使用自定義的圖片緩存實現
imageLoader.setImageCache(new ImageCache() {
@Override
public void put(String url, Bitmap bmp) {
// 緩存圖片
}
@Override
public Bitmap get(String url) {
return null/*從緩存中獲取圖片*/;
}
});