美文网首页
rxJava&rxAndroid-概念了解(2)

rxJava&rxAndroid-概念了解(2)

作者: ZzzRicardo_Yue | 来源:发表于2016-11-20 10:02 被阅读0次

    暂时没时间把rxJava&rxAndroid的进阶内容全部整理完,先整理一部分

    附上博客链接:
    http://blog.csdn.net/wds1181977/article/details/51554170

    注意,下文说的Subscriber 就是基础篇里面讲的Observer,因为在rxJava的源码里面,无论你使用Observer还是Subscriber 最后都会被转换成Subscriber 来执行~~

    一、Scheduler线程控制

    前面基础篇我们看到的 subscribeOn() 和 observeOn() 就是Scheduler线程控制的一部分。

    注意:

    在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。


    rxJava有如下几个Scheduler线程参数:

    • Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。
    • Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
    • Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
    • Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
    • AndroidSchedulers.mainThread():它指定的操作将在 Android 主线程运行,针对View的操作都要在这个上进行

    有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。 总而言之,就是:

    **subscribeOn(): 指定subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。 **

    **observeOn(): 指定 Subscriber (等同前面的Observer)所运行在的线程。或者叫做事件消费的线程。 **


    由图片 id 取得图片并显示的例子:

    int drawableRes = ...;
    ImageView imageView = ...;
    Observable.create(new OnSubscribe<Drawable>() {
        @Override
        public void call(Subscriber<? super Drawable> subscriber) {
            Drawable drawable = getTheme().getDrawable(drawableRes));
            subscriber.onNext(drawable);
            subscriber.onCompleted();
        }
    })
    .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
    .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
    .subscribe(new Observer<Drawable>() {
        @Override
        public void onNext(Drawable drawable) {
            imageView.setImageDrawable(drawable);
        }
    
        @Override
        public void onCompleted() {
        }
    
        @Override
        public void onError(Throwable e) {
            Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
        }
    });
    

    那么,加载图片将会发生在 IO 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。

    二、变换(上):map()

    这一部分的关键词是

    “变换”

    那么,何为“变换”呢?我们继续往下看


    在基础篇里面我们提到了Map,下面有一段代码

    Observable.just("images/logo.png") // 输入类型 String
        .map(new Func1<String, Bitmap>() {
            @Override
            public Bitmap call(String filePath) { // 参数类型 String
                return getBitmapFromPath(filePath); // 返回类型 Bitmap
            }
        })
        .subscribe(new Action1<Bitmap>() {
            @Override
            public void call(Bitmap bitmap) { // 参数类型 Bitmap
                showBitmap(bitmap);
            }
        });
    

    这里我们可以看到,Observable将传入的String类型转变成了Bitmap类型,这个转变关系就是我们要讲的“变换”

    如上所示,map() 方法将参数中的 String 对象转换成一个 Bitmap 对象后返回,而在经过 map() 方法后,事件的参数类型也由String 转为了 Bitmap。这种直接变换对象并返回的,是最常见的也最容易理解的变换。


    除此之外, RxJava 还可以针对整个事件队列进行变换操作,这使得 RxJava 变得非常灵活。我列举几个常用的变换:

    • map(): 事件对象的直接变换,具体功能上面已经介绍过。它是 RxJava 最常用的变换。 map() 的示意图:


      变换

    三、变换(下):flatMap()

    flatMap(): 这是一个很有用但非常难理解的变换,因此我决定花多些篇幅来介绍它。 首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:

    Student[] students = ...;
    Subscriber<String> subscriber = new Subscriber<String>() {
        @Override
        public void onNext(String name) {
            Log.d(tag, name);
        }
        ...
    };
    Observable.from(students)
        .map(new Func1<Student, String>() {
            @Override
            public String call(Student student) {
                return student.getName();
            }
        })
        .subscribe(subscriber);
    

    很简单。那么再假设:如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于,每个学生只有一个名字,但却有多个课程。)首先可以这样实现:

    Student[] students = ...;
    Subscriber<Student> subscriber = new Subscriber<Student>() {
        @Override
        public void onNext(Student student) {
            List<Course> courses = student.getCourses();
            for (int i = 0; i < courses.size(); i++) {
                Course course = courses.get(i);
                Log.d(tag, course.getName());
            }
        }
        ...
    };
    Observable.from(students)
        .subscribe(subscriber);
    

    依然很简单。那么如果我不想在 Subscriber 中使用 for 循环,而是希望 Subscriber 中直接传入单个的 Course 对象呢(这对于代码复用很重要)?用 map() 显然是不行的,因为 map() 是一对一的转化,而我现在的要求是一对多的转化。那怎么才能把一个 Student 转化成多个 Course 呢?

    这个时候,就需要用 flatMap() 了:

    Student[] students = ...;
    Subscriber<Course> subscriber = new Subscriber<Course>() {
        @Override
        public void onNext(Course course) {
            Log.d(tag, course.getName());
        }
        ...
    };
    Observable.from(students)
        .flatMap(new Func1<Student, Observable<Course>>() {
            @Override
            public Observable<Course> call(Student student) {
                return Observable.from(student.getCourses());
            }
        })
        .subscribe(subscriber);
    

    从上面的代码可以看出, flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。

    首先我们先要理解这样一个前提:这里一个Students数组当做事件对象创建了一个Oberable对象,然后绑定了一个Subscriber,Oberable对象会自动将这个Students数组拆解,然后把一个一个Student对象传给Subscriber,然后Subscriber不断响应传入的Student对象,利用循环加载单个Student的好多个课程,这也是之前使用map()的原理。

    而现在我们要求传给Subscriber的必须是多个学生的多个课程。那么利用一种类似递归的思想去理解这个问题。flatMap() 在这里的原理是这样的:

    1. 使用传入的Students数组事件对象创建一个 Observable 对象
    2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件,也就是这里它发送Observable<Course>,注意这个Course也是一个数组(或者任一集合框架类型)哦
    3. 看这里的代码
    public Observable<Course> call(Student student) {
                return Observable.from(student.getCourses());
            }
    

    这里return的Observable.from(student.getCourses())并没有直接绑定Subscriber,而是又经过了一个二级传递,将原来student.getCourses()中获得的一个List<Course>中的一个一个Course单独取出来排成队列,然后统一发给传给了返回类型中的Observable<Course>

    1. 然后这个返回类型中的Observable<Course>将这些course统一交给 Subscriber 的回调方法

    总结:每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个Observable 负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat
    flatMap() 示意图:


    紫圈红圈代表单个Student,每个圈被转换成一个Obserable,每个Obserable又发出一个一个Course,最后被一个统一的Ovserable传给Oberver

    扩展:由于可以在嵌套的 Observable 中添加异步代码, flatMap() 也常用于嵌套的异步操作,例如嵌套的网络请求。示例代码(Retrofit + RxJava):

    networkClient.token() // 返回 Observable<String>,在订阅时请求 token,并在响应后发送 token
        .flatMap(new Func1<String, Observable<Messages>>() {
            @Override
            public Observable<Messages> call(String token) {
                // 返回 Observable<Messages>,在订阅时请求消息列表,并在响应后发送请求到的消息列表
                return networkClient.messages();
            }
        })
        .subscribe(new Action1<Messages>() {
            @Override
            public void call(Messages messages) {
                // 处理显示消息列表
                showMessages(messages);
            }
        });
    

    传统的嵌套请求需要使用嵌套的 Callback 来实现。而通过 flatMap() ,可以把嵌套的请求写在一条链中,从而保持程序逻辑的清晰。

    throttleFirst(): 在每次事件触发后的一定时间间隔内丢弃新的事件。常用作去抖动过滤,例如按钮的点击监听器:RxView.clickEvents(button) // RxBinding 代码,后面的文章有解释 .throttleFirst(500, TimeUnit.MILLISECONDS) // 设置防抖间隔为 500ms .subscribe(subscriber);妈妈再也不怕我的用户手抖点开两个重复的界面啦。
    此外, RxJava 还提供很多便捷的方法来实现事件序列的变换,这里就不一一举例了。

    暂时先写到这里。

    相关文章

      网友评论

          本文标题:rxJava&rxAndroid-概念了解(2)

          本文链接:https://www.haomeiwen.com/subject/zkaepttx.html