RxWeekend——RxJava周末狂欢

作者地址: MrFu Blog--RxWeekend
周五的时候就打算这个周末就看 RxJava 了,于是利用一个周末的时间把咖啡变成了文字,对,就是咖啡,不是啤酒和炸鸡,周六把 RxJava Essentials 英文版再看了一遍,顺便看了一遍翻译版,周日把小鄧子的博客以及他引述的其他文章全部看了一遍。
Part1 部分主要是 RxJava Essentials 的操作符
Part2 部分主要是一些 tips
对于Part1我更建议你先去看 RxJava Essentials 这本书,再回过头来看这部分。我这里的解释可能是非常抽象的,都是一些总结性的解释。

这里有一个实例,和 Tips7 有关:RxFace,喜欢就 star,不要犹豫 ^^

Part 1: RxJava Essentials -- Operators

Basic

  • just() 方法可以传入1到9个参数,它们会按照传入的参数的顺序来发射它们。

  • Observable.empty() 需要一个 Oservable 但是什么都不发射

  • Observable.never() 传一个不发射数据并永远不会结束的 Observable

  • Observable.throw() 创建一个不发射数据并且以错误结束的 Observable

  • repeat()

  • defer() 在观察者订阅时创建 Observable,而不是创建后立即执行,这篇文章有着更棒的解释:小鄧子:使用RxJava实现延迟订阅

  • range() 从一个指定的数字开始发射 N 个数字

  • interval(3, TimeUnit.SECONDES) 轮询时用:参数:指定两次发射时间间隔,时间单位。

  • timer() 一段时间后才发射 Observable

Filtering

  • filter(), take(), takeLast()

  • distinct() 去掉序列中重复项,是作用于一个完整的序列的

  • distinctUntilChanged() 在一个存在的序列上来创建一个新的不重复发射元素的序列

distinctuntilchanged
  • first(), last(), firstOrDefault(), lastOrDefault()

  • skip(), skipLast() 跳过前几个或者最后几个元素

  • elementAt() 发射指定元素。但如果元素不足可以使用:elementAtOrDefault()

  • sample(30,TimeUnit.SECONDS) 指定的时间间隔里发射最近一次的数值

sample
  • throttleFirst() 定时发射第一个元素

  • timeout() 限时,在指定时间间隔 Observable 不发射值的话, 就会触发 onError() 函数

  • debounce() 过滤发射速率过快的数据,即:在一个时间间隔过去之后,仍然没有发射的话,则发射最后的那个

Transforming

  • map() 接收到的对象应用到每个发射的值上

  • flatMap() 将发射的序列转换成另外一种对象的 Observable 序列,注意:它允许交叉,即 flatMap() 不保证最终生成的 Observable 和源 Observable 发射序列相同。 FlatMap

  • concatMap() 解决了 flatMap() 交叉的问题,提供了 能把发射值连续在一起的铺平函数,而非合并它们。

关于flatMap()concatMap() 必须看这篇文章: 小鄧子-RxJava变换操作符:.concatMap( )与.flatMap( )的比较

  • flatMapInterable() 类似于 flatMap() 只是它将源数据两两结成对并生成 Iterable,而不是原始数据项和生成的 Observables

  • switchMap()flatMap() 区别在于每当源 Observable 发射一个新的数据项时,将取消订阅并停止监视之前那个数据项产生的 Observable,并开始监视当前发射的这个。

  • scan() 累加器,对原始Observable 发射的每项数据都应用一个函数,计算出函数的结果值,并填充回可观测序列,等待下一次发射的数据一起使用。

  • scan(R, Func2) 用初始值作为第一个发射的值

  • groupBy() 引用小鄧子的一段话来说是这样的:去这里看更详细的解释,会恍然大悟的:小鄧子-Architecting Android with RxJava

