RxJava从放弃到入门(二):学,不能停

写在前面

上篇文章RxJava从放弃到入门(一):基础篇讲述的东西都是非常基础的东西,这一篇准备讲述以下一些东西:

创建Observable:

  • just
  • defer

操作符:

  • map
  • flatmap

经验总结:

  • 自己遇到的一些坑和经验

再叙Observable

在上一篇里我们已经了解了在RxJava中Observable扮演了什么样的角色,如果你还没看过,没事,我们一起来回忆以下:在RxJava中,Observable扮演的是一个发射数据或数据序列的角色。Observer则是接收Observable发射的东西。上次只提到了一种hello world的打印方法,是否会让你感到不爽?让我们当一回孔乙己,学一下“茴”字的N种写法。当然在这我并不会以流水账的形式记录每一种创建操作的流程,那样无疑是没有必要的,实在想要了解你可以去看文档。

just

首先肯定还是我们的hello world

 Observable.just("hello","world").subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                Log.e(TAG, s);
            }
        });
just.png

上面出现了一个Action1,你去看他的继承关系可能会让你疑惑,因为接收数据的Observer或者是其子类都跟他没啥关系。没关系,我们直接看源码。

    public final Subscription subscribe(final Action1<? super T> onNext) {
        if (onNext == null) {
            throw new IllegalArgumentException("onNext can not be null");
        }

        return subscribe(new Subscriber<T>() {

            @Override
            public final void onCompleted() {
                // do nothing
            }

            @Override
            public final void onError(Throwable e) {
                throw new OnErrorNotImplementedException(e);
            }

            @Override
            public final void onNext(T args) {
                onNext.call(args);
            }

        });
    }

可以很明显的看到在这个方法内部,还是有一个Subscriber来接收数据的,这个Subscriber是观察者Observer的实现类(Subscriber本身是一个抽象类)。不同的只是在onNext方法中将处理数据的逻辑交给接口Action1的call方法,而最终实现逻辑是交由用户来实现的。这种设计明显是为了方便我们使用,弄明白这一点后,我们可以就可以安心的使用Action1来跑我们的代码了。

使用just方法创建的Observable会按照顺序将参数一一发射,如果不能理解的话,恩,官方文档的图也是极好的:


文档
文档

看了just之后你可能会觉得这玩意很方便,可以不用自己手动调subscriber.onNext()和其他的方法了~但是有得必有失,你用一个create方法从头开始创建一个observable时,你对这个observable的特性是了解的。而你将目光投向一个经过封装的方法时,你需要花费更多的时间去了解他。下面来一个我曾看过的一篇文章里谈到的一个错误使用:

    public Observable<String> aaobservable() {
        return Observable.just(value);
    }
       //调用以上方法创建一个Observable
        Observable<String> aa = aaobservable();
        value = "String";
        aa.subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                Log.e(TAG, s);
            }
        });

以上代码你觉得运行的结果会是什么?会是 String 吗?结果在下面


你这么一说我就懂了.png

啥?为毛是null?很简单,上源码里看看就行了。首先点进just(T value)这个方法里瞧瞧,他究竟干了啥。

    public final static <T> Observable<T> just(final T value) {
        return ScalarSynchronousObservable.create(value);
    }

继续追踪,发现create()源码如下:

    public static final <T> ScalarSynchronousObservable<T> create(T t) {
        return new ScalarSynchronousObservable<T>(t);
    }

我去,可算看到了,既然是调了构造方法,那应该不会再有什么幺蛾子了吧?进构造方法看一看:

    protected ScalarSynchronousObservable(final T t) {
        super(new OnSubscribe<T>() {

            @Override
            public void call(Subscriber<? super T> s) {
                /*
                 *  We don't check isUnsubscribed as it is a significant performance impact in the fast-path use cases.
                 *  See PerfBaseline tests and https://github.com/ReactiveX/RxJava/issues/1383 for more information.
                 *  The assumption here is that when asking for a single item we should emit it and not concern ourselves with 
                 *  being unsubscribed already. If the Subscriber unsubscribes at 0, they shouldn't have subscribed, or it will 
                 *  filter it out (such as take(0)). This prevents us from paying the price on every subscription. 
                 */
                s.onNext(t);
                s.onCompleted();
            }

        });
        this.t = t;
    }

首先抛开注释,直接调用了onNext和onCompleted,明显没其他多的东西。而那段注释,额用我比较差的英语水平来翻译一下第一句话……我们不检查是否被订阅,因为在用例中这会显著的影响性能。恩,出于各种考虑,他直接发射了数据,并不关心有没有人订阅之类的……哦,原来如此,在创建Observable的时候value还是null,在那时数据已经被发射了,之后再更改value的值也无济于事了。知晓了原因之后那该怎么解决这个问题呢?

很容易想到的一个方法是create()方法,毕竟这个方法会从头建造一个Observable,一切尽在你的掌握中。

defer

