以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>
<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>
网络请求
<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>
压合(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并非一次性的,而是可以多次使用,直到它超时或被销毁。这样的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>