将原始Observable根据不同的key分组成多个GroupedObservable,由原始Observable发射(原始Observable的泛型将变成这样Observable<GroupedObservable<K, T>>),每一个GroupedObservable既是事件本身也是一个独立的Observable,每一个GroupedObservable发射一组原始Observable的事件子集。

  • buffer() 将得到一个新的 Observable,这个 Observable 每次发射一组列表值而不是单个发射,你还可以指定它的 skip 值和 timespan 项数据

  • window() 类似于 buffer(),但它发射的是 Observable 而不是列表

  • cast() 将源 Observable 中每一项数据都转换成新的类型,转成了一个不同的 Class。

Combining

  • merge() 多个序列合并在一个最终发射的 Observable. mergeDelayError() 当所有的 Observable 都完成时,再处理有 error 的情况,发射 onError()

  • zip() 合并两个或多个 Observables 发射出的数据项,根据指定的函数 Func* 变换它们,并发射一个新值

  • join() 基于时间窗口将两个 Observables 发射的数据结合在一起,组成一个新的 Observable。它可以控制每个 Observable 产生结果的生命周期,在每个结果的生命周期内,可以与另一个 Observable 产生的结果按照一定的规则进行合并!

join

join方法的用法如下:
observableA.join(observableB,
observableA产生结果生命周期控制函数,
observableB产生结果生命周期控制函数,
observableA产生的结果与observableB产生的结果的合并规则)

蓝线和粉色的线表示对应的Observable 上的元素的生命周期。Android RxJava使用介绍(四) RxJava的操作符

  • combineLatest()zip() 的特殊形式,zip()作用于最近未打包的两个 Observables,相反 combineLatest() 作用于最近发射的数据项
combinelatest
  • and(), then(), when(): 如下:
Pattern2<O1, O2> pattern = JoinObservable.from(obserable1).and(obserable2);
Plan0<O1> plan = pattern.then(this::updateTitle);
JoinObservable.when(plan).toObservable().observeOn(…).subscribe(…);

解释:两个发射序列 obserable1 和 obserable2 通过 and 链接。使用 pattern 对象创建 Plan 对象,然后使用 when...(好吧,我想不到使用场景...)

and_then_when
  • switch() 将一个发射多个 Observables 的 Observable 转换成另一个单独的 Observable,后者发射那些 Observables 最近发射的数据项,注:当源 Observable 发射一个新的 Observable 时,switch() 会立即取消订阅前一个发射数据的 Observable,然后订阅一个新的 Observable,并开始发射它的数据。

  • startWith()concat() 对应,通过传一个参数来先发射一个数据序列

Part 2: Tips

Tips1

使用RxJava从多个数据源获取数据

// Our sources (left as an exercise for the reader)
Observable<Data> memory = ...;
Observable<Data> disk = ...;
Observable<Data> network = ...;

// Retrieve the first source with data
Observable<Data> source = Observable
  .concat(memory, disk, network)
  .first();
//先取 memory 中的数据,如果有,就取出,然后停止检索队列;没有就取 disk 的数据,有就取出,然后停止检索队列;最后才是网络请求
 //持久化数据or缓存数据
 Observable<Data> networkWithSave = network.doOnNext(new Action1<Data>() {
 @Override public void call(Data data) {
 saveToDisk(data);
 cacheInMemory(data);
 }
});

 Observable<Data> diskWithCache = disk.doOnNext(new Action1<Data>() {
 @Override public void call(Data data) {
  cacheInMemory(data);
 }

});
//现在,如果你使用 networkWithSave 和 diskWithCache,数据将会在加载后自动保存
//处理陈旧数据
Observable<Data> source = Observable
    .concat(memory, diskWithCache, networkWithSave)
    .first(new Func1<Data, Boolean>() {

      @Override public Boolean call(Data data) {
        return data.isUpToDate();//需要 update 的话,则筛选掉该数据源,检索下一个数据源
      }
    });//注:first() 和 takeFirst() 区别在于,如果没有符合的数据源,first() 会抛 NoSuchElementException 异常

Tips2

在正确的线程上观察

  • .subsribeOn() 操作符可以改变Observable应该在哪个调度器上执行任务。

  • .observeOn() 操作符可以改变Observable将在哪个调度器上发送通知。

  • 另外,默认情况下,链上的操作符将会在调用 .subsribeOn()的那个线程上执行任务。如下:

