《Android源码设计模式》学习笔记之ImageLoader
微信公众号: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的更多相关文章
- MacOS10.9获取Android源码不完全笔记(2014)
第一步:安装Macports 这个我就不叙述了,网上有无数教程 第二步:创建一个磁盘镜像 1.打开磁盘工具,然后: 第三步:使用Macport安装编译环境 1.打开终端输入以下内容 sudo port ...
- 《Android源码设计模式》--抽象工厂模式
No1: 4种MediaPlayer Factory分别会生成不同的MediaPlayer基类:StagefrightPlayer.NuPlayerDriver.MidiFile和TestPlayer ...
- 《Android源码设计模式》--Builder模式
No1: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 No2: 在Android源码中,最常用到的Builder模式就是AlertDialog.Builder No3: ...
- zepto 源码 $.contains 学习笔记
$.contains(parent,node) 返回值为一个布尔值 ==> boolean parent,node我们需要检查的节点检查父节点是否包含给定的dom节点,如果两者是相同的节点,返 ...
- c++ stl源码剖析学习笔记(一)uninitialized_copy()函数
template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...
- android源码追踪学习 RecipientsEditor
RecipientsEditor 新建短信时输入收接者的editor, public class RecipientsEditor extends MultiAutoCompleteTextView ...
- 《Android源码设计模式》--模板方法模式
No1: 模板方法模式包括:抽象类(其中定义了一系列顺序方法).具体实现类A.具体实现类B 如果子类有实现不一样的细节,重写父类的某个方法即可 No2: AsyncTask对象调用execute方法后 ...
- 《Android源码设计模式》--状态模式--责任链模式--解释器模式--命令模式--观察者模式--备忘录模式--迭代器模式
[状态模式] No1: Wifi设置界面是一个叫做WifiSetting的Fragment实现的 No2: 在不同的状态下对于扫描Wifi这个请求的处理是完全不一样的.在初始状态下扫描请求被直接忽略, ...
- requests源码阅读学习笔记
0:此文并不想拆requests的功能,目的仅仅只是让自己以后写的代码更pythonic.可能会涉及到一部分requests的功能模块,但全看心情. 1.另一种类的初始化方式 class Reques ...
随机推荐
- 读阮一峰老师 es6 入门笔记 —— 第一章
鉴于最近用 vuejs 框架开发项目,其中有很多涉及到 es6 语法不太理解所以便认真地读了一下这本书. 地址:http://es6.ruanyifeng.com/#README 第一章:let ,c ...
- JAVA基础知识总结:三
一.Java语句的执行结构 1.顺序语句 按照顺序从上往下依次执行的语句,中间没有任何的判断和跳转 2.分支语句 根据不同的条件来产生不同的分支 if语句.switch语句 3.循环语句 重复执行某句 ...
- C++虚函数(09)
一旦基类定义了虚函数,该基类的派生类中的同名函数也自动称为虚函数. 虚函数只能是类中的一个成员函数,但不能是静态成员,关键字virtual用于类中该函数的声明中. 关键字virtual指示C++编译器 ...
- 关于CSS 的position定位问题
对于初学者来说,css的position定位问题是比较常见的.之前搞不清楚postion定位是怎么回事,排版一直歪歪斜斜的,老是排不好 css的定位一般来说,分为四种: position:static ...
- LeetCode 229. Majority Element II (众数之二)
Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. The algorit ...
- 主键乱序插入对Innodb性能的影响
主键乱序插入对Innodb性能的影响 在平时的mysql文档学习中我们经常会看到这么一句话: MySQL tries to leave space so that future inserts do ...
- 聊聊 Material Design 里,阴影的那些事儿!
当你的设计师要求你在某个 View 上增加阴影效果,那你只需要认真阅读本文,阴影的问题就不再是问题. 一.前言 设计师的世界,与常人不同,有时候想要扁平化的风格,有时候又想要拟物化的风格.而在 Mat ...
- common lisp和scheme的区别
1. 在Common Lisp 眼中,一个符号的symbol-value 和symbol-function 是不一样的,而Scheme对两者不作区分.在Scheme 里面,变量只有唯一对应的值,它可以 ...
- linux学习(九)set_uid、set_gid、stick_bit、软链接、硬链接
一.set_uid set_uid其实是一种特殊权限,我们看一个文件: [root@iZ25lzba47vZ ~]# ls -l /usr/bin/passwd -rwsr-xr-x. root ro ...
- lua中的require机制
lua中的require机制 为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来.现在看看lua的require的处理流程.1.require机制相关的数据 ...