理解RxJava:(四)Reactive Android

在前三部分,我在通用层面介绍了RxJava的工作原理。但是作为一个Android开发者,如何在工作中使用它呢?下面是一些给Android开发者的RxJava的具体应用。

RxAndroid

RxAndroid是RxJava在Android开发中的拓展。它包含能节省我们大量时间的特殊bindings。

首先,其中有AndroidSchedulers,它能提供专门为Android线程系统提供的schedulers。需要在UI线程运行代码?没问题——只需要使用AndroidSchedulers.mainThread()方法即可:

retrofitService.getImage(url)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));

如果你拿到的是Handler,可以通过HandlerThreadScheduler创建一个scheduler绑定在Handler上。

接下来介绍的是AndroidObservable,它能提供很多在Android生命周期中的特色功能。bindActivity()bindFragment()方法能停止发出items,在ActivityFragment结束的时候。另外会自动为订阅使用AndroidSchedulers.mainThread()。(因此你不需要在Activity或Fragment无效的时候来改变状态)。

AndroidObservable.bindActivity(this, retrofitService.getImage(url))
    .subscribeOn(Schedulers.io())
    .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));

我也喜欢AndroidObservable.fromBroadcast(),它让我们可以创建一个像BroadcastReceiver那样工作的Observable。以下是无论什么时候网络连接发生变化时通知的方法。

IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
AndroidObservable.fromBroadcast(context, filter)
    .subscribe(intent -> handleConnectivityChange(intent));

最后介绍的是ViewObservable,它能为Views添加bindings。如果你想要得到View每次被点击的事件,可以通过ViewObservable.clicks()方法。也可以通过ViewObservable.text()方法来监测TextView的内容发生的任何变化。

ViewObservable.clicks(mCardNameEditText, false)
    .subscribe(view -> handleClick(view));

Retrofit

有一个知名并且支持RxJava的库:Retrofit,它是Android开发中的非常出名的REST风格的网络库。通常,我们定义一个异步方法并添加Callback:

@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);

用了RxJava,可以用Observable代替Callback作为返回值。

@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

现在可以对Observable做你想要的操作了,不仅可以获得数据,也能变换它。

Retrofit支持Observable,也使得合并多个REST请求变得容易。例如,假设我们有一个请求获得图片,另一个请求获得元数据(metadata)。我们可以把结果组合到一起:

Observable.zip(
    service.getUserPhoto(id),
    service.getPhotoMetadata(id),
    (photo, metadata) -> createPhotoWithData(photo, metadata))
    .subscribe(photoWithData -> showPhoto(photoWithData));

我在第二部分展示了相似的例子(使用flatMap())。这便证明使用RxJava+Retrofit合并多个REST请求有多简单。

旧的,耗时长的代码

Retrofit能返回Observerables这固然非常好,但是如果你用的其他的库不支持它呢?或者你想要将一些内部代码转换为Observables?总之,你如何将旧的代码和新的代码联系在一起,而不用重写所有代码?

Observable.just()Observable.from()大多数时候足以将以前的代码转换为Observable

private Object oldMethod() { ... }

public Observable<Object> newMethod() {
    return Observable.just(oldMethod());
}

这在oldMethod()耗时少的情况下能正常工作,但是如果耗时长呢?因为调用oldMethod(),在它被传递到Observable.just()方法前就会阻塞了线程。

为了对付这个问题,以下是我一直使用的方法——使用defer方法将耗时长的部分包装起来。

private Object slowBlockingMethod() { ... }

public Observable<Object> newMethod() {
    return Observable.defer(() -> Observable.just(slowBlockingMethod()));
}

现在, 直到你订阅Observable才会去调用slowBlockMethod()方法。

生命周期

我把最难的部分留在了最后。你是如何处理RxJava与Activity的生命周期的(配合使用的)?以下两个问题会多次出现:

  • 1.Activity的配置发生变化后继续订阅一个Subscribtion

    假设你用Retrofit做了一次REST请求,想要把请求结果展示在ListView上。如果用户旋转了屏幕怎么办?如果你想要继续相同的请求,但是如何做呢?

  • 2.Observables持有Context引用会造成内存泄漏。

    这个问题是由于创建了一个以某种方式持有Contextsubscribtion,特别是你和Views交互的时候容易出现。如果Observable没有准时完成,最后可能持有非常多的额外内存。

不幸的是,两个问题都没有很好的解决办法。但是有一些能节约你时间的指导方针。

第一个问题能用一些RxJava内置的缓存机制解决,因此你可以取消订阅/再订阅同一个Observable,而不需要重复它之前的(准备)工作。特别的,cache()(或是replay())方法将继续在(方法)之下的请求(即使你取消订阅)。这意味着Activity重新生成时,你能重新开始新的subscription。

Observable<Photo> request = service.getUserPhoto(id).cache();
Subscription sub = request.subscribe(photo -> handleUserPhoto(photo));

// ...When the Activity is being recreated...
sub.unsubscribe();

// ...Once the Activity is recreated...
request.subscribe(photo -> handleUserPhoto(photo));

注意我们在两种情况下,用的是同一个缓存的请求(request),那种方式隐含的调用只会发生一次。存放请求的地方你自己决定,但是像所有的生命周期解决方案一样,它必须存放在生命周期之外的地方(一个保存的fragment,单例等等)。

第二问题可以通过根据生命周期正确的取消订阅来实现。通用的方法是用一个CompositeSubscription来持有所有的Subscription,然后在onDestroy()onDestroyView()方法中取消所有的订阅。

private CompositeSubscription mCompositeSubscription
    = new CompositeSubscription();

private void doSomething() {
    mCompositeSubscription.add(
        AndroidObservable.bindActivity(this, Observable.just("Hello, World!"))
        .subscribe(s -> System.out.println(s)));
}

@Override
protected void onDestroy() {
    super.onDestroy();

    mCompositeSubscription.unsubscribe();
}

更进一步,你可以创建一个根ActivityFragment,顺带添加一个CompositeSubscription,随后可以相应的取消订阅。

注意,只要你调用了CompositeSubscription.unsubscribe(),该对象(`CompositeSubscription`)就不能用了。因为它会自动取消订阅随后你添加的任何事物。如果你今后想要使用这种方法,你必须创建一个新的CompositeSubscription作为替代。

两个问题的解决方法都涉及到添加代码,我希望有一天能出现不需要写这些样板代码就能解决这些问题的天才。

本文翻译自Grokking RxJava, Part 4: Reactive Android,著作权归原作者danlew所有。译文由JohnTsai翻译。转载请注明出处,并保留此段声明。

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

推荐阅读更多精彩内容