RxJava(六、变换)

目录

一、Is what 是什么
二、Concept 概念
三、Basic realization 基本实现
四、Scheduler 线程控制(上)
五、Scheduler 线程控制(下)
六、变换

因个人学习需要,故文章内容均为网上摘抄整理,感谢创作者的辛勤,源文章地址请看文末。

变换

将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。

RxJava核心功能之一:提供了对事件序列进行变换的支持。

分类

变换有多种,即可以针对事件对象,也可以针对整个事件队列。

基类
  • lift(Operator):对事件项和事件序列变换

  • compose(Transformer):对 Observable 自身变换

拓展类######
  • map():事件对象的直接变换,变换中常用的一种。
    map() 示意图
  • flatMap():很有用,但很难理解的变换,以下详解。

相同点:把传入的参数转化后返回另一个对象。

不同点:flatMap() 中返回的是个 Observable 对象,该对象并不是被直接发送到 Subscriber 的回调方法中。

API

//示例1 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);
        }
    });

其中,Func1 类是RxJava的一个接口,用于包装含有一个参数的方法。

FuncX 和 ActionX 相似,拥有多个,用于不同参数个数的方法。
区别:FuncX包装的是有返回值的方法

map()

map() 方法将参数中的 String 对象转换成 Bitmap 对象后返回,经过 map() 方法后,事件的参数类型由 String 转为 Bitmap

flatMap()

原理
  1. 使用传入的事件对象创建一个 Observable 对象;
  2. 并不发送这个 Observable ,而是将它激活,于是它开始发送事件;
  3. 每个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件同意交给 Subscriber 的回调方法。

以上三个步骤,把事件拆成两级,通过一组新建的 Observable 将初始的对象『铺平』之后通过统一路径分发下去,而这个『铺平』就是 flatMap() 所谓的flat

flatMap() 示意图

示例

现有一个数据结构(学生)

//1. 打印出一组学生的名字
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);

//2. 打印出每个学生所需要修的所有课程的名称
//需求区别:每个学生只有一个名字,但却有多个课程
//第一种实现
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);

//第二种实现
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);
扩展

可以在嵌套的 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); 妈妈再也不怕我的用户手抖点开两个重复的界面啦。

变换的原理: lift()

各种变换虽然功能不同,但实质上都是针对事件序列的处理和再发送。而在RxJava的内部,它们是基于同一个基础变换方法:lift(Operator)

lift()的内部实现(仅核心代码):

// 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
    return Observable.create(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber subscriber) {
            Subscriber newSubscriber = operator.call(subscriber);
            newSubscriber.onStart();
            onSubscribe.call(newSubscriber);
        }
    });
}

其中,生成了一个新的Observable并返回,而且创建新Observale所用的参数OnSubscribe的回调方法call()中的实现和Observable.subscribe()类似,但是并不一样,关键在于第二行onSubscribe.call(subscriber)中的onSubscribe所指代的对象不同:

  • subscribe()中这句话的onSubscribe指的是Observable中的onSubscribe对象,这个没有问题,但是list()之后的情况就复杂了。
  • 当含有list()时:
  1. lift()创建了一个Observable后,加上之前原始的Obervable,已经有两个Observable了;
  2. 同样的,新Observable里的新OnSubscribe加上原始Observable中的原始OnSubscribe,也就有了两个OnSubscribe;
  3. 当用户调用经过lift()后的Observablesubscribe()的时候,使用的是lift()所返回的新的Observable,于是它所触发的 onSubscribe.call(subscriber),也是用的新Observable中的新OnSubscribe,即在lift()中生成的那个OnSubscribe
  4. 而这个新OnSubscribecall()方法中的onSubscribe,就是指的原始Observable中的原始OnSubscribe,在这个call()方法里,新OnSubscribe利用operator.call(subscriber)生成了一个新的Subscriber(Operator 就是在这里,通过自己的 call() 方法将新 Subscriber 和原始 Subscriber 进行关联,并插入自己的『变换』代码以实现变换),然后利用这个新Subscriber向原始 Observable进行订阅。

这样实现了lift()过程,像一种代理机制,通过事件拦截和处理实现事件序列的变换

简而言之:在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的 Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber

示意图:


流程图 (静态)
流程图 (动态)
两次和多次的 lift()
示例
//具体的 Operator 的实现
//将事件中的 Integer 对象转换成 String
observable.lift(new Observable.Operator<String, Integer>() {
    @Override
    public Subscriber<? super Integer> call(final Subscriber<? super String> subscriber) {
        // 将事件序列中的 Integer 对象转换为 String 对象
        return new Subscriber<Integer>() {
            @Override
            public void onNext(Integer integer) {
                subscriber.onNext("" + integer);
            }

            @Override
            public void onCompleted() {
                subscriber.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                subscriber.onError(e);
            }
        };
    }
});

注意:讲述 lift() 的原理是为了更好地了解 RxJava ,从而更好地使用它。然而不管是否理解了 lift() 的原理,RxJava 都不建议开发者自定义 Operator 来直接使用 lift(),而是建议尽量使用已有的 lift() 包装方法(如 map()flatMap() 等)进行组合来实现需求,因为直接使用 lift() 非常容易发生一些难以发现的错误。

compose:对Observable整体的变换

//有多个 Observable ,都需要应用一组相同的 lift() 变换
//第一种方法
observable1
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber1);
observable2
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber2);
observable3
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber3);
observable4
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber1);

//第二种方法
private Observable liftAll(Observable observable) {
    return observable
        .lift1()
        .lift2()
        .lift3()
        .lift4();
}
...
liftAll(observable1).subscribe(subscriber1);
liftAll(observable2).subscribe(subscriber2);
liftAll(observable3).subscribe(subscriber3);
liftAll(observable4).subscribe(subscriber4);

第二种方法可读性、可维护性提高了。可是被方法包起来,这种方式对于 Observale 的灵活性似乎增添了那么点限制。

//使用 compose() 解决
//第三种方法
public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
    @Override
    public Observable<String> call(Observable<Integer> observable) {
        return observable
            .lift1()
            .lift2()
            .lift3()
            .lift4();
    }
}
...
Transformer liftAll = new LiftAllTransformer();
observable1.compose(liftAll).subscribe(subscriber1);
observable2.compose(liftAll).subscribe(subscriber2);
observable3.compose(liftAll).subscribe(subscriber3);
observable4.compose(liftAll).subscribe(subscriber4);

第三种方法,Observable 可以利用传入的 Transformer 对象的 call 方法直接对自身进行处理,也就不必包在方法里。

参考
给 Android 开发者的 RxJava 详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy阅读 5,468评论 7 62
  • 前言我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard...
    占导zqq阅读 9,164评论 6 151
  • 文章转自:http://gank.io/post/560e15be2dca930e00da1083作者:扔物线在正...
    xpengb阅读 7,031评论 9 73
  • 怎样快速赚钱的方法,怎样快速提高英语成绩,怎样快速获得他人的好感....这个社会教会了人们好多快速的方法,却殊不知...
    eileen宝贝阅读 200评论 0 1
  • 其实一直很讨厌拖沓,这是个致命的缺点。自我认识清晰,却总是没办法行动速度迅捷。想写点东西,就这么简单的想法,一直在...
    生命是华丽错觉_阅读 197评论 0 1