用RxJava实现一个ImageLoader

原创 2016年08月29日 10:31:35

在看这篇文章已经假设你对RxJava有了一点点了解,如果你都不知道RxJava是啥,戳我,这篇文章是RxJava入门很好的资料。

RxJava的流式编程方式确实让有些复杂的代码简化不少,提高了代码的可阅读性,后期维护起来自然也方便很多。一开始只是看RxJava的文档确实很抽象,面对一大堆操作符,各种不知道什么鬼东西的概念,看的云里雾里的,内心一万头草泥马奔过,摔!所以还是做一个Demo来学习是比较高效的学习方式。

在这个前提下,我就开始着手写了一个ImageLoader。之所以是做ImageLoader,是因为最近也正好在看主席的《Android开发艺术探索》,其中一章正好是实现了一个ImageLoader,书里的实现方式是线程池和Handler,耗时的加载图片任务在线程池中执行,执行完成后发送消息到Handler,给ImageView设置图片在主线程中完成。基本设计思路和AsyncTask一致,对这块不懂的可以去看下AsyncTask的源码,在此就不展开分析了,善用搜索。

这种类似AsyncTask的实现看着代码很繁琐,用RxJava来代替Handler和线程池实现线程切换和异步加载便捷很多,代码的逻辑看起来也舒服多了。