使用defer()来创建Observable会在有观察者订阅时才创建Observable,并且为每一个观察者创建一个新的Observable。回想一下,我们上面的代码之所以出现错误,就是因为过早的创建Observable和发射数据导致的。defer()这种在订阅时才创建是解决以上问题的方法之一,那么上代码:
创建Observable的方法:

   public Observable<String> aaobservable() {
       return Observable.defer(new Func0<Observable<String>>() {
           @Override
           public Observable<String> call() {
               return Observable.just(value);
           }
       });
   }

验证:

        Observable<String> aaobservable = aaobservable();
        value = "String";
        aaobservable.subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                Log.e(TAG, s);
            }
        });
成功

好了,对于just和defer就先告一段落,同时对于整个Observable也就告一段落了。可能到这你还觉得不够,毕竟归根到底我说到这也只讲了just和defer,不过我还是不打算继续下去了,我这里对于Observable的描述已经占去了太多的篇幅了。关于更多的创建操作我的建议是阅读官方文档。

操作符

Map

图来自于文档
图来自于文档

在我看来这个操作符体现的是一种“一对一”的转换,比如你现在需要一张图,但是你的输入是一个string(这算是比较经典的场景了),你就可以使用如下代码进行变换:

        Observable.just(str).map(new Func1<String, Bitmap>() {
            @Override
            public Bitmap call(String s) {
                try {
                    URLConnection con = new URL(s).openConnection();
                    InputStream inputStream = con.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    return bitmap;
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }).subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread());

代码效果就不展示了,你懂就行。这篇文章里我暂时不会去解析map以及接下来会讲述的几个操作符的变换原理,留待以后更深入的了解之后再去用更清晰的语言来表述。

flatmap

上述map可以实现一对一的转换,那么flatmap则是实现一对多的转换。在RxJava的文档上是如此描述flatmap的:

将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据合并后放进一个单独的Observable

图片来自官方文档
图片来自官方文档

对于一个url你可以用map一对一的将其转换为一个bitmap,对于一个含有url的string数组你也可以采用以下的方式来转换:

        Observable.just(str).map(new Func1<String[], Bitmap>() {
            @Override
            public Bitmap call(String[] s) {
                for (String a : s) {
                    try {
                        URLConnection con = new URL(a).openConnection();
                        InputStream inputStream = con.getInputStream();
                        Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                        return bitmap;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }
        }).subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<Bitmap>() {
            @Override
            public void call(Bitmap bitmap) {
                image.setImageBitmap(bitmap);
            }
        });

但是写完之后会不会觉得有点不得劲?感觉这很不RxJava,很让人不愉快?那就对了,人要对自己好一点,觉得不爽就换个flatmap试试:

        Observable.from(str).flatMap(new Func1<String, Observable<String>>() {
            @Override
            public Observable<String> call(String s) {
                return Observable.just(s);
            }
        }).map(new Func1<String, Bitmap>() {
            @Override
            public Bitmap call(String s) {
                Bitmap bt = null;
                try {
                    URL url = new URL(s);
                    URLConnection con = url.openConnection();
                    bt = BitmapFactory.decodeStream(con.getInputStream());
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return bt;
            }
        }).subscribe(new Action1<Bitmap>() {
            @Override
            public void call(Bitmap bitmap) {

            }
        });

不管效果如何,这一条链子写下来就是让人很舒服~同时逻辑也非常的清晰,以后再改这种代码的时候可以少骂几句娘了。

小结

这一次关于RxJava的不能停到这里就要告一段落了,好在我已经把我想要写的都写的差不多了,不过这里的小结也不能光灌水。最近本来打算开个项目把gank io的api搞一下,不过最近突然就忙起来了,所以那个东西先搁置一下,关于我初学时的项目的重构已经在进行了。使用了MVP,部分逻辑使用了RxJava,不过因为是初学时写的,自己不满意的地方实在是太多了。以下的两点是我最近在使用RxJava时自己总结的一点经验,不足之处欢迎指出:

线程控制就交给RxJava去做

说实话,我还没有开始使用Retrofit,我使用的一直是Hongyang大神封装的OkhttpUtil,因为的确挺好用的。现在一般的请求框架也都会自己封装线程池,实现异步回调。我就在这上面吃了一个小亏,我在使用RxJava实现读取数据的逻辑的时候,先从网络获取数据,获取不到则读取本地缓存。结果每次读取的都是本地数据,这让我很奇怪,我打了log发现网络请求也是成功的。后来我有点明白了:

我使用okhttputil获取数据成功时回调,但是我在订阅的回调里面判断数据状态时,数据还没有获取到,所以执行了获取本地数据的逻辑。所以关于异步这个问题,我想说的就是写同步的代码,让RxJava做异步的事。当然,我的看法并不一定准确,你可以在底下评论说出你的想法,欢迎讨论。

最后再来个真正的小结吧,说实话,我觉得写这两篇RxJava的文章对我自己的帮助真的挺大的,让我对RxJava的认知又提升了一点。现在再看以前写的一些RxJava的代码,都觉得不够RxJava,所以要走的路还很长啊~

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

推荐阅读更多精彩内容