寸阳分阴须爱惜,休负春色与时光。这篇文章主要讲述Android实战——RxJava2解锁图片三级缓存框架相关的知识,希望能为你提供帮助。
Rxjava2解锁图片三级缓存框架
本篇文章包括以下内容前言 RxJava2作为如今那么主流的技术, 不学习学习都不行了, 本篇文章需要你有RxJava2的基础, 如果需要对RxJava2学习的同学, 可以关注我的博客, 查看Android实战——RxJava2+ Retrofit+ RxBinding解锁各种新姿势 。项目代码实现模仿Picasso, 大伙们可以看下最后的代码效果, 那么废话不多说, Hensen老师开车啦
- 前言
- 图片三级缓存的介绍
- 框架结构目录的介绍
- 构建项目整体框架
- 实现图片三级缓存
- 演示效果
- 源码下载
- 结语
RxImageLoader.with(TextImageLoaderActivity.this).load(url).into(iv);
图片三级缓存的介绍 图片的三级缓存很多同学可能已经掌握了, 很多同学可能也听说过, 那么这里就简单的来回顾一下你们学习的三级缓存机制是否正确吧

文章图片
首先, 这个图就是表示三级缓存机制的所有, 其三级分别是( 按先后顺序)
- 内存缓存( 一级) : 如果内存存在我们的缓存信息, 直接用它
- 文件缓存( 二级) : 如果内存不存在我们的缓存信息, 那么就查看是否有我们的缓存文件, 如果有, 直接使用它。同时, 将其缓存到内存中
- 网络缓存( 三级) : 如果文件不存在缓存文件, 直接从网络上下载, 直接使用它。同时, 将其缓存到文件和内存中
- 内存缓存: 采用Google自带的LruCache进行缓存
- 文件缓存: 采用Github上没有被Google收录却被Google认证的DiskLruCache
- 网络缓存: 通过io流的Stream进行文件的读写
框架结构目录的介绍 框架的结构看似复杂, 其实内容不多, 实现起来也不难

文章图片
下面对框架结构目录进行介绍, 图片上显示了目录结构之间的关系

