微信公众号: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的更多相关文章

  1. MacOS10.9获取Android源码不完全笔记(2014)

    第一步:安装Macports 这个我就不叙述了,网上有无数教程 第二步:创建一个磁盘镜像 1.打开磁盘工具,然后: 第三步:使用Macport安装编译环境 1.打开终端输入以下内容 sudo port ...

  2. 《Android源码设计模式》--抽象工厂模式

    No1: 4种MediaPlayer Factory分别会生成不同的MediaPlayer基类:StagefrightPlayer.NuPlayerDriver.MidiFile和TestPlayer ...

  3. 《Android源码设计模式》--Builder模式

    No1: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 No2: 在Android源码中,最常用到的Builder模式就是AlertDialog.Builder No3: ...

  4. zepto 源码 $.contains 学习笔记

    $.contains(parent,node)  返回值为一个布尔值 ==> boolean parent,node我们需要检查的节点检查父节点是否包含给定的dom节点,如果两者是相同的节点,返 ...

  5. c++ stl源码剖析学习笔记(一)uninitialized_copy()函数

    template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...

  6. android源码追踪学习 RecipientsEditor

    RecipientsEditor 新建短信时输入收接者的editor, public class RecipientsEditor extends MultiAutoCompleteTextView  ...

  7. 《Android源码设计模式》--模板方法模式

    No1: 模板方法模式包括:抽象类(其中定义了一系列顺序方法).具体实现类A.具体实现类B 如果子类有实现不一样的细节,重写父类的某个方法即可 No2: AsyncTask对象调用execute方法后 ...

  8. 《Android源码设计模式》--状态模式--责任链模式--解释器模式--命令模式--观察者模式--备忘录模式--迭代器模式

    [状态模式] No1: Wifi设置界面是一个叫做WifiSetting的Fragment实现的 No2: 在不同的状态下对于扫描Wifi这个请求的处理是完全不一样的.在初始状态下扫描请求被直接忽略, ...

  9. requests源码阅读学习笔记

    0:此文并不想拆requests的功能,目的仅仅只是让自己以后写的代码更pythonic.可能会涉及到一部分requests的功能模块,但全看心情. 1.另一种类的初始化方式 class Reques ...

随机推荐

  1. 什么是git?window下安装git

    一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以 ...

  2. 神经网络JOONE的实践

    什么是joone Joone是一个免费的神经网络框架来创建,训练和测试人造神经网络.目标是为最热门的Java技术创造一个强大的环境,为热情和专业的用户. Joone由一个中央引擎组成,这是Joone开 ...

  3. Spring批量更新batchUpdate提交和Hibernate批量更新executeUpdate

    1:先看hibernate的批量更新处理. 版本背景:hibernate 5.0.8 applicationContext.xml 配置清单: <?xml version="1.0&q ...

  4. cocoapods导入第三方库提示RPC failed curl 18 transfer

    错误提示: error: RPC failed; curl 18 transfer closed with outstanding read data remaining fatal: The rem ...

  5. 【转】scatterlist && DMA

    原文:scatterlist && DMA DMA是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制.使用DMA可以是系统CPU从实际的IO数据传输过程中摆脱出 ...

  6. 想要写出高性能sql语句,你得记住这些……

    1.在from子句中包含多个表名时,必须选择记录条数最少的表作为基础表:若有三个以上的表连接查询,那就需要选择交叉表作为基础表,交叉表指被其他表引用的表. 2.使用exists代替in.使用not e ...

  7. js math对象总结

    1:  Math 对象用于执行数学任务. 2:Math 对象并不像 Date 和 String 那样是对象的类,因此没有构造函数 Math(), Math.sin() 这样的函数只是函数 3:通过把 ...

  8. 忘记root密码,进入单用户模式修改密码

    进入单用户模式 rhel61.在系统数秒时,按下键,进入到系统引导菜单 中2.选择系统后 按“e”键 选择kernel后 按“e”键 后空格 1+回车 b:启动系统 进入到单用户模式 rhel71.在 ...

  9. 微信小程序获取微信的openid

    通过登录接口获取登录凭证,然后通过request请求后台获取openid,需要把后台域名放到小程序后台的request 合法域名内: 1.wx.login  获取登录凭证 2.wx.request 发 ...

  10. css 权重 特殊性

    选择器的特异性由 CSS2 规范定义如下:     !important的规则比其他的权值都大     p{color: red !important} 如果声明来自于“style”属性,而不是带有选 ...