以GitHub上的一个例子进行分析一下
https://github.com/rengwuxian/RxJavaSamples
先讲下总体架构
分为基本、转换(MAP)、压合(ZIP)、TOKEN(FLATMAP)、TOKEN_高级(RETRYWHEN)、缓存(BEHAVIORSUBJECT)。
整体是通过viewpager+fragment进行实现,通过Tablayout和viewpager进行关联,进行网络请求展示相关数据。
定义接口API
<span style="font-size:18px;">public interface ZhuangbiApi {
@GET("search")
Observable<List<ZhuangbiImage>> search(@Query("q") String query);
}</span>
定义NetWork类返回API接口的实现类。
<span style="font-size:18px;">public class Network {
private static ZhuangbiApi zhuangbiApi;
private static GankApi gankApi;
private static FakeApi fakeApi;
private static OkHttpClient okHttpClient = new OkHttpClient();
private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create();
private static CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create();
public static ZhuangbiApi getZhuangbiApi() {
if (zhuangbiApi == null) {
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl("http://zhuangbi.info/")
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(rxJavaCallAdapterFactory)
.build();
zhuangbiApi = retrofit.create(ZhuangbiApi.class);
}
return zhuangbiApi;
}
public static GankApi getGankApi() {
if (gankApi == null) {
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl("http://gank.io/api/")
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(rxJavaCallAdapterFactory)
.build();
gankApi = retrofit.create(GankApi.class);
}
return gankApi;
}
public static FakeApi getFakeApi() {
if (fakeApi == null) {
fakeApi = new FakeApi();
}
return fakeApi;
}
}</span>
基本使用
<span style="font-size:18px;">Observer<List<ZhuangbiImage>> observer = new Observer<List<ZhuangbiImage>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.loading_failed, Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(List<ZhuangbiImage> images) {
swipeRefreshLayout.setRefreshing(false);
adapter.setImages(images);
}
};</span>
在onNext方法执行完成后进行数据的展示。
网络请求
<span style="font-size:18px;"> subscription = Network.getZhuangbiApi()
.search(key)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);</span>
转换(MAP)
有些服务端的接口设计,会在返回数据外层包裹一些额外信息,这些信息对于调试很有用,但本地显示是用不到的,使用map()可以把外层的格式去掉,只留下需要显示的数据格式。
以下是网络请求代码
<span style="font-size:18px;">unsubscribe();//当点击按钮进行网络请求时,先取消然后在进行网络请求
subscription = Network.getGankApi()
.getBeauties(10, page)
.map(GankBeautyResultToItemsMapper.getInstance())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);</span>
map的功能类似下图所示:
下面是Func1的实现类
<span style="font-size:18px;">public class GankBeautyResultToItemsMapper implements Func1<GankBeautyResult, List<Item>> {
private static GankBeautyResultToItemsMapper INSTANCE = new GankBeautyResultToItemsMapper();
private GankBeautyResultToItemsMapper() {
}
public static GankBeautyResultToItemsMapper getInstance() {
return INSTANCE;
}
@Override
public List<Item> call(GankBeautyResult gankBeautyResult) {
List<GankBeauty> gankBeauties = gankBeautyResult.beauties;
List<Item> items = new ArrayList<>(gankBeauties.size());
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");
SimpleDateFormat outputFormat = new SimpleDateFormat("yy/MM/dd HH:mm:ss");
for (GankBeauty gankBeauty : gankBeauties) {
Item item = new Item();
try {
Date date = inputFormat.parse(gankBeauty.createdAt);
item.description = outputFormat.format(date);
} catch (ParseException e) {
e.printStackTrace();
item.description = "unknown date";
}
item.imageUrl = gankBeauty.url;
items.add(item);
}
return items;
}
}</span>
http://reactivex.io/documentation/operators/map.html 这篇文章对map进行了介绍。
压合(ZIP)
有时候,app中会需要同时访问不同接口,然后将结果糅合后转为统一的格式后输出。这种并行的异步处理比较麻烦,不过用了zip()之后就会简单得多。
下面是网络请求的代码
<span style="font-size:18px;">unsubscribe();
subscription = Observable.zip(Network.getGankApi().getBeauties(200, 1).map(GankBeautyResultToItemsMapper.getInstance()),
Network.getZhuangbiApi().search("装逼"),
new Func2<List<Item>, List<ZhuangbiImage>, List<Item>>() {
@Override
public List<Item> call(List<Item> gankItems, List<ZhuangbiImage> zhuangbiImages) {
List<Item> items = new ArrayList<Item>();
for (int i = 0; i < gankItems.size() / 2 && i < zhuangbiImages.size(); i++) {
items.add(gankItems.get(i * 2));
items.add(gankItems.get(i * 2 + 1));
Item zhuangbiItem = new Item();
ZhuangbiImage zhuangbiImage = zhuangbiImages.get(i);
zhuangbiItem.description = zhuangbiImage.description;
zhuangbiItem.imageUrl = zhuangbiImage.image_url;
items.add(zhuangbiItem);
}
return items;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);</span>
zip就是将多个Observable糅合成一个Observable。然后在进行数据的展示。
如下图例所示
Token(flatMap)
多余安全性、性能等方面的考虑,多数服务器会有一些接口需要传入token才能正确返回结果,而token是需要从另一个接口获取的,这就需要使用两步连续的请求才能获取数据。使用flatMap()可以用较为清晰的代码实现这种连续请求,避免Callback嵌套的结构。
API是这样定义的:
<span style="font-size:18px;">public class FakeApi {
Random random = new Random();
public Observable<FakeToken> getFakeToken(@NonNull String fakeAuth) {
return Observable.just(fakeAuth)
.map(new Func1<String, FakeToken>() {
@Override
public FakeToken call(String fakeAuth) {
// Add some random delay to mock the network delay
int fakeNetworkTimeCost = random.nextInt(500) + 500;
try {
Thread.sleep(fakeNetworkTimeCost);
} catch (InterruptedException e) {
e.printStackTrace();
}
FakeToken fakeToken = new FakeToken();
fakeToken.token = createToken();
return fakeToken;
}
});
}
private static String createToken() {
return "fake_token_" + System.currentTimeMillis() % 10000;
}
public Observable<FakeThing> getFakeData(FakeToken fakeToken) {
return Observable.just(fakeToken)
.map(new Func1<FakeToken, FakeThing>() {
@Override
public FakeThing call(FakeToken fakeToken) {
// Add some random delay to mock the network delay
int fakeNetworkTimeCost = random.nextInt(500) + 500;
try {
Thread.sleep(fakeNetworkTimeCost);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (fakeToken.expired) {
throw new IllegalArgumentException("Token expired!");
}
FakeThing fakeData = new FakeThing();
fakeData.id = (int) (System.currentTimeMillis() % 1000);
fakeData.name = "FAKE_USER_" + fakeData.id;
return fakeData;
}
});
}
}</span>
网络请求是这样的:
<strong> </strong> <span style="font-size:18px;">unsubscribe();
final FakeApi fakeApi = Network.getFakeApi();
subscription = fakeApi.getFakeToken("fake_auth_code")
.flatMap(new Func1<FakeToken, Observable<FakeThing>>() {
@Override
public Observable<FakeThing> call(FakeToken fakeToken) {
return fakeApi.getFakeData(fakeToken);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<FakeThing>() {
@Override
public void call(FakeThing fakeData) {
swipeRefreshLayout.setRefreshing(false);
tokenTv.setText(getString(R.string.got_data, fakeData.id, fakeData.name));
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.loading_failed, Toast.LENGTH_SHORT).show();
}
});</span>
TOKEN_高级(RETRYWHEN)
有的token并非一次性的,而是可以多次使用,直到它超时或被销毁。这样的token处理起来比较麻烦:需要把他保存起来,并且在发现它失效的时候要能够自动重新获取新的token并继续访问之前由于token失效而失败的请求。用传统的Callback请求会写出非常复杂的代码,而RxJava的RetryWhen可以轻松的处理这样的问题。
<span style="font-size:18px;">final FakeApi fakeApi = Network.getFakeApi();
subscription = Observable.just(null)
.flatMap(new Func1<Object, Observable<FakeThing>>() {
@Override
public Observable<FakeThing> call(Object o) {
return cachedFakeToken.token == null
? Observable.<FakeThing>error(new NullPointerException("Token is null!"))
: fakeApi.getFakeData(cachedFakeToken);
}
})
.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
if (throwable instanceof IllegalArgumentException || throwable instanceof NullPointerException) {
return fakeApi.getFakeToken("fake_auth_code")
.doOnNext(new Action1<FakeToken>() {
@Override
public void call(FakeToken fakeToken) {
tokenUpdated = true;
cachedFakeToken.token = fakeToken.token;
cachedFakeToken.expired = fakeToken.expired;
}
});
}
return Observable.error(throwable);
}
});
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<FakeThing>() {
@Override
public void call(FakeThing fakeData) {
swipeRefreshLayout.setRefreshing(false);
String token = cachedFakeToken.token;
if (tokenUpdated) {
token += "(" + getString(R.string.updated) + ")";
}
tokenTv.setText(getString(R.string.got_token_and_data, token, fakeData.id, fakeData.name));
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.loading_failed, Toast.LENGTH_SHORT).show();
}
});</span>
RxJava中有一个很少被人用到的类叫做Subject,它是一种{既是Observable又是Obsver}的东西,因此可以被用做中间件来做数据传递。可以用他的子类BehaviorSubject来进行缓存。
下面是网络请求
<span style="font-size:18px;">Network.getGankApi()
.getBeauties(100, 1)
.subscribeOn(Schedulers.io())
.map(GankBeautyResultToItemsMapper.getInstance())
.doOnNext(new Action1<List<Item>>() {
@Override
public void call(List<Item> items) {
Database.getInstance().writeItems(items);
}
})
.subscribe(new Action1<List<Item>>() {
@Override
public void call(List<Item> items) {
cache.onNext(items);//cache是BehaviorSubject的对象
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});</span>