在贴上代码之前还是先讲下ImageLoader实现的大体思路吧,首先加载图片需要内存缓存和磁盘缓存功能,分别使用了LruCache和DiskLruCache,并且需要图片压缩的功能,压缩功能是BitmapFactory解析bitmap时通过更改Options中的inSampleSize(采样率)实现的。加载图片入口方法如下:

   public void bindImage(ImageView imageView, String url, int reqWidth, int reqHeight) {
        String key = hashKeyFormUrl(url);
        try {
            if (mLruCache.get(key) != null) {
                bindImageFromMemo(imageView, key); //从内存缓存中加载
            }
            if (mDiskLruCache.get(key) != null) {
                bindImageFromDisk(imageView, key, reqWidth, reqHeight); //从磁盘缓存中加载
            } else {
                downloadImageFromHttp(imageView, url, reqWidth, reqHeight);  //从网络下载图片
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

想必不用过多解释这个加载流程了吧,依次从内存缓存,磁盘缓存,网络加载图片。这里以从网络加载图片代码逻辑为例,对比使用RxJava和不使用的区别,大家自行感受区别。代码具体逻辑是从网络加载图片,并将下载好的图片存储到磁盘缓存中,并且将压缩过的bitmap设置给imageview。。首先演示使用线程池加Handler的实现方式:

    private Handler mHandler = new Handler(getMainLooper()){//主线程中的Handler,作用是切换线程,在这里是将线程池中执行完毕返回的bitmap等封装好的对象发送到主线程中处理
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == MESSAGE_POST_RESULT){
                Result result = (Result)msg.obj;
                ImageView imageview = result.imageview;
                String url = result.url;
                Bitmap bitmap = result.bitmap;
                if(url.equals((String)imageview.getTag(R.id.imageloader_id))){//防止图片错位
                       imageview.setImageBitmap(bitmap);
                }
            }
        }
    };

private void downloadImageFromHttp(final ImageView imageView, final String url, final int reqWidth, final int reqHeight) {
        imageView.setTag(R.id.imageloader_id, url);
        THREAD_POOL_EXECUTOR.execute(//在线程池中执行下载任务,执行完成后发送消息
                new Runnable() {
                    @Override
                    public void run() {
                       InputStream inputStream = new URL(s).openStream();
                       saveBitmapToDisk(inputStream, hashKeyFormUrl(url));  
                       Bitmap bitmap = getBitmapFromDisk(hashKeyFormUrl(url),reqWidth,reqHeight);
                       mHandler.obtainMessage(MESSAGE_POST_RESULT,new Result(imageView,url,bitmap)).setToTarget();
                    }
                });
    }

以上省去了线程池配置代码,代码逻辑还是有点绕的,而且代码量也不少,对于不是很熟悉Handler和线程池的盆友可能写着写着就绕晕了,所以这里强烈推荐使用RxJava来实现以上功能,代码如下:

 private void downloadImageFromHttp(final ImageView imageView, final String url, final int reqWidth, final int reqHeight) {
        imageView.setTag(R.id.imageloader_id, url);
        Observable.just(url)
                .flatMap(new Func1<String, Observable<Bitmap>>() {
                    @Override
                    public Observable<Bitmap> call(String s) {
                        InputStream inputStream = null;
                        try {
                            inputStream = new URL(s).openStream();
                            saveBitmapToDisk(inputStream, hashKeyFormUrl(url));
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            return Observable.just(getBitmapFromDisk(hashKeyFormUrl(url), reqWidth, reqHeight));
                        }
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<Bitmap>() {
                    @Override
                    public void call(Bitmap bitmap) {
                        if (imageView.getTag(R.id.imageloader_id) == url) {
                            imageView.setImageBitmap(bitmap);
                        }
                    }
                });
    }

直观来看,代码从上到下,一气呵成,不用线程池繁琐的配置,没有handler线程切换等操作,像穿珠子一样,逻辑从头到尾都是一条线。

首先just操作符接收了传入的url,接下来flatmap进行数据的变换操作,通过url获取输入流,并且将其解析成bitmap存储到磁盘缓存中,然后再从磁盘缓存中读取刚写入的bitmap,并且最终将读取的bitmap传入subscriber的回调方法中设置bitmap。看到这里,你可能会疑问了,这么一串操作都是在主线程中完成的吗?这还能不ANR???其实并不是··关键在于以下两行代码:

subscribeOn(Schedulers.io())
observeOn(AndroidSchedulers.mainThread())

这两行代码的作用是切换线程,前者让subscribe过程中发生在子线程中,这里的Schedulers.io()表示的进行io操作的线程调度器,内部实质是线程池。后者是让subscriber的回调过程发生在UI线程中。轻松两行代码就实现了线程间的切换,比起handler来切换线程这样简直不能太方便。

其他使用到RxJava的地方和以上大同小异,无非是从磁盘缓存加载和从内存缓存加载过程的不同,具体的代码逻辑可以参照源码,使用到比较频繁的操作符有:just,from,map,flatmap等,大家可以着重去了解这几个操作符的使用。个人觉得使用RxJava主要还是一个编程思维的转变,概括来说就是“流”,忘记回调,忘记线程,忘记。。。

写完了,请大家批评指正。。

最后贴上源码地址:

源码

版权声明:本文为博主原创文章,转载请注明出处。

使用rxjava2.0根据url获取bitmap,并显示到imageview上

/*retrofit的引入*/ compile 'com.squareup.retrofit2:retrofit:2.2.0' compile 'com.squareup.retrof...
  • shb2058
  • shb2058
  • 2017年06月28日 09:30
  • 262

Retrofit Rxjava 实现图片下载、保存并展示

首先我们看一下Retrofit常规的用法,在不使用Rxjava的情况下,我们默认返回的是Call。 public interface ServiceApi { //下载文件 @GET Ca...

ImageLoader的代码实现和一个例子

  • 2016年01月07日 17:30
  • 7.94MB
  • 下载

自己实现一个ImageLoader

一般来说,一个优秀的ImageLoader应该具备:图片的同步加载,图片的异步加载,图片压缩,内存缓存,硬盘缓存,网络拉取等功能.   那么,我们现在就开始来一一实现这几种功能!图片压缩图片压缩主要...

Android-MVP+Retrofit+Rxjava实现一个知乎日报客户端

使用MVP+Retrofit+Rxjava实现一个知乎日报客户端,界面基于Material design,还没有全部完成orz,,放假太懒效果图开源项目 name introduction ...

动手实现一个轻量级无侵入性的RxJava自动注销库RxLifecycle

RxLifecycle是一个轻量级,侵入性低的RxJava注销管理库.他不会改变原有项目Activity的继承结构,轻松实现RxJava在Android平台下的自动注销,甚至你在自定义view的时候使...

这是用RxJava实现的一个简单demo

这是用RxJava实现的一个简单demo RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based...
  • GRY_YJ
  • GRY_YJ
  • 2016年11月08日 22:40
  • 224

ImageLoader缓存一个网址图片并存到SD卡上

public class MainActivity extends AppCompatActivity { String uri = "https://api.tianapi.com/wxne...

【从零开始写一个简单的ImageLoader框架】项目介绍

相关文章【从零开始写一个简单的ImageLoader框架】项目介绍【从零开始写一个简单的ImageLoader框架】ImageLoader分析【从零开始写一个简单的ImageLoader框架】MyIm...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:用RxJava实现一个ImageLoader
举报原因:
原因补充:

(最多只允许输入30个字)