微信公众号: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. Ubuntu使用Windows下的conio.h

    把虚线框里面的内容粘贴进文档文本里面 --------------------------------------------------------------------------------- ...

  2. 利用大白菜制作多系统启动U盘(win+ubuntu+PE+...)

    网上提供的方法很多都过时了,不适用,要不就是讲的不清楚 我结合http://www.xuebuyuan.com/848003.html大神的方案,加以研究,整理出了此篇文章 先看下最终成果: 好了,感 ...

  3. 逆向知识第一讲,IDA的熟悉使用,以及TEB,PEB结构

    逆向知识第一讲,IDA的熟悉使用,以及TEB,PEB结构 一丶熟悉IDA,以及手工制作sig文件. IDA,静态分析工具,网上随便找一个即可下载. 首先,我们写一个可执行EXE,最简单的 使用IDA打 ...

  4. CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装

    CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装 http://www.cnblogs.com/ppoo24/p/4918288.ht ...

  5. Linux_服务器_01_查看公网IP

    在linux终端提示符下,输入以下命令: 精选: curl icanhazip.com/curl ifconfig.mecurl ipecho.net/plain 可以看到下图已经查询到公网IP地址了 ...

  6. Holding Bin-Laden Captive!(1.多重背包 2.母函数)

    Holding Bin-Laden Captive! Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/ ...

  7. jQuery选择器(基本过滤选择器)第三节

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...

  8. 浅析is和as两个关键词在类型转换时的使用

    is检查对象是否兼容与指定类型,返回Boolean值true或者false,值得注意的是,在使用is进行类型转换的时候是永远不会抛出异常的,例如: object o=new Object(); Boo ...

  9. 一起写框架-控制反转(Ioc)概述(二)

    控制反转概述 控制反转(Inversion of Control,英文缩写为IoC),就是将代码的调用的控制权,由调用方转移给被调用方. 如图:修改代码A类的代码,才能将B类的对象换成C类.代码的控制 ...

  10. 利用Python循环(包括while&for)各种打印九九乘法表

    一.for循环打印九九乘法表 #注意:由于缩进在浏览器不好控制,请大家见谅,后续会有图片传入. 1.1 左下角 for i in range(1,10): for j in range(1,i+1): ...