有如下场景:
在前段调用后端的 API 时,经常会出现回调嵌套的情况。假设我们有两个 API,queryA 和 queryB. 并且 queryB 的运行依赖于 queryA 的结果。那么我们的程序在一般的情况下可能是这个样子。
想象有如下的代码:
是不是感觉非常不舒服?假如嵌套的 API 再多几层,那么这将是个灾难。一个人开发的时候可能不觉得有什么问题,但是可以想象做 code review 或者新入项目组的同事看到你这复杂的嵌套时的表情。
是时候让 flatmap 出现啦!让我们把程序稍微改造一下,在 Server 类里面把 makeRequest 的方式变成 RxJava 的 Observable 的形式(我会在例子之后解释为什么要用 flatmap()):
看上去好像没觉得有都简洁是么?你等着我给你看看假如嵌套多几层之后:
看到了么?在 RxJava 的链式调用下,所有之前需要嵌套的地方都被 flatMap() 隔开了。代码可读性大大增加!假如你的 IDE 支持 java 8 的话,你可以体验更美妙的事情:lambda!
在抛弃了可恶的匿名类之后,代码更加简洁了!
看来很多同学都不太理解为何要用 flatmap 来解决嵌套回调。那咱们就深入点。
flatMap() 究竟是用来干嘛的?简单的说,flatmap 把一个 Observable 变成多个 Observable,然后把得到的多个 Obervable 的元素一个个的发射出去。
假设你有一个 API queryA,用这个 query 可以通过一个 userID 得到一个指定用户的所有关注的明星的 userID(但是原始数据只是 String),你需要把这些 userID 一个个打印出来。
那么我们在调用 queryA 的时候就已经构建了一个 Obervable 了,我们暂且叫他 O1. 在 O1 每发射结果的同时,我们需要调用把返回的 String 结果变成另一个 Observable,O2,O2 含有所有的明星 userID,并且一个个发射出去。代码例子:
大家可以看到,新的 observable2 是一个 JsonObject 类型的。因为在第三行注释之后,我们返回了一个 (可以是多个) 新的包含所有 userID 的 observable,RxJava 会将这个(或者多个)Observable 平铺发射.
或者大家可以参考抛物线大神文章中的学生和课程的例子 大概在文章的中间
总之 flatmap 最核心的功能大家可以理解为:转换,可一个 Obervable 转换成多个 Observable,再讲结果平铺发射。
在我们的例子中,是一个 observable(O1) 变成另一个 observable(O2), 是一对一的关系,因为 queryA 只会返回一个 String s 的结果,所以我们只会将一个 observable(O2)平铺并发射。
所以这和 nested callback 有什么关系呢?
关系大了!
再想想,我们的刚刚做的和 nested callback 不同的地方差在哪里?唯一不通的地方是在获取所有明星 userID 的时候,我们是一个同步的操作,也就是说 userID 全部都包含在 queryA 返回的结果里面。我们用同步的方法把其封装成一个 JSONArray.
假如 queryA 返回的只是用户关注明星 userID 的 url 呢?假如在第二部我们需要再做一步 API call 呢?
是的,就算我们还需要一步 API call,程序的结构还是一样的:
flatmap 最核心的功能大家可以理解为:转换,可一个 Obervable 转换成多个 Observable,再将结果平铺发射。
至于转换的 Observable 里面的操作是同步还是异步,我们不需要去过多的思考,Rxjava 已经完美的解决了。
参考:www.jianshu.com/p/0f926fda6…
延伸:实例项目(体会 flatMap 用法)
一. 添加依赖
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'io.reactivex:rxjava:1.1.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
//Gson解析器
compile 'io.reactivex:rxandroid:1.1.0'
//rxjava中用到的AndroidSchedulers.mainThread()
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'
这里使用的是retrofit2.0,默认为okhttp3.0
二. 定义请求接口,转换 HTTPAPI 为 Java 接口
public interface IgankApi {
@GET("a.json")
Call<List<GirlEntity>> getGirl();
@GET("data/%E7%A6%8F%E5%88%A9/{count}/{page}")
Call<GirlJsonData> getGirl(@Path("count") int count, @Path("page") int page);
//与rxjava结合api
@GET("data/%E7%A6%8F%E5%88%A9/{count}/{page}")
Observable<GirlJsonData> getG(@Path("count") int count, @Path("page") int page);
}
三. 接着使用类 Retrofit 生成 接口的实现,使用了动态代理。
public static IgankApi getIgankApi() {
if (igankApi == null) {
synchronized (IgankApi.class) {
if (igankApi == null) {
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://gank.io/api/")
.addConverterFactory(gsonConverterFactory)
.client(okHttpClient)
.addCallAdapterFactory(rxJavaCallAdapterFactory)
.build();
igankApi = retrofit.create(IgankApi.class);
}
}
}
return igankApi;
}
四. 调用接口
private void getImg() {
NetUtils.getIgankApi().getG(10, 2).flatMap(new Func1<GirlJsonData, Observable<List<GirlEntity>>>() {
@Override
public Observable<List<GirlEntity>> call(GirlJsonData girlJsonData) {
return Observable.just(girlJsonData.getResults());
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<GirlEntity>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(List<GirlEntity> girlEntities) {
girlEntityList.addAll(girlEntities);
loge(girlEntityList.get(1).getUrl() + "");
myRecycleViewAdapter.notifyDataSetChanged();
}
});
}
再比如下面场景:
访问某个接口时并不能直接访问,而需要填入一个在线获取的 token ,代码应该怎么写?
Callback 方式,可以使用嵌套的 Callback:
@GET("/token")
public void getToken(Callback<String> callback);
@GET("/user")
public void getUser(@Query("token") String token, @Query("userId") String userId, Callback<User> callback);
...
getToken(new Callback<String>() {
@Override
public void success(String token) {
getUser(userId, new Callback<User>() {
@Override
public void success(User user) {
userView.setUser(user);
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
};
}
@Override
public void failure(RetrofitError error) {
// Error handling
...
}
});
使用 RxJava 的话,代码如下:
@GET("/token")
public Observable<String> getToken();
@GET("/user")
public Observable<User> getUser(@Query("token") String token, @Query("userId") String userId);
...
getToken()
.flatMap(new Func1<String, Observable<User>>() {
@Override
public Observable<User> call(String token) {
return getUser(token, userId);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onNext(User user) {
userView.setUser(user);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable error) {
// Error handling
...
}
});
使用操作符 flatMap() 就搞定了逻辑,依然是一条链。
flatMap() 操作符的作用是将 Observable 发射的数据集合变换为 Observables 集合,然后将这些 Observable 发射的数据平坦化的放进一个单独的 Observable,还是太抽象了。
简单说就是将 一个 List 或者数组中的每一条数据都 转换成一个 Observable 对象。
场景:
假如我们写了个网络请求, 然后拿出所有请求的数据, 现在我们不需要那么多数据, 我们只需要 city 字段和 WD 字段 (运行过上段代码就知道了), 而且这次我不在只给你一个 url 了,而是给你多个 url。
那我们就开始写代码吧 (代码是最好的老师):
public static final String HOST = "http://www.weather.com.cn";
List<String> values = new ArrayList<>();
private String TAG = "SecondActivity2";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
values.add("/adat/sk/101010100.html");
values.add("/adat/sk/101010100.html");
values.add("/adat/sk/101010100.html");
values.add("/adat/sk/101010100.html");
values.add("/adat/sk/101010100.html");
Observable.just(values).flatMap(new Func1<List<String>, Observable<?>>() {
@Override
public Observable<?> call(List<String> strings) {
return Observable.from(strings);
}
}).cast(String.class).map(new Func1<String, String>() {
@Override
public String call(String s) {
return doNetTaskForString(HOST + s); //取出想要的字段,这里我就不取出来了
}
}).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.i(TAG, "value: " + s);
}
});
}
@NonNull
@Override
public int getContentView() {
return R.layout.activity_second2;
}
private synchronized String doNetTaskForString(String s) {
HttpClient client = new DefaultHttpClient();
Log.i(TAG, "url:" + s);
HttpGet get = new HttpGet(s);
String result;
try {
HttpResponse response = client.execute(get);
Log.i(TAG, "state code :" + response.getStatusLine().getStatusCode());
if (200 == response.getStatusLine().getStatusCode()) {
result = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);
} else {
result = "状态行非200";
}
} catch (Exception e1) {
result = "抛出了异常" + e1.getMessage();
e1.printStackTrace();
}
return result;
}
打印 log:
这段代码里我们又用到的新的操作符 cast .
cast 的作用就是 在发射之前强制将 Observable 发射的所有数据转换为指定类型。
再举个例子。
这里结合了 retrofit 在 flatMap 中通过一个城市的 str, 返回一个 Observable, 这个 Observable 的参数是一些获取到的天气信息结构 WeatherData,这样在后面 subscrib 中就可以对其进行处理了。
flatMap 多线程执行任务
单线程
有如下代码:
private Observable<String> processUrlIpByOneFlatMap() {
return Observable.just(
"http://www.baidu.com/",
"http://www.google.com/",
"https://www.bing.com/")
.flatMap(new Func1<String, Observable<String>>() {
@Override
public Observable<String> call(String s) {
return createIpObservable(s);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
printLog(tvLogs, "Consume Data <- ", s);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
printErrorLog(tvLogs, "throwable call()", throwable.getMessage());
}
});
}
//根据主机获取ip
private Observable<String> createIpObservable(final String url) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
String ip = getIPByUrl(url);
subscriber.onNext(ip);
printLog(tvLogs, "Emit Data -> ",url+" : " +ip);
} catch (MalformedURLException e) {
e.printStackTrace();
//subscriber.onError(e);
subscriber.onNext(null);
} catch (UnknownHostException e) {
e.printStackTrace();
//subscriber.onError(e);
subscriber.onNext(null);
}
subscriber.onCompleted();
}
});
}
执行结果:
Emit Data -> 'http://www.baidu.com/ : 115.239.211.112'
Main Thread:false, Thread Name:RxCachedThreadScheduler-1
Consume Data <- '115.239.211.112'
Main Thread:true, Thread Name:main
Emit Data -> 'http://www.google.com/ : 216.58.199.100'
Main Thread:false, Thread Name:RxCachedThreadScheduler-1
Consume Data <- '216.58.199.100'
Main Thread:true, Thread Name:main
Emit Data -> 'https://www.bing.com/ : 202.89.233.104'
Main Thread:false, Thread Name:RxCachedThreadScheduler-1
Consume Data <- '202.89.233.104'
Main Thread:true, Thread Name:main
我们从上面的输出结果可以看出,效果和使用 map 操作符的效果是一样。
我们同时也发现线程的名称(Thread Name)都是 RxCachedThreadScheduler-1 ,说明他们是通过一个线程来完成所有的任务的。
多线程
如果任务很多,仅仅通过一个线程去做,效率上是不是有点低呢?如果我想使用多个线程来完成这些任务该怎么做呢?
很简单,只需要在创建 Observable 的时候加上 subscribeOn(Schedulers.io()) 即可。完整代码如下:
//根据主机获取ip
private Observable<String> createIpObservable(final String url) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
String ip = getIPByUrl(url);
subscriber.onNext(ip);
printLog(tvLogs, "Emit Data -> ",url+" : " +ip);
} catch (MalformedURLException e) {
e.printStackTrace();
//subscriber.onError(e);
subscriber.onNext(null);
} catch (UnknownHostException e) {
e.printStackTrace();
//subscriber.onError(e);
subscriber.onNext(null);
}
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io());
}
执行结果:
Consume Data <- ‘202.89.233.103’
Main Thread:true, Thread Name:main
Emit Data -> ‘https://www.bing.com/ : 202.89.233.103’
Main Thread:false, Thread Name:RxCachedThreadScheduler-8
Emit Data -> ‘http://www.google.com/ : 216.58.203.36’
Main Thread:false, Thread Name:RxCachedThreadScheduler-7
Consume Data <- ‘216.58.203.36’
Main Thread:true, Thread Name:main
Emit Data -> ‘http://www.baidu.com/ : 115.239.211.112’
Main Thread:false, Thread Name:RxCachedThreadScheduler-6
Consume Data <- ‘115.239.211.112’
Main Thread:true, Thread Name:main
从运行可以看出,执行完成任务的不是一个线程了,而是三个不同的线程 RxCachedThreadScheduler-8 、RxCachedThreadScheduler-7、RxCachedThreadScheduler-6 。
但是发现一个问题,输出的结果的顺序乱了,不是我们输入的 baidu.com/google.com/bing.com 顺序了。
那怎么办呢?
这时候 concatMap 操作符就闪亮登场了,详情:
blog.csdn.net/jdsjlzx/art…