文章图片
- TextImageLoaderActivity: 是我们的测试界面
- ImageBean: 使用RxJava的onNext传递的Bean对象
- CacheObservable: 三级缓存的父类
- DiskCacheObservable: 文件缓存Observable
- MemoryCacheObservable: 内存缓存Observable
- NetworkCacheObservable: 网络缓存Observable
- RequestCreator: 对三级缓存进行统一管理
- RxImageLoader: 使用RequestCreator管理类进行缓存机制的调用
- DiskCacheUtils: 文件缓存的工具类
导入我们需要的依赖库
//rxjava
compile "
io.reactivex.rxjava2:rxjava:2.0.8"
compile '
io.reactivex.rxjava2:rxandroid:2.0.1'
//disklrucache
compile '
com.jakewharton:disklrucache:2.0.2'
2、Bean对象的创建
我们以key、value的形式来创建该Bean对象
public class ImageBean {
private String url;
private Bitmap bitmap;
public ImageBean(Bitmap bitmap, String url) {
this.bitmap =
bitmap;
this.url =
url;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap =
bitmap;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url =
url;
}
}
3、缓存类的创建
public abstract class CacheObservable {/**
* 获取缓存数据
* @
param url
* @
return
*/
public Observable<
ImageBean>
getImage(final String url) {
return Observable.create(new ObservableOnSubscribe<
ImageBean>
() {
@
Override
public void subscribe(@
NonNull ObservableEmitter<
ImageBean>
e) throws Exception {
if (!e.isDisposed()) {
ImageBean image =
getDataFromCache(url);
e.onNext(image);
e.onComplete();
}
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}/**
* 取出缓存数据
* @
param url
* @
return
*/
public abstract ImageBean getDataFromCache(String url);
/**
* 缓存数据
* @
param image
*/
public abstract void putDataToCache(ImageBean image);
}
这里我们的三级缓存只要继承至该类, 实现存入缓存和取出缓存的操作就可以了
public class DiskCacheObservable extends CacheObservable {
@
Override
public ImageBean getDataFromCache(String url) {
return null;
}
@
Override
public void putDataToCache(final ImageBean image) {}
}
public class MemoryCacheObservableextends CacheObservable {
@
Override
public ImageBean getDataFromCache(String url) {
return null;
}
@
Override
public void putDataToCache(final ImageBean image) {}
}
public class NetworkCacheObservable extends CacheObservable {
@
Override
public ImageBean getDataFromCache(String url) {
return null;
}
@
Override
public void putDataToCache(final ImageBean image) {}
}
这里也是我们最后一步所要实现的逻辑功能, 这里我们先把框框搭建好
4、管理缓存类创建
public class RequestCreator {
public MemoryCacheObservable memoryCacheObservable;
public DiskCacheObservable diskCacheObservable;
public NetworkCacheObservable networkCacheObservable;
public RequestCreator(Context context) {
memoryCacheObservable =
new MemoryCacheObservable();
diskCacheObservable =
new DiskCacheObservable();
networkCacheObservable =
new NetworkCacheObservable();
}public Observable<
ImageBean>
getImageFromMemory(String url) {
return memoryCacheObservable.getImage(url);
}public Observable<
ImageBean>
getImageFromDisk(String url) {
return diskCacheObservable.getImage(url);
}public Observable<
ImageBean>
getImageFromNetwork(String url) {
return networkCacheObservable.getImage(url);
}
}
5、模拟Picasso源码, 使用构造者模式创建我们的RxImageLoader
public class RxImageLoader {static RxImageLoader singleton;
private String mUrl;
private RequestCreator requestCreator;
//防止用户可以创建该对象
private RxImageLoader(Builder builder) {
requestCreator =
new RequestCreator(builder.mContext);
}public static RxImageLoader with(Context context) {
if (singleton =
=
null) {
synchronized (RxImageLoader.class) {
if (singleton =
=
null) {
singleton =
new Builder(context).build();
}
}
}
return singleton;
}public RxImageLoader load(String url) {
this.mUrl =
url;
return singleton;
}public void into(final ImageView imageView) {
Observable
.concat(
requestCreator.getImageFromMemory(mUrl),
requestCreator.getImageFromDisk(mUrl),
requestCreator.getImageFromNetwork(mUrl)
)
.first(new ImageBean(null,mUrl)).toObservable()
.subscribe(new Observer<
ImageBean>
() {
@
Override
public void onSubscribe(Disposable d) {}@
Override
public void onNext(ImageBean imageBean) {
if (imageBean.getBitmap() !=
null) {
imageView.setImageBitmap(imageBean.getBitmap());
}
}@
Override
public void onError(Throwable e) {
e.printStackTrace();
}@
Override
public void onComplete() {
Log.e("
onComplete"
, "
onComplete"
);
}
});
}public static class Builder {private Context mContext;
public Builder(Context mContext) {
this.mContext =
mContext;
}public RxImageLoader build() {
return new RxImageLoader(this);
}
}
}
上面代码做了哪些事
- 采用双判空的单例模式
- 可采用链式编程方式
- 使用RxJava的concat和first方法
- concat方法表示将缓存机制Observable进行有序的连接, 按顺序读取内存缓存, 文件缓存, 网络缓存
- first方法表示判断, 如果IamgeBean中的bitmap为空, 那么跳过此次连接, 例如, requestCreator.getImageFromMemory(mUrl)获取的bitmap为空, 那么直接跳过这次concat连接, 进行requestCreator.getImageFromDisk(mUrl)操作, 直到bitmap不为空则程序继续往下执行, 断开concat的连接
我们简单的使用一个按钮加载图片就好了
public class TextImageLoaderActivity extends AppCompatActivity {ImageView iv;
Button bt;
String url;
@
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_text_image_loader);
iv =
(ImageView) findViewById(R.id.iv);
bt =
(Button) findViewById(R.id.bt);
url =
"
http://img2.baa.bitautotech.com/usergroup/editor_pic/2017/3/22/694494c2f3544226ae911bf86b4e2bcc.png"
;
bt.setOnClickListener(new View.OnClickListener() {
@
Override
public void onClick(View v) {
RxImageLoader.with(TextImageLoaderActivity.this).load(url).into(iv);
}
});
}
}
实现图片三级缓存 做好了框架的框框, 下面就是对具体的DiskCacheObservable、MemoryCacheObservable、NetworkCacheObservable进行对应的方法实现就可以了
1、内存缓存
内存缓存最简单了, 只要放入到LruCache即可
public class MemoryCacheObservable extends CacheObservable {private int maxMemory =
(int) (Runtime.getRuntime().maxMemory() / 1024);
private int cacheSize =
maxMemory / 4;
private LruCache<
String, Bitmap>
mLruCache =
new LruCache<
String, Bitmap>
(cacheSize) {
@
Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
@
Override
public ImageBean getDataFromCache(String url) {
Log.e("
getDataFromCache"
, "
getDataFromMemoryCache"
);
Bitmap bitmap =
mLruCache.get(url);
return new ImageBean(bitmap, url);
}@
Override
public void putDataToCache(ImageBean image) {
mLruCache.put(image.getUrl(), image.getBitmap());
}
}
2、文件缓存
文件缓存涉及DiskLruCache的使用、文件下载和文件名用MD5加密
public class DiskCacheObservable extends CacheObservable {private Context mContext;
private DiskLruCache mDiskLruCache;
private final int maxSize =
10 * 1024 * 1024;
public DiskCacheObservable(Context mContext) {
this.mContext =
mContext;
initDiskLruCache();
}@
Override
public ImageBean getDataFromCache(String url) {
Log.e("
getDataFromCache"
,"
getDataFromDiskCache"
);
Bitmap bitmap =
getDataFromDiskLruCache(url);
return new ImageBean(bitmap, url);
}@
Override
public void putDataToCache(final ImageBean image) {
//由于网络读取需要在子线程中执行
Observable.create(new ObservableOnSubscribe<
ImageBean>
() {
@
Override
public void subscribe(@
NonNull ObservableEmitter<
ImageBean>
e) throws Exception {
putDataToDiskLruCache(image);
}
}).subscribeOn(Schedulers.io()).subscribe();
}public void initDiskLruCache() {
File cacheDir =
DiskCacheUtils.getDiskCacheDir(mContext, "
our_cache"
);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
int versionCode =
DiskCacheUtils.getAppVersion(mContext);
try {
//这里需要注意参数二:
缓存版本号,
只要不同版本号,
缓存都会被清除,
重新使用新的
mDiskLruCache =
DiskLruCache.open(cacheDir, versionCode, 1, maxSize);
} catch (IOException e) {
e.printStackTrace();
}
}/**
* 获取文件缓存
* @
param url
* @
return
*/
private Bitmap getDataFromDiskLruCache(String url) {
Bitmap bitmap =
null;
FileDescriptor fileDescriptor =
null;
FileInputStream fileInputStream =
null;
try {
final String key =
DiskCacheUtils.getMD5String(url);
DiskLruCache.Snapshot snapshot =
mDiskLruCache.get(key);
if (snapshot !=
null) {
fileInputStream =
(FileInputStream) snapshot.getInputStream(0);
fileDescriptor =
fileInputStream.getFD();
}
if (fileDescriptor !=
null) {
bitmap =
BitmapFactory.decodeStream(fileInputStream);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream !=
null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}/**
* 缓存文件数据
* @
param imageBean
*/
private void putDataToDiskLruCache(ImageBean imageBean) {
try {
String key =
DiskCacheUtils.getMD5String(imageBean.getUrl());
DiskLruCache.Editor editor =
mDiskLruCache.edit(key);
if (editor !=
null) {
OutputStream outputStream =
editor.newOutputStream(0);
boolean isSuccess =
downloadUrlToStream(imageBean.getUrl(), outputStream);
if (isSuccess) {
editor.commit();
} else {
editor.abort();
}
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}/**
* 下载文件
* @
param urlString
* @
param outputStream
* @
return
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection =
null;
BufferedOutputStream out =
null;
BufferedInputStream in =
null;
try {
final URL url =
new URL(urlString);
urlConnection =
(HttpURLConnection) url.openConnection();
in =
new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out =
new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b =
in.read()) !=
-1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection !=
null) {
urlConnection.disconnect();
}
try {
if (out !=
null) {
out.close();
}
if (in !=
null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}}
这里用到一个DiskLruCacheUtils
public class DiskCacheUtils {/**
* 获取缓存路径
* @
param context
* @
param uniqueName
* @
return
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath =
context.getExternalCacheDir().getPath();
} else {
cachePath =
context.getCacheDir().getPath();
}
return new File(cachePath +
File.separator +
uniqueName);
}/**
* 获取App版本号
* @
param context
* @
return
*/
public static int getAppVersion(Context context) {
try {
PackageInfo info =
context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}/**
* 获取加密后的MD5
* @
param key
* @
return
*/
public static String getMD5String(String key) {
String cacheKey;
try {
final MessageDigest mDigest =
MessageDigest.getInstance("
MD5"
);
mDigest.update(key.getBytes());
cacheKey =
bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey =
String.valueOf(key.hashCode());
}
return cacheKey;
}private static String bytesToHexString(byte[] bytes) {
StringBuilder sb =
new StringBuilder();
for (int i =
0;
i <
bytes.length;
i+
+
) {
String hex =
Integer.toHexString(0xFF &
bytes[i]);
if (hex.length() =
=
1) {
sb.append('
0'
);
}
sb.append(hex);
}
return sb.toString();
}}
3、网络缓存
网络缓存只需要下载文件, 不需要实现缓存数据的方法即可
public class NetworkCacheObservable extends CacheObservable {
@
Override
public ImageBean getDataFromCache(String url) {
Log.e("
getDataFromCache"
, "
getDataFromNetworkCache"
);
Bitmap bitmap =
downloadImage(url);
return new ImageBean(bitmap, url);
}@
Override
public void putDataToCache(ImageBean image) {}/**
* 下载文件
* @
param url
* @
return
*/
public Bitmap downloadImage(String url) {
Bitmap bitmap =
null;
InputStream inputStream =
null;
try {
URL imageUrl =
new URL(url);
URLConnection urlConnection =
(HttpURLConnection) imageUrl.openConnection();
inputStream =
urlConnection.getInputStream();
bitmap =
BitmapFactory.decodeStream(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream !=
null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
}
4、管理缓存
获取三级缓存逻辑实现完之后, 就应该对管理我们的缓存, 进行对应的缓存操作
public class RequestCreator {public MemoryCacheObservable memoryCacheObservable;
public DiskCacheObservable diskCacheObservable;
public NetworkCacheObservable networkCacheObservable;
public RequestCreator(Context context) {
memoryCacheObservable =
new MemoryCacheObservable();
diskCacheObservable =
new DiskCacheObservable(context);
networkCacheObservable =
new NetworkCacheObservable();
}public Observable<
ImageBean>
getImageFromMemory(String url) {
return memoryCacheObservable.getImage(url)
.filter(new Predicate<
ImageBean>
() {
@
Override
public boolean test(@
NonNull ImageBean imageBean) throws Exception {
Bitmap bitmap =
imageBean.getBitmap();
return bitmap !=
null;
}
});
}public Observable<
ImageBean>
getImageFromDisk(String url) {return diskCacheObservable.getImage(url)
.filter(new Predicate<
ImageBean>
() {
@
Override
public boolean test(@
NonNull ImageBean imageBean) throws Exception {
Bitmap bitmap =
imageBean.getBitmap();
return bitmap !=
null;
}
}).doOnNext(new Consumer<
ImageBean>
() {
@
Override
public void accept(@
NonNull ImageBean imageBean) throws Exception {
//缓存内存
memoryCacheObservable.putDataToCache(imageBean);
}
});
}public Observable<
ImageBean>
getImageFromNetwork(String url) {
return networkCacheObservable.getImage(url)
.filter(new Predicate<
ImageBean>
() {
@
Override
public boolean test(@
NonNull ImageBean imageBean) throws Exception {
Bitmap bitmap =
imageBean.getBitmap();
return bitmap !=
null;
}
})
.doOnNext(new Consumer<
ImageBean>
() {
@
Override
public void accept(@
NonNull ImageBean imageBean) throws Exception {
//缓存文件和内存
diskCacheObservable.putDataToCache(imageBean);
memoryCacheObservable.putDataToCache(imageBean);
}
});
}
}
演示效果 1、首次运行程序, 没有任何缓存, 当我们连续点击2次按钮时

文章图片
可以看到其运行的顺序
- 第一次点击按钮可以先从内存和文件获取图片, 发现没有, 再从网络获取图片
- 第二次点击按钮从内存获取

文章图片
可以看到其运行的顺序
- 第一次点击按钮先从内存获取, 发现没有, 再从文件获取图片
- 第二次点击按钮从内存获取
结语 【Android实战——RxJava2解锁图片三级缓存框架】各位同学可以下载源码进行阅读, 最好自己手写一遍, 你会更深刻体会到RxJava的好处和掌握图片的三级缓存机制, 如果看不懂的同学不要气馁, 多看几遍就会了。喜欢我的朋友可以关注我的博客, 一定会有你想要学习的知识
推荐阅读
- Android Canvas之Path操作
- appium使用教程(一 环境搭建)-------------2.安装部署
- 在Android Studio中打开Android Device Monitor时报错的解决方法
- 一键生成 Android 录屏 gif 的脚本
- 迅为开发板知识库-4418开发板编译Android镜像内容分享
- Android底层有一定的认识,研究过相关的Android源码
- 安卓工程的启动过程
- Android性能优化的一些方案
- Android开发菜单以及子菜单