Rxjava自定义图片缓存框架 仿Picasso
目的
掌握通过例子掌握 Rxjava 的基本使用
Lrucache基本使用
缓存基本设计思路
实现结果
RxImageLoader.with(context).load("http://mmbiz.qpic.cn/mmbiz_png/via3iaqIEsXjVPJs0yFic6tBobapYt55RMYYfP153xMQOKibTuRY7Tg2IdluCeyVoyEVA3k2d84DsolPjNwYyaum2A/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1").into(imageView);
类似Picasso的使用load图片url 地址,into 到 imageview 完成图片加载
设计
先看一下图片2层缓存设计流程,分成内存,本地文件,网络加载文件
RxImageLoader
先把架子搭好
RxImageLoader采用单例模式(使用静态锁,涉及到懒汉,饿汉模式可以了解一下),在 with 方法中使用构造者创建单例
public class RxImageLoader {
public Context context;
public String mUrl;
//各图片缓存实际类
public RequestCreator requestCreator;
static RxImageLoader singletoon;
private RxImageLoader(Builder builder ){
this.context = builder.context;
requestCreator= new RequestCreator(context);
}
//需要修改第二次传入 context 应该修改context,这样会导致 context 释放不掉
public static RxImageLoader with(Context context){
if(singletoon==null){
synchronized (RxImageLoader.class){
if(singletoon==null){
singletoon = new Builder(context).build();
}
}
}
return singletoon;
}
//只是传入 url
public RxImageLoader load(String url){
mUrl = url;
return singletoon;
}
//下载图片加载到 iamgeview 是主要方法
public RxImageLoader into(final ImageView imageView){
//这里面需要实现从多个缓存查找图片加载到 iamgeview 一会再来实现
//将底层缓存加载到优先缓存
// 后期增加缓存加载顺续属于RxImageLoader的功能,设计一个方法返回所有缓存的Observable并将底层缓存加载到优先缓存,缓存加载顺序在这里处理
return singletoon;
}
public static class Builder{
public Context context;
public Builder(Context context){
this.context = context;
}
public RxImageLoader build(){
return new RxImageLoader(this);
}
}
}
RequestCreator
主要完成 返回所有缓存的Observable并将底层缓存加载到优先缓存
实现了三个缓存也可以根据项目实际情况增加更多
public class RequestCreator {
private MemoryCacheObservable memoryCacheObservable;
private DiskCacheObservable diskCacheObservable;
private NetworkCacheObservable networkCacheObservable;
public RequestCreator(Context context) {
//todo 需要优化工厂模式创建
memoryCacheObservable = new MemoryCacheObservable();
diskCacheObservable = new DiskCacheObservable(context);
networkCacheObservable = new NetworkCacheObservable();
}
public Observable<Image> getImageFromMemory(String url){
return memoryCacheObservable.requestImage(url);
}
public Observable<Image> getImageFromDisk(String url){
return diskCacheObservable.requestImage(url)
.filter(new Predicate<Image>() {
@Override
public boolean test(Image image) throws Exception {
return image.getBitmap()!=null;
}
})
.doOnNext(new Consumer<Image>() {
@Override
public void accept(Image image) throws Exception {
memoryCacheObservable.putImage(image);
}
});
}
public Observable<Image> getImageFromNetwork(String url){
return networkCacheObservable.requestImage(url)
.filter(new Predicate<Image>() {
@Override
public boolean test(Image image) throws Exception {
return image.getBitmap()!=null;
}
})
.doOnNext(new Consumer<Image>() {
@Override
public void accept(Image image) throws Exception {
diskCacheObservable.putImage(image);
memoryCacheObservable.putImage(image);
}
});
}
}
AbstractCacheObservable
开始缓存的操作
先构建所有CacheObservable的基类
public abstract class AbstractCacheObservable {
public Observable<Image> requestImage(final String url){
return Observable.create(new ObservableOnSubscribe<Image>() {
@Override
public void subscribe(ObservableEmitter<Image> e) throws Exception {
e.onNext(getImage(url));
e.onComplete();
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
/**
* 保存图片
* @param image
*/
public abstract void putImage(Image image);
/**
* 具体获取 image方法
* @param url
* @return
*/
public abstract Image getImage(String url);
}
NetworkCacheObservable从网络取图片,不需要做保存
public class NetworkCacheObservable extends AbstractCacheObservable {
@Override
public void putImage(Image image) {
}
@Override
public Image getImage(String url) {
Log.d("TAG","getImage from network");
Bitmap bitmap = downloadImage(url);
return new Image(url, bitmap);
}
/**
* 下载图片
* @param url
* @return
*/
private Bitmap downloadImage(String url) {
Bitmap bitmap = null;
InputStream inputStream = null;
try{
final URLConnection con = new URL(url).openConnection();
inputStream = con.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
}catch (IOException e){
e.printStackTrace();
}finally {
if(inputStream !=null){
try{
inputStream.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
return bitmap;
}
}
DiskCacheObservable 本地缓存
使用到的 DiskCache DiskLruCache
可以看一下郭神的Android DiskLruCache完全解析,硬盘缓存的最佳方案
public class DiskCacheObservable extends AbstractCacheObservable {
private DiskLruCache mDiskLruCache;
private Context mContext;
//缓存20m
private long maxSize = 20*1024*1024;
public DiskCacheObservable(Context context){
this.mContext = context;
initDiskLruCache();
}
@Override
public void putImage(final Image image) {
Observable.create(new ObservableOnSubscribe<Image>() {
@Override
public void subscribe(ObservableEmitter<Image> e) throws Exception {
putDataToDiskCache(image);
}
}).subscribeOn(Schedulers.io()).subscribe();
}
/**
* image加入缓存
* @param image
*/
private void putDataToDiskCache(Image image){
try {
String key = DiskCacheUtil.getMd5String(image.getUrl());
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor!=null){
OutputStream outputStream = editor.newOutputStream(0);
if (saveBitmap(image.getBitmap(), outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 保存 bitmap
* @param bitmap
* @param outputStream
* @return
*/
private boolean saveBitmap(Bitmap bitmap, OutputStream outputStream){
boolean b = bitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream);
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return b;
}
@Override
public Image getImage(String url) {
Bitmap bitmap = getDataFromDiskCache(url);
Image image = new Image(url,bitmap);
Log.d("TAG","getImage from Disk"+(image.getBitmap()==null));
return image;
}
//从缓存中取出 bitmap
private Bitmap getDataFromDiskCache(String url){
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapshot =null;
try{
String key = DiskCacheUtil.getMd5String(url);
Log.d("TAG","Disk Cache key"+key);
snapshot = mDiskLruCache.get(key);
if(snapshot!=null){
fileInputStream = (FileInputStream) snapshot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
}
Bitmap bitmap = null;
if(fileDescriptor!=null){
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
return bitmap;
}catch (IOException e){
e.printStackTrace();
}finally {
if(fileDescriptor== null && fileInputStream!=null){
try{
fileInputStream.close();
}catch (IOException e){
}
}
}
return null;
}
//实例化 cache
private void initDiskLruCache(){
try{
File cacheDir = DiskCacheUtil.getDiskCacheDir(this.mContext,"image_cache");
if(!cacheDir.exists()){
cacheDir.mkdirs();
}
int versionCode = DiskCacheUtil.getAppVersionCode(mContext);
mDiskLruCache = DiskLruCache.open(cacheDir,versionCode,1,maxSize);
}catch (Exception e){
e.printStackTrace();
}
}
}
MemoryCacheObservable内存缓存也是用 lru 算法,本质上是 linkedhashmap
曾经,在各大缓存图片的框架没流行的时候。有一种很常用的内存缓存技术:SoftReference 和 WeakReference(软引用和弱引用)。但是走到了 Android 2.3(Level 9)时代,垃圾回收机制更倾向于回收 SoftReference 或 WeakReference 的对象。后来,又来到了 Android3.0,图片缓存在内容中,因为不知道要在是什么时候释放内存,没有策略,没用一种可以预见的场合去将其释放。这就造成了内存溢出。
就当做一个 map 使用就可以
Android开发学习之路-LruCache使用
LruCache 源码解析
public class MemoryCacheObservable extends AbstractCacheObservable {
//获取到应用的最大内存
private int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024);//kb
//设置LruCache的缓存大小
private int cacheSize = maxMemory/8;
private LruCache<String,Bitmap > bitmapLruCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;
}
};
@Override
public void putImage(Image image) {
Log.d("TAG","putImage from Memory"+image.getUrl()+image.getBitmap());
bitmapLruCache.put(image.getUrl(),image.getBitmap());
Log.d("TAG","bitmapLruCache"+ bitmapLruCache.size());
}
@Override
public Image getImage(String url) {
Bitmap bitmap = bitmapLruCache.get(url);
Image image = new Image(url,bitmap);
Log.d("TAG","getImage from Memory"+(image.getBitmap()==null));
Log.d("TAG","bitmapLruCache"+ bitmapLruCache.size());
return image;
}
}
实现了所有缓存之后填一开始留下的坑
public RxImageLoader into(final ImageView imageView){
//todo 需要优化 获取CacheObservable的方法
Observable.concat(requestCreator.getImageFromMemory(mUrl),requestCreator.getImageFromDisk(mUrl),requestCreator.getImageFromNetwork(mUrl))
.filter(new Predicate<Image>() {
@Override
public boolean test(Image image) throws Exception {
//
Log.d("TAG","filter");
if(image.getBitmap()==null){
return false;
}
return true;
}
})
.firstElement().toObservable()
.subscribe(new Observer<Image>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Image image) {
Log.d("TAG",image.getUrl());
imageView.setImageBitmap(image.getBitmap());
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
Log.d("TAG","onComplete");
}
});
return singletoon;
}
其他
Imag保存 bitmap 和 url
public class Image {
private String url;
public Image(String url, Bitmap bitmap) {
this.url = url;
this.bitmap = bitmap;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
private Bitmap bitmap;
}
DiskCache构建用到的方法
public class DiskCacheUtil {
/**
* 获取缓存目录
*
* @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.getCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
Log.d("DiskCacheUtil",cachePath);
return new File(cachePath + File.separator + uniqueName);
}
/**
* 获取 version code
*
* @param context
* @return
*/
public static int getAppVersionCode(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
public static String getMd5String(String key) {
String cacheKey;
try {
final MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(key.getBytes());
cacheKey = bytesToHexString(messageDigest.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();
}
}
最后
需要优化缓存CacheObservable构建方法,开发过程中设计到 Rxjava2 升级之后的操作符可以看一下RxJava1 升级到 RxJava2 所踩过的坑
项目所有代码可以在 guthub 下载 我的github,后来的优化也会更新