Observable.just(1,2,3)
  .subscribeOn(Schedulers.newThread())
  .flatMap(/** 与UI线程无关的逻辑**//)//会在 subscribeOn() 指定的线程上执行任务
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe();

Tips3

Architecting Android with RxJava

Backpressure(背压): 事件产生的速度比消费快(在 producer-consumer(生产者-消费者) 模式中)。发生 overproucing 后,当链式结构不能承受数据压力时,就会抛出 MissingBackpressureException 异常。
最常见的 Backpressure 就是连续快速点击按钮....

Tips4

避免打断链式结构:使用.compose()操作符:

再重用操作符的方式上,使用 compose(),而不是 flatMap():

compose_flatmap

Tips5

Schedulers:

将一个耗时的操作,通过 Scehdulers.io() 放到 I/O 线程中去处理


public static void storeBitmap(Context context, Bitmap bitmap, String filename){
    Schedulers.io().createWorker().schedule(() -> {
        blockingStoreBitmap(context, bitmap, filename);
    })
}

Tips6

  • subject 可以同时是一个 Observable 也可以是一个 Observer,一个 Subject 可以订阅一个 Observable,就像一个观察者,并发射新数据,或者传递它接受到的数据,就像一个 Observable。see more

  • 对于空的 subscribe() 意为仅仅是为了开启 Observable,而不用管已发出的值。

  • subscriber.onNextsubscriber.onCompleted() 前检测观察者的订阅情况,使代码更高效,因为如果没有观察者等待时我们就不生成没必要的数据项。就像这样:

if (!subscriber.isUnsubscribed()){//避免生成不必要的数据项
    return;
}
subscriber.onNext();

if (!subscriber.isUnsubscribed()){
    subscriber.onCompleted();
}

Tips7

我觉得这个 Tips 是最有用的

先祭出两个工具类

对于 SchedulersCompat 类,我们的目的,是为了写出这样的代码:

.compose(SchedulersCompat.<SomeEntity>applyExecutorSchedulers());

场景是这样的:work thread 中处理数据,然后 UI thread 中处理结果。当然,我们知道是要使用 subscribeOn()observeOn() 进行处理。最常见的场景是,调server 的 API 接口取数据的时候,那么,那么多接口,反复写这两个操作符是蛋疼的,为了避免这种情况,我们可以通过 compse() 操作符来实现复用,上面这段代码就实现了这样的功能。

SchedulersCompat 类中有这么一段 Schedulers.from(ExecutorManager.eventExecutor),哇喔,这里ExecutorManager 类里维护了一个线程池!目的呢!避免线程反复创建,实现线程复用!!!这样,我就不需要每次都通过Schedulers.newThread()来实现了!!

如果你想了解更多,关于 compose()操作符,可以看这里:小鄧子-避免打断链式结构:使用.compose( )操作符

对于这个 Tips, 我给出一个项目实例:RxFace,这是我在做一个人脸识别的 demo 的时候所写的,用了 RxJava, retrofit, Okhttp。我在v1.1版本的时候增加通过compose()操作符复用 subscribeOn()observeOn() 的逻辑。觉得还 OK 的话,可以点个 star 喔,哈哈

/**
 * 这个类是 小鄧子 提供的!
 */
public class SchedulersCompat {
    private static final Observable.Transformer computationTransformer =
            new Observable.Transformer() {
                @Override public Object call(Object observable) {
                    return ((Observable) observable).subscribeOn(Schedulers.computation())
                            .observeOn(AndroidSchedulers.mainThread());
                }
            };
    private static final Observable.Transformer ioTransformer = new Observable.Transformer() {
        @Override public Object call(Object observable) {
            return ((Observable) observable).subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread());
        }
    };
    private static final Observable.Transformer newTransformer = new Observable.Transformer() {
        @Override public Object call(Object observable) {
            return ((Observable) observable).subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread());
        }
    };
    private static final Observable.Transformer trampolineTransformer = new Observable.Transformer() {
        @Override public Object call(Object observable) {
            return ((Observable) observable).subscribeOn(Schedulers.trampoline())
                    .observeOn(AndroidSchedulers.mainThread());
        }
    };
    private static final Observable.Transformer executorTransformer = new Observable.Transformer() {
        @Override public Object call(Object observable) {
            return ((Observable) observable).subscribeOn(Schedulers.from(ExecutorManager.eventExecutor))
                    .observeOn(AndroidSchedulers.mainThread());
        }
    };
    /**
     * Don't break the chain: use RxJava's compose() operator
     */
    public static <T> Observable.Transformer<T, T> applyComputationSchedulers() {
        return (Observable.Transformer<T, T>) computationTransformer;
    }
    public static <T> Observable.Transformer<T, T> applyIoSchedulers() {
        return (Observable.Transformer<T, T>) ioTransformer;
    }
    public static <T> Observable.Transformer<T, T> applyNewSchedulers() {
        return (Observable.Transformer<T, T>) newTransformer;
    }
    public static <T> Observable.Transformer<T, T> applyTrampolineSchedulers() {
        return (Observable.Transformer<T, T>) trampolineTransformer;
    }
    public static <T> Observable.Transformer<T, T> applyExecutorSchedulers() {
        return (Observable.Transformer<T, T>) executorTransformer;
    }
}
/**
 * 这个类也是 小鄧子 提供的!!
 */
public class ExecutorManager {
    public static final int DEVICE_INFO_UNKNOWN = 0;
    public static ExecutorService eventExecutor;
    //private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CPU_COUNT = ExecutorManager.getCountOfCPU();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;
    private static final BlockingQueue<Runnable> eventPoolWaitQueue = new LinkedBlockingQueue<>(128);
    private static final ThreadFactory eventThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
        public Thread newThread(@NonNull Runnable r) {
            return new Thread(r, "eventAsyncAndBackground #" + mCount.getAndIncrement());
        }
    };
    private static final RejectedExecutionHandler eventHandler =
            new ThreadPoolExecutor.CallerRunsPolicy();
    static {
        eventExecutor =
                new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
                        eventPoolWaitQueue, eventThreadFactory, eventHandler);
    }
    /**
     * Linux中的设备都是以文件的形式存在,CPU也不例外,因此CPU的文件个数就等价与核数。
     * Android的CPU 设备文件位于/sys/devices/system/cpu/目录,文件名的的格式为cpu\d+。
     *
     * 引用:http://www.jianshu.com/p/f7add443cd32#,感谢 liangfeizc :)
     * https://github.com/facebook/device-year-class
     */
    public static int getCountOfCPU() {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
            return 1;
        }
        int count;
        try {
            count = new File("/sys/devices/system/cpu/").listFiles(CPU_FILTER).length;
        } catch (SecurityException | NullPointerException e) {
            count = DEVICE_INFO_UNKNOWN;
        }
        return count;
    }
    private static final FileFilter CPU_FILTER = new FileFilter() {
        @Override public boolean accept(File pathname) {
            String path = pathname.getName();
            if (path.startsWith("cpu")) {
                for (int i = 3; i < path.length(); i++) {
                    if (path.charAt(i) < '0' || path.charAt(i) > '9') {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }
    };
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 本篇文章介主要绍RxJava中操作符是以函数作为基本单位,与响应式编程作为结合使用的,对什么是操作、操作符都有哪些...
    嘎啦果安卓兽阅读 2,782评论 0 10
  • 创建操作 用于创建Observable的操作符Create通过调用观察者的方法从头创建一个ObservableEm...
    rkua阅读 1,450评论 0 1
  • 响应式编程简介 响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河:它可以被观测,被过滤,被操作,或者...
    长夜西风阅读 3,001评论 0 5
  • RxJava正在Android开发者中变的越来越流行。唯一的问题就是上手不容易,尤其是大部分人之前都是使用命令式编...
    刘启敏阅读 1,801评论 1 7
  • 作者: maplejaw本篇只解析标准包中的操作符。对于扩展包,由于使用率较低,如有需求,请读者自行查阅文档。 创...
    maplejaw_阅读 45,337评论 8 93