Rxjava自定义图片缓存框架 仿Picasso

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,后来的优化也会更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值