1. 程式人生 > >《Android源碼設計模式》學習筆記之ImageLoader

《Android源碼設計模式》學習筆記之ImageLoader

format trac dir nload download 活性 thread 大內存 idg

微信公眾號:CodingAndroid
cnblog:http://www.cnblogs.com/angel88/
CSDN:http://blog.csdn.net/xinpengfei521

  • 需求:設計一個圖片加載工具類。
  • 要求:職責單一、可擴展性強、實現三級緩存,遵循開閉原則。

1.改造前原始代碼

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public class ImageLoader {
// 圖片緩存
LruCache<String, Bitmap> mImageCache;
// 線程池,線程池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
public ImageLoader() {
initImageCache();
}
/**
* 初始化圖片緩存大小
*/
private void initImageCache() {
// 計算可使用的最大內存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取1/4的可用內存作為緩存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
/**
* 加載顯示圖片
*
* @param url
* @param imageView
*/
public void displayImage(final String url, final ImageView imageView) {
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);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

2.遵循單一原則將原始類分為加載和緩存兩個類(功能)

2.1.圖片加載類為:

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public class ImageLoader {
// 圖片緩存
ImageCache mImageCache = new ImageCache();
// 線程池,線程池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
/**
* 加載顯示圖片
*
* @param url
* @param imageView
*/
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);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

2.2.緩存類為

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by xpf on 2017/10/22 :)
* Function:圖片緩存類
*/
public class ImageCache {
// 圖片LRU緩存
LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
/**
* 初始化圖片緩存大小
*/
private void initImageCache() {
// 計算可使用的最大內存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取1/4的可用內存作為緩存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
}

3.提高擴展性,增加SD卡緩存

以上將代碼的功能分開了,邏輯更清晰了,職責也單一了,但是可擴展性還是比較差,接下來進行增加SD卡緩存。

3.1增加SD卡緩存類

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public class DiskCache {
static String cacheDir = "/sdcard/cache/image/";
/**
* 從SD卡中讀取
*
* @param url
* @return
*/
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
/**
* 緩存到SD卡中
*
* @param url
* @param bmp
*/
public void put(String url, Bitmap bmp) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(cacheDir + url);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

3.2ImageLoader中增加一個boolean值來設置使用哪種緩存方式

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public class ImageLoader {
// 內存緩存
ImageCache mImageCache = new ImageCache();
// SD卡緩存
DiskCache mDiskCache = new DiskCache();
// 是否使用SD卡緩存
boolean isUseDiskCache = false;
// 線程池,線程池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
/**
* 加載顯示圖片
*
* @param url
* @param imageView
*/
public void displayImage(final String url, final ImageView imageView) {
// 優先從緩存中加載
Bitmap bitmap = isUseDiskCache ? mImageCache.get(url) : mDiskCache.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);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 設置是否使用SD卡緩存
*
* @param useDiskCache
*/
public void setUseDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
}

4.進一步改造,使用雙緩存,優先使用內存加載,如果無再使用SD卡緩存

以上代碼修改雖然增加了SD卡緩存,但是為了節省用戶的流量及加載速度我們應該設計成優先使用內存加載,如果無再使用SD卡緩存。

4.1增加雙緩存類

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public class DoubleCache {
ImageCache mMemoryCache = new ImageCache();
DiskCache mDiskCache = new DiskCache();
/**
* 優先使用內存加載,如果無再使用SD卡緩存
*
* @param url
* @return
*/
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
/**
* 將圖片緩存到內存和SD卡中
*
* @param url
* @param bitmap
*/
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}

4.2ImageLoader增加雙緩存配置

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
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());
/**
* 加載顯示圖片
*
* @param url
* @param imageView
*/
public void displayImage(final String url, final ImageView imageView) {
// 優先從緩存中加載
Bitmap bitmap = null;
if (isUseDoubleCache) {
bitmap = mDoubleCache.get(url);
} else if (isUseDiskCache) {
bitmap = mDiskCache.get(url);
} else {
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);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 設置是否使用SD卡緩存
*
* @param useDiskCache
*/
public void setUseDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
/**
* 設置是否使用雙緩存
*
* @param useDoubleCache
*/
public void setUseDoubleCache(boolean useDoubleCache) {
isUseDoubleCache = useDoubleCache;
}
}

以上改造總算可以了,但是這樣每次增加緩存策略都要修改源代碼,這樣很有可能引入bug,所以我們的原則是要對修改關閉,對擴展開放,這樣以後有新需求的時候我們就可以使用擴展的方法來實現。

5.抽象公共方法的接口

5.1接口抽取

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public interface ImageCache {
Bitmap get(String url);
void put(String url, Bitmap bitmap);
}

5.2ImageLoader註入接口的實現類

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public class ImageLoader {
ImageCache mImageCache = new MemoryCache();
// 線程池,線程池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
/**
* 註入緩存實現
*
* @param mImageCache
*/
public void setmImageCache(ImageCache mImageCache) {
this.mImageCache = mImageCache;
}
/**
* 加載顯示圖片
*
* @param url
* @param imageView
*/
public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 圖片沒有緩存提交到線程池中下載
submitLoadRequest(url, imageView);
}
private void submitLoadRequest(final String url, final ImageView imageView) {
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);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}

5.3內存緩存、SD卡緩存和雙緩存分別實現接口

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
/**
* Created by xpf on 2017/10/22 :)
* Function:
*/
public class DoubleCache implements ImageCache {
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new DiskCache();
/**
* 優先使用內存加載,如果無再使用SD卡緩存
*
* @param url
* @return
*/
@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
/**
* 將圖片緩存到內存和SD卡中
*
* @param url
* @param bitmap
*/
@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}

內存緩存、SD卡緩存實現同上。

6.外部調用及設置緩存策略

private void loadImage() {
ImageLoader imageLoader = new ImageLoader();
// 使用內存緩存
imageLoader.setmImageCache(new MemoryCache());
// 使用SD卡緩存
imageLoader.setmImageCache(new DiskCache());
// 使用雙緩存
imageLoader.setmImageCache(new DoubleCache());
// 使用自定義的圖片緩存
imageLoader.setmImageCache(new ImageCache() {
@Override
public Bitmap get(String url) {
return null;
}
@Override
public void put(String url, Bitmap bitmap) {
}
});
String imageUrl = "http://p1.meituan.net/160.0.80/xianfu/5e369ac9d6aa54125ad1b6562282b2ca36024.jpeg";
imageLoader.displayImage(imageUrl, imageView);
}

經過上述代碼的重構,我們可以通過setImageCache(ImageCache cache)方法註入不同的緩存實現,來使得ImageLoader更簡單、健壯、擴展性好靈活性也更高。以上三種緩存圖片的具體實現完全不一樣,但是它們都有一個共同的特點是都實現了ImageCache接口。當用戶需要增加一種新的緩存策略時,我們只需新建一個實現ImageCache接口等待類就可以了,這樣就實現了千變萬化的緩存策略,並且新擴展的策略不會影響導致ImageLoader類的修改,這正是體現了“對修改關閉,對擴展開放的”原則,所以,我們在設計寫代碼的時候應該認真地進行思考,希望大家一起思考,一起學習,有所成長!

源碼鏈接:https://github.com/xinpengfei520/MyImageLoader

如果本文對你有幫助,歡迎大家點贊、評論,碼字不易,再小的支持也是對博主的莫大鼓勵!

今天的分享就到這裏註明,謝謝!


聲明:文中部分代碼摘抄自《Android源碼設計模式》一書。

註:本文由博主原創,轉載請註明出處,謝謝!

若在使用過程中遇到什麽問題,或有好提議,歡迎在下方留言、評論,或者關註我的公眾號“CodingAndroid”留言。

技術分享圖片

《Android源碼設計模式》學習筆記之ImageLoader