RxJava 近几年开始在 Android 开发者社区流行,但是由于解决的是复杂的异步操作问题,再加上 Java 语法的限制导致 RxJava 的代码看起来比较费劲,RxJav 的学习曲线也进一步被拉高了。本文介绍 RxJava 的基本原理以及其要解决的问题,来帮助初学者理解 RxJava 框架。
Cat App
将使用一个真实世界的例子来逐步帮助大家理解 RxJava 的设计思想。 所以我们将创建一个下载 Cat 图片的应用。
需要解决的问题是:
有一个 web 服务提供了一些 API 来搜索整个网络上的符合查询关键字的所有猫的图片。 每个图片包含一个可爱程度的参数 – 一个整数值表示其可爱程度。 我们的任务就是 :下载返回的猫图片,选择最可爱的那个,并且把图片保存到本地存储中。
我们只关注 下载、处理和保存图片的三个核心功能。
下面开动吧!
Model 和 API
下面是一个简单的描述 Cat 的模型类:
Java
public class Cat implements Comparable{
Bitmap image;
int cuteness;
@Override
public int compareTo(Cat another) {
return Integer.compare(cuteness, another.cuteness);
}
}
1
2
3
4
5
6
7
8
9
publicclassCatimplementsComparable{
Bitmapimage;
intcuteness;
@Override
publicintcompareTo(Catanother){
returnInteger.compare(cuteness,another.cuteness);
}
}
我们的 API 将会使用来自 cat-sdk.jar 中的阻塞风格的接口。
Java
public interface Api {
List queryCats(String query);
Uri store(Cat cat);
}
1
2
3
4
publicinterfaceApi{
ListqueryCats(Stringquery);
Uristore(Catcat);
}
看起来很简单吧! 现在开始编写我们的业务逻辑。
Java
public class CatsHelper {
Api api;
public Uri saveTheCutestCat(String query){
List cats = api.queryCats(query);
Cat cutest = findCutest(cats);
Uri savedUri = api.store(cutest);
return savedUri;
}
private Cat findCutest(List cats) {
return Collections.max(cats);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
publicclassCatsHelper{
Apiapi;
publicUrisaveTheCutestCat(Stringquery){
Listcats=api.queryCats(query);
Catcutest=findCutest(cats);
UrisavedUri=api.store(cutest);
returnsavedUri;
}
privateCatfindCutest(Listcats){
returnCollections.max(cats);
}
}
(@ο@) 哇~,这也太简单太清晰了吧!来看看上面的代码是多么的酷。主要的函数 saveTheCutestCat 只包含了 3个函数调用。使用参数来调用这些函数,并接收返回的参数。 并且等待每个函数执行并返回结果。
如此简单、如此有效。下面来看看这种简单函数的其他优点。
组合(Composition)
可以看到我们的 saveTheCutestCat 由其他三个函数调用所组成的。我们通过函数来把一个大功能分割为每个容易理解的小功能。通过函数调用来组合使用这些小功能。使用和理解起来都相当简单。
异常传递
另外一个使用函数的好处就是方便处理异常。每个函数都可以通过抛出异常来结束运行。该异常可以在抛出异常的函数里面处理,也可以在调用该函数的外面处理,所以我们无需每次都处理每个异常,我们可以在一个地方处理所有可能抛出的异常。
Java
try{
List cats = api.queryCats(query);
Cat cutest = findCutest(cats);
Uri savedUri = api.store(cutest);
return savedUri;
} catch (Exception e) {
e.printStackTrace()
return someDefaultValue;
}
1
2
3
4
5
6
7
8
9
try{
Listcats=api.queryCats(query);
Catcutest=findCutest(cats);
UrisavedUri=api.store(cutest);
returnsavedUri;
}catch(Exceptione){
e.printStackTrace()
returnsomeDefaultValue;
}
这样,我们就可以处理这三个函数中所抛出的任何异常了。如果没有 try catch 语句,我们也可以把异常继续传递下去。
向异步进军
但是,现实世界中我们往往没法等待。有些时候你没法只使用阻塞调用。在 Android 中你需要处理各种异步操作。
就拿 Android 的 OnClickListener 接口来说吧, 如果你需要处理一个 View 的点击事件,你必须提供一个 该 Listener 的实现来处理用户的点击事件。下面来看看如何处理异步调用。
异步网络调用
假设我们的 cats-sdk.jar 使用了异步调用的 API 来访问网络资源,例如使用 Ion来异步访问网络。
这样我们的新 API 接口就变为这样了:
Java
public interface Api {
interface CatsQueryCallback {
void onCatListReceived(List cats);
void onError(Exception e);
}
void queryCats(String query, CatsQueryCallback catsQueryCallback);
Uri store(Cat cat);
}
1
2
3
4
5
6
7
8
9
10
11
publicinterfaceApi{
interfaceCatsQueryCallback{
voidonCatListReceived(Listcats);
voidonError(Exceptione);
}
voidqueryCats(Stringquery,CatsQueryCallbackcatsQueryCallback);
Uristore(Catcat);
}
这样我们查询猫的操作就变为异步的了, 通过 CatsQueryCallback 回调接口来结束查询的数据和处理异常情况。
我们的业务逻辑也需要跟着改变一下:
Java
public class CatsHelper {
public interface CutestCatCallback {
void onCutestCatSaved(Uri uri);
void onQueryFailed(Exception e);
}
Api api;
public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){
api.queryCats(query, new Api.CatsQueryCallback() {
@Override
public void onCatListReceived(List cats) {
Cat cutest = findCutest(cats);
Uri savedUri = api.store(cutest);
cutestCatCallback.onCutestCatSaved(savedUri);
}
@Override
public void onError(Exception e) {
cutestCatCallback.onQueryFailed(e);
}
});
}
private Cat findCutest(List cats) {
return Collections.max(cats);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
publicclassCatsHelper{
publicinterfaceCutestCatCallback{
voidonCutestCatSaved(Uriuri);
voidonQueryFailed(Exceptione);
}
Apiapi;
publicvoidsaveTheCutestCat(Stringquery,CutestCatCallbackcutestCatCallback){
api.queryCats(query,newApi.CatsQueryCallback(){
@Override
publicvoidonCatListReceived(Listcats){
Catcutest=findCutest(cats);
UrisavedUri=api.store(cutest);
cutestCatCallback.onCutestCatSaved(savedUri);
}
@Override
publicvoidonError(Exceptione){
cutestCatCallback.onQueryFailed(e);
}
});
}
privateCatfindCutest(Listcats){
returnCollections.max(cats);
}
}
这样我们就没法使用阻塞式函数调用了, 我们无法继续使用阻塞调用来使用这些 API 了(当然了,万事无绝对,如果你使用 synchronized, CountdownLatch 等方式来阻塞线程,你还是可以继续使用,本文我们需要处理异步方式来理解这些概念。) 所以,我们没法让 saveTheCutestCat 函数返回一个值了, 我们需要一个回调接口来异步的处理结果。
这里我们再进一步,使用两个异步操作来实现我们的功能, 例如 我们还使用异步 IO 来写文件。
Java
public interface Api {
interface CatsQueryCallback {
void onCatListReceived(List cats);
void onQueryFailed(Exception e);
}
interface StoreCallback{
void onCatStored(Uri uri);
void onStoreFailed(Exception e);
}
void queryCats(String query, CatsQueryCallback catsQueryCallback);
void store(Cat cat, StoreCallback storeCallback);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
publicinterfaceApi{
interfaceCatsQueryCallback{
voidonCatListReceived(Listcats);
voidonQueryFailed(Exceptione);
}
interfaceStoreCallback{
voidonCatStored(Uriuri);
voidonStoreFailed(Exceptione);
}
voidqueryCats(Stringquery,CatsQueryCallbackcatsQueryCallback);
voidstore(Catcat,StoreCallbackstoreCallback);
}
业务逻辑变为:
Java
public class CatsHelper {
public interface CutestCatCallback {
void onCutestCatSaved(Uri uri);
void onError(Exception e);
}
Api api;
public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){
api.queryCats(query, new Api.CatsQueryCallback() {
@Override
public void onCatListReceived(List cats) {
Cat cutest = findCutest(cats);
api.store(cutest, new Api.StoreCallback() {
@Override
public void onCatStored(Uri uri) {
cutestCatCallback.onCutestCatSaved(uri);
}
@Override
public void onStoreFailed(Exception e) {
cutestCatCallback.onError(e);
}
});
}
@Override
public void onQueryFailed(Exception e) {
cutestCatCallback.onError(e);
}
});
}
private Cat findCutest(List cats) {
return Collections.max(cats);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
publicclassCatsHelper{
publicinterfaceCutestCatCallback{
voidonCutestCatSaved(Uriuri);
voidonError(Exceptione);
}
Apiapi;
publicvoidsaveTheCutestCat(Stringquery,CutestCatCallbackcutestCatCallback){
api.queryCats(query,newApi.CatsQueryCallback(){
@Override
publicvoidonCatListReceived(Listcats){
Catcutest=findCutest(cats);
api.store(cutest,newApi.StoreCallback(){
@Override
publicvoidonCatStored(Uriuri){
cutestCatCallback.onCutestCatSaved(uri);
}
@Override
publicvoidonStoreFailed(Exceptione){
cutestCatCallback.onError(e);
}
});
}
@Override
publicvoidonQueryFailed(Exceptione){
cutestCatCallback.onError(e);
}
});
}
privateCatfindCutest(Listcats){
returnCollections.max(cats);
}
}
现在再来看看我们的业务逻辑代码,是不是和之前的阻塞式调用那么简单、那么清晰? 当然不一样了,上面的异步操作代码看起来太恐怖了! 这里有太多的干扰代码了,太多的匿名类了,但是不可否认,他们的业务逻辑其实是一样的。查询猫的列表数据、找出最可爱的并保持其图片。
组合功能也不见了! 现在你没法像阻塞操作一样来组合调用每个功能了。异步操作中,每次你都必须通过回调接口来手工的处理结果。
那么关于异常传递和处理呢? 没了!异步代码中的异常不会自动传递了,我们需要手工的重新传递出去。(onStoreFailed 和 onQueryFailed 就是干这事用的)
上面的代码非常难懂也更难发现潜在的 BUG。
然后呢?我们如何处理这种情况呢?我们是不是就被困在这种无法组合的回调接口困局中呢? 下次我们来看看如何优化该问题。