Android中通过RxJava进行响应式程序设计的入门指南

错误处理

到目前为止,我们都没怎么介绍onComplete()和onError()函数。这两个函数用来通知订阅者,被观察的对象将停止发送数据以及为什么停止(成功的完成或者出错了)。

下面的代码展示了怎么使用这两个函数:

Observable.just("Hello, world!")
  .map(s -> potentialException(s))
  .map(s -> anotherPotentialException(s))
  .subscribe(new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { System.out.println("Completed!"); }

    @Override
    public void onError(Throwable e) { System.out.println("Ouch!"); }
  });

代码中的potentialException() 和 anotherPotentialException()有可能会抛出异常。每一个Observerable对象在终结的时候都会调用onCompleted()或者onError()方法,所以Demo中会打印”Completed!”或者”Ouch!”。

这种模式有以下几个优点:

1.只要有异常发生onError()一定会被调用

这极大的简化了错误处理。只需要在一个地方处理错误即可以。

2.操作符不需要处理异常

将异常处理交给订阅者来做,Observerable的操作符调用链中一旦有一个抛出了异常,就会直接执行onError()方法。

3.你能够知道什么时候订阅者已经接收了全部的数据。

知道什么时候任务结束能够帮助简化代码的流程。(虽然有可能Observable对象永远不会结束)

我觉得这种错误处理方式比传统的错误处理更简单。传统的错误处理中,通常是在每个回调中处理错误。这不仅导致了重复的代码,并且意味着每个回调都必须知道如何处理错误,你的回调代码将和调用者紧耦合在一起。

使用RxJava,Observable对象根本不需要知道如何处理错误!操作符也不需要处理错误状态-一旦发生错误,就会跳过当前和后续的操作符。所有的错误处理都交给订阅者来做。

调度器

假设你编写的Android app需要从网络请求数据(感觉这是必备的了,还有单机么?)。网络请求需要花费较长的时间,因此你打算在另外一个线程中加载数据。那么问题来了!

编写多线程的Android应用程序是很难的,因为你必须确保代码在正确的线程中运行,否则的话可能会导致app崩溃。最常见的就是在非主线程更新UI。

使用RxJava,你可以使用subscribeOn()指定观察者代码运行的线程,使用observerOn()指定订阅者运行的线程:

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

是不是很简单?任何在我的Subscriber前面执行的代码都是在I/O线程中运行。最后,操作view的代码在主线程中运行.

最棒的是我可以把subscribeOn()和observerOn()添加到任何Observable对象上。这两个也是操作符!。我不需要关心Observable对象以及它上面有哪些操作符。仅仅运用这两个操作符就可以实现在不同的线程中调度。

如果使用AsyncTask或者其他类似的,我将不得不仔细设计我的代码,找出需要并发执行的部分。使用RxJava,我可以保持代码不变,仅仅在需要并发的时候调用这两个操作符就可以。

订阅(Subscriptions)

当调用Observable.subscribe(),会返回一个Subscription对象。这个对象代表了被观察者和订阅者之间的联系。

ubscription subscription = Observable.just("Hello, World!")
  .subscribe(s -> System.out.println(s));

你可以在后面使用这个Subscription对象来操作被观察者和订阅者之间的联系.

subscription.unsubscribe();
System.out.println("Unsubscribed=" + subscription.isUnsubscribed());
// Outputs "Unsubscribed=true"

RxJava的另外一个好处就是它处理unsubscribing的时候,会停止整个调用链。如果你使用了一串很复杂的操作符,调用unsubscribe将会在他当前执行的地方终止。不需要做任何额外的工作!

RxAndroid

RxAndroid是RxJava的一个针对Android平台的扩展。它包含了一些能够简化Android开发的工具。

首先,AndroidSchedulers提供了针对Android的线程系统的调度器。需要在UI线程中运行某些代码?很简单,只需要使用AndroidSchedulers.mainThread():

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

如果你已经创建了自己的Handler,你可以使用HandlerThreadScheduler1将一个调度器链接到你的handler上。

接着要介绍的就是AndroidObservable,它提供了跟多的功能来配合Android的生命周期。bindActivity()和bindFragment()方法默认使用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,使用它可以给View添加了一些绑定。如果你想在每次点击view的时候都收到一个事件,可以使用ViewObservable.clicks(),或者你想监听TextView的内容变化,可以使用ViewObservable.text()。

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

Retrofit

大名鼎鼎的Retrofit库内置了对RxJava的支持(官方下载页http://square.github.io/retrofit/#download)。通常调用发可以通过使用一个Callback对象来获取异步的结果:

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

使用RxJava,你可以直接返回一个Observable对象。

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

现在你可以随意使用Observable对象了。你不仅可以获取数据,还可以进行变换。
Retrofit对Observable的支持使得它可以很简单的将多个REST请求结合起来。比如我们有一个请求是获取照片的,还有一个请求是获取元数据的,我们就可以将这两个请求并发的发出,并且等待两个结果都返回之后再做处理:

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

在第二篇里我展示过一个类似的例子(使用flatMap())。这里我只是想展示以下使用RxJava+Retrofit可以多么简单地组合多个REST请求。

遗留代码,运行极慢的代码

Retrofit可以返回Observable对象,但是如果你使用的别的库并不支持这样怎么办?或者说一个内部的内码,你想把他们转换成Observable的?有什么简单的办法没?

绝大多数时候Observable.just() 和 Observable.from() 能够帮助你从遗留代码中创建 Observable 对象:

private Object oldMethod() { ... }

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

上面的例子中如果oldMethod()足够快是没有什么问题的,但是如果很慢呢?调用oldMethod()将会阻塞住他所在的线程。
为了解决这个问题,可以参考我一直使用的方法–使用defer()来包装缓慢的代码:

private Object slowBlockingMethod() { ... }

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

现在,newMethod()的调用不会阻塞了,除非你订阅返回的observable对象。

生命周期

我把最难的不分留在了最后。如何处理Activity的生命周期?主要就是两个问题:
1.在configuration改变(比如转屏)之后继续之前的Subscription。

比如你使用Retrofit发出了一个REST请求,接着想在listview中展示结果。如果在网络请求的时候用户旋转了屏幕怎么办?你当然想继续刚才的请求,但是怎么搞?

2.Observable持有Context导致的内存泄露

这个问题是因为创建subscription的时候,以某种方式持有了context的引用,尤其是当你和view交互的时候,这太容易发生!如果Observable没有及时结束,内存占用就会越来越大。
不幸的是,没有银弹来解决这两个问题,但是这里有一些指导方案你可以参考。

第一个问题的解决方案就是使用RxJava内置的缓存机制,这样你就可以对同一个Observable对象执行unsubscribe/resubscribe,却不用重复运行得到Observable的代码。cache() (或者 replay())会继续执行网络请求(甚至你调用了unsubscribe也不会停止)。这就是说你可以在Activity重新创建的时候从cache()的返回值中创建一个新的Observable对象。

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));

注意,两次sub是使用的同一个缓存的请求。当然在哪里去存储请求的结果还是要你自己来做,和所有其他的生命周期相关的解决方案一延虎,必须在生命周期外的某个地方存储。(retained fragment或者单例等等)。

第二个问题的解决方案就是在生命周期的某个时刻取消订阅。一个很常见的模式就是使用CompositeSubscription来持有所有的Subscriptions,然后在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();
}

你可以在Activity/Fragment的基类里创建一个CompositeSubscription对象,在子类中使用它。

注意! 一旦你调用了 CompositeSubscription.unsubscribe(),这个CompositeSubscription对象就不可用了, 如果你还想使用CompositeSubscription,就必须在创建一个新的对象了。

(0)

相关推荐

  • Android Retrofit和Rxjava的网络请求

    Android  Retrofit和Rxjava的网络请求 去年的时候好多公司就已经使用Rxjava和Retrofit了,最近自自己学习了一下,感觉真的是很好用,让自己的网络请求变得更简单了,而且封装性极强. 首先做一下准备工作,导入需要引用的文件 compile 'com.android.support:appcompat-v7:25.1.0' testCompile 'junit:junit:4.12' compile 'io.reactivex:rxjava:1.1.0' compile

  • 一篇文章就能了解Rxjava

    前言: 第一次接触RxJava是在前不久,一个新Android项目的启动,在评估时选择了RxJava.RxJava是一个基于事件订阅的异步执行的一个类库.听起来有点复杂,其实是要你使用过一次,就会大概明白它是怎么回事了!为是什么一个Android项目启动会联系到RxJava呢?因为在RxJava使用起来得到广泛的认可,又是基于Java语言的.自然会有善于组织和总结的开发者联想到Android!没错,RxAndroid就这样在RxJava的基础上,针对Android开发的一个库.今天我们主要是来讲

  • Android中用RxJava和ViewPager实现轮播图

    前言 很多人要实现轮播图都会想到使用ViewPager + Handler来完成轮播图的效果.但是在RxJava快速发展的情况下,已经可以使用RxJava来代替Handler完成这样任务了. 下面我们就来介绍如何实现RxJava+ViewPager的轮播图. 效果图如下 ViewPager的操作 说到ViwePager应该大家都不陌生,它可以结合普通的View也可以结合Fragment一起使用.在此我也就不对它的使用方法进行过多的介绍了.直接开始介绍轮播的方法. 常见的轮播操作 private

  • android使用RxJava实现预加载

    在上一篇文章中介绍了使用非RxJava环境下,使用Handler机制SyncBarrier的特性实现预加载功能的方法. 在RxJava的环境下使用BehaviorSubject的特性来实现也是很方便的. BehaviorSubject内部会缓存消息流中最近的一个消息, 在后续有Subscriber订阅时,会直接将缓存的消息发送给Subscriber. RxPreLoader.java封装如下: import android.support.annotation.NonNull; import j

  • Android中的Retrofit+OkHttp+RxJava缓存架构使用

    RxJava如何与Retrofit结合 先扔出build.gradle文件的内容 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.0' compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxand

  • Android中通过RxJava进行响应式程序设计的入门指南

    错误处理 到目前为止,我们都没怎么介绍onComplete()和onError()函数.这两个函数用来通知订阅者,被观察的对象将停止发送数据以及为什么停止(成功的完成或者出错了). 下面的代码展示了怎么使用这两个函数: Observable.just("Hello, world!") .map(s -> potentialException(s)) .map(s -> anotherPotentialException(s)) .subscribe(new Subscrib

  • Android 中 Activity显示隐式跳转

    安卓中经常涉及到activity的跳转,跳转怎么样在安卓中实现呢,如下. 创建第二个activity就是创建一个class,继承自Android.app.Activity. 创建第二个activity的同时需要在清单文件中配置,不然会找不到 <activity android:name="com.ldw.createActivity.SecondActivity"></activity> 入口activity有下面的代码,只要activity有下面的代码,就会创

  • Java及Android中常用链式调用写法简单示例

    本文实例讲述了Java及Android中常用链式调用写法.分享给大家供大家参考,具体如下: 最近发现,目前大火的许多开源框架中,大多都使用了一种"(方法).(方法).(方法)"的形式进行调用,最典型的就是RxJava.android中AlertDialog控件的源码也是这种形式的.查阅可知,大家把它叫做链式调用."行动是检验程序的唯一标准"0.0!查了.说了那么多,还是得自己写个例子并运行出预期的效果. /** * * 链式调用 * * @author k.k *

  • Android简单实现自定义流式布局的方法

    本文实例讲述了Android简单实现自定义流式布局的方法.分享给大家供大家参考,具体如下: 首先来看一下 手淘HD - 商品详情 - 选择商品属性 页面的UI 商品有很多尺码,而且展现每个尺码所需要的View的大小也不同(主要是宽度),所以在从服务器端拉到数据之前,展现所有尺码所需要的行数和每一行的个数都无法确定,因此不能直接使用GridView或ListView. 如果使用LinearLayout呢? 一个LinearLayout只能显示一行,如果要展示多行,则每一行都要new一个Linear

  • Android中AlertDilog显示简单和复杂列表的方法

    本文实例讲述了Android中AlertDialog显示简单和复杂列表的方法.分享给大家供大家参考,具体如下: AlertDialog 显示简单列表 setItems import Android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; import andro

  • Android中Spinner控件之键值对用法实例分析

    本文实例讲述了Android中Spinner控件之键值对用法.分享给大家供大家参考.具体如下: 一.字典表,用来存放键值对信息 package com.ljq.activity; import java.io.Serializable; @SuppressWarnings("serial") public class Dict implements Serializable { private Integer id; private String text; public Dict()

  • Android中ListView Item布局优化技巧

    本文实例讲述了Android中ListView Item布局优化技巧.分享给大家供大家参考,具体如下: 之前一直都不知道ListView有多种布局的优化方法,只能通过隐藏来实现,自己也知道效率肯定是很低的,但是也不知道有什么方法,这些天又查了一些资料,然后知道 其实google早就帮我们想好了优化方案了. 假设你的ListView Item有三种布局样式的可能:就比如很简单的显示一行字,要靠左,居中,靠右. 这时我们就可以在BaseAdapter里面重写两个方法: private static

  • Android中Activity常用功能设置小结(包括全屏、横竖屏等)

    本文实例讲述了Android中Activity常用功能设置小结(包括全屏.横竖屏等).分享给大家供大家参考,具体如下: Activity全屏设置 方式1:AndroidManifest.xml 复制代码 代码如下: <activity android:name="myAcitivty"  android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /> 方式2:代码实现 复制代码 代码如下: re

  • Android中Service实时向Activity传递数据实例分析

    本文实例讲述了Android中Service实时向Activity传递数据的方法.分享给大家供大家参考.具体如下: 这里演示一个案例,需求如下: 在Service组件中创建一个线程,该线程用来生产数值,每隔1秒数值自动加1,然后把更新后的数值在界面上实时显示. 步骤如下: 1.新建一个android项目工程,取名为demo. 2.新建一个Service类,用来实时生产数值,供界面实时显示. package com.ljq.activity; import android.app.Service;

  • 详解Android中Intent对象与Intent Filter过滤匹配过程

    如果对Intent不是特别了解,可以参见博文<详解Android中Intent的使用方法>,该文对本文要使用的action.category以及data都进行了详细介绍.如果想了解在开发中常见Intent的使用,可以参见<Android中Intent习惯用法>. 本文内容有点长,希望大家可以耐心读完. 本文在描述组件在manifest中注册的Intent Filter过滤器时,统一用intent-filter表示. 一.概述 我们知道,Intent是分两种的:显式Intent和隐式

随机推荐