二:开闭原则(Open Close Principle)
定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改时封闭的。(虽然在实战中修改原有代码,扩展代码往往是难以避免的)
遵循开闭原则的重要手段应该是通过抽象!!!
承接与上一篇文章,单一职责原则:点击打开链接
问题代码:
ImageLoader.java
/**
* 图片加载类
*/
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) ;
}
}
于是加入DiskCache.java
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中,可扩展性可是框架的最重要特性之一。
于是通过抽象来达到程序的可扩展性,如图所示:
具体实现:
ImageLoader.java
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/*从缓存中获取图片*/;
}
});