最近學(xué)習(xí)了RxJava在android中的使用擂涛,關(guān)于RxJava是啥羞延,為什么要用RxJava,好在哪逛裤,這里就不敘述了指孤,如果想要了解請移步官方文檔启涯、大神文章。
這里只講解一下RxJava中的操作符恃轩,以及項(xiàng)目中具體的使用場景结洼。
因?yàn)閷W(xué)習(xí)了有20個(gè)操作符,可能一篇文章過于臃腫叉跛,所以打算寫成系列文章松忍,本文中所有的操作符使用,都寫在了一個(gè)demo中筷厘,已上傳至github
場景一:RxJava基本使用
配合Retrofit請求網(wǎng)絡(luò)數(shù)據(jù)鸣峭,如果你對Retrofit不熟悉就先看Retrofit官網(wǎng)宏所,實(shí)現(xiàn)步驟如下:
-
先是build.gradle的配置
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta3'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta3'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta3'
compile 'com.jakewharton:butterknife:7.0.1'也就是說本文是基于RxJava1.1.0和Retrofit 2.0.0-beta3來進(jìn)行的。
添加rxandroid是因?yàn)閞xjava中的線程問題摊溶。 -
基本網(wǎng)絡(luò)請求使用準(zhǔn)備
我們使用http://zhuangbi.info/search?q=param測試連接爬骤,返回的是json格式,代碼就不貼了
接下來我們要?jiǎng)?chuàng)建一個(gè)接口取名為ZhuangbiApi莫换,代碼如下:
public interface ZhuangbiApi { @GET("search") Observable<List<ImageInfoBean>> search(@Query("q") String query); }
Retrofit霞玄、Gson、RxJava結(jié)合使用拉岁,建立網(wǎng)絡(luò)請求類:
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; }
-
具體使用
將要查詢的關(guān)鍵字傳進(jìn)去坷剧,使用上面建立的網(wǎng)絡(luò)請求類請求數(shù)據(jù),并在訂閱者的回調(diào)方法中膛薛,進(jìn)行網(wǎng)絡(luò)請求結(jié)果的處理private void search(String key) { subscription = Network.getZhuangbiApi() .search(key) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(getObserver()); }
private Observer<? super List<ImageInfoBean>> getObserver() { if (null == observer) { observer = new Observer<List<ImageInfoBean>>() { @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<ImageInfoBean> images) { swipeRefreshLayout.setRefreshing(false); adapter.setImages(images); } }; } return observer; }
-
詳解
search方法中傳入的key是要查詢的關(guān)鍵詞听隐,getObserver()是獲取訂閱者對象补鼻,并在其回調(diào)方法中根據(jù)返回結(jié)果哄啄,做相應(yīng)處理:
其中onNext方法返回了數(shù)據(jù),這樣我們能夠在onNext里面處理數(shù)據(jù)相關(guān)的邏輯风范;
onError方法中處理錯(cuò)誤咨跌,同時(shí)也可以停止ProgressDialog等;
onComplated只調(diào)用一次結(jié)束本次請求操作硼婿,也可以停止ProgressDialog锌半;
場景二:Map操作符的使用(變換)
對Observable發(fā)射的每一項(xiàng)數(shù)據(jù)應(yīng)用一個(gè)函數(shù),執(zhí)行變換為指定類型的操作寇漫,然后再發(fā)射
有些服務(wù)端的接口設(shè)計(jì)刊殉,會(huì)在返回的數(shù)據(jù)外層包裹一些額外信息,這些信息對于調(diào)試很有用州胳,但本地顯示是用不到的记焊。使用 map() 可以把外層的格式剝掉,只留下我們只關(guān)心的部分栓撞,具體實(shí)現(xiàn)步驟如下:
-
網(wǎng)絡(luò)請求使用準(zhǔn)備
我們使用http://gank.io/api/測試連接
接下來我們要?jiǎng)?chuàng)建一個(gè)接口取名為GankApi 遍膜,代碼如下:public interface GankApi { @GET("data/福利/{number}/{page}") Observable<BeautyResult> getBeauties(@Path("number") int number, @Path("page") int page); }
Retrofit、Gson瓤湘、RxJava結(jié)合使用瓢颅,建立網(wǎng)絡(luò)請求類:
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; }
-
數(shù)據(jù)轉(zhuǎn)換
返回?cái)?shù)據(jù)就不貼了,有興趣可以請求接口看一下弛说。
接口返回的數(shù)據(jù)包含了一些額外的信息挽懦,但是我們只需要返回?cái)?shù)據(jù)中的list部分,所以創(chuàng)建一個(gè)類木人,來實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換的功能巾兆,代碼如下:public class BeautyResult2Beautise implements Func1<BeautyResult, List<ImageInfoBean>> { public static BeautyResult2Beautise newInstance() { return new BeautyResult2Beautise(); } /** * 將接口返回的BeautyResult數(shù)據(jù)中的list部分提取出來猎物,返回集合List<ImageInfoBean> * @param beautyResult * @return */ @Override public List<ImageInfoBean> call(BeautyResult beautyResult) { List<ImageInfoBean> imageInfoBeanList = new ArrayList<>(beautyResult.results.size()); for (ImageInfoBean bean : beautyResult.results) { ImageInfoBean imageInfoBean = new ImageInfoBean(); imageInfoBean.description = bean.desc; imageInfoBean.image_url = bean.url; imageInfoBeanList.add(imageInfoBean); } return imageInfoBeanList; } }
-
操作符的使用
加載數(shù)據(jù)
/** * 加載數(shù)據(jù)的方法 * @param page */ private void loadPage(int page) { mSwipeRefreshLayout.setRefreshing(true); unsubscribe(); subscription = Network.getGankApi() .getBeauties(8, page) .map(BeautyResult2Beautise.newInstance()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(getObserver()); }
訂閱者
/** * 獲取訂閱者 * @return */ private Observer<? super List<ImageInfoBean>> getObserver() { if (null == observer) { observer = new Observer<List<ImageInfoBean>>() { @Override public void onCompleted() { mSwipeRefreshLayout.setRefreshing(false); } @Override public void onError(Throwable e) { mSwipeRefreshLayout.setRefreshing(false); Toast.makeText(getActivity(), R.string.loading_failed, Toast.LENGTH_SHORT).show(); } @Override public void onNext(List<ImageInfoBean> images) { adapter.setImages(images); } }; } return observer; }
- 詳解
Map操作符對Observable發(fā)射的每一項(xiàng)數(shù)據(jù)應(yīng)用一個(gè)函數(shù),執(zhí)行變換操作角塑,然后返回一個(gè)發(fā)射這些結(jié)果的Observable蔫磨。
本例中,接口返回的數(shù)據(jù)格式是:
但是我們只關(guān)心list部分的數(shù)據(jù)圃伶,所以進(jìn)行轉(zhuǎn)換操作堤如,這樣訂閱者回調(diào)方法中拿到的數(shù)據(jù)直接進(jìn)行使用就好了public class BeautyResult { public boolean error; public List<ImageInfoBean> results; }
場景三:Zip操作符的使用(結(jié)合)
通過一個(gè)函數(shù)將多個(gè)Observables的發(fā)射物結(jié)合到一起,基于這個(gè)函數(shù)的結(jié)果為每個(gè)結(jié)合體發(fā)射單個(gè)數(shù)據(jù)項(xiàng)窒朋,具體實(shí)現(xiàn)步驟如下:
網(wǎng)絡(luò)請求裝備
網(wǎng)絡(luò)請求Api搀罢,以及請求類,還是使用場景一侥猩、二中的創(chuàng)建好的榔至。-
請求數(shù)據(jù),并結(jié)合欺劳,代碼如下:
/** * 請求兩個(gè)接口唧取,對返回的數(shù)據(jù)進(jìn)行結(jié)合 */ private void load() { swipeRefreshLayout.setRefreshing(true); subscription = Observable.zip(Network.getGankApi().getBeauties(188, 1).map(BeautyResult2Beautise.newInstance()), Network.getZhuangbiApi().search("裝逼"), new Func2<List<ImageInfoBean>, List<ImageInfoBean>, List<ImageInfoBean>>() { @Override public List<ImageInfoBean> call(List<ImageInfoBean> imageInfoBeen, List<ImageInfoBean> imageInfoBeen2) { int num = imageInfoBeen.size() < imageInfoBeen2.size() ? imageInfoBeen.size() : imageInfoBeen2.size(); List<ImageInfoBean> list = new ArrayList<>(); for (int i = 0; i < num; i++) { list.add(imageInfoBeen.get(i)); list.add(imageInfoBeen2.get(i)); } return list; } }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(getObserver()); }
-
詳解
請求GankApi中的數(shù)據(jù)使用map操作符進(jìn)行轉(zhuǎn)換,取出自己想要的list數(shù)據(jù)划提,然后結(jié)合ZhuangbiApi中的數(shù)據(jù)枫弟,形成新的數(shù)據(jù)集合,填充到view鹏往。Zip操作符使用函數(shù)按順序結(jié)合多個(gè)Observables發(fā)射的數(shù)據(jù)項(xiàng)淡诗,然后它發(fā)射這個(gè)函數(shù)返回的結(jié)果,它只發(fā)射與數(shù)據(jù)項(xiàng)最少的那個(gè)Observable一樣多的數(shù)據(jù)伊履。
一般app中同一個(gè)界面有時(shí)會(huì)需要同時(shí)訪問不同接口韩容,然后將結(jié)果糅合后轉(zhuǎn)為統(tǒng)一的格式后輸出(例如將第三方廣告 API 的廣告夾雜進(jìn)自家平臺(tái)返回的數(shù)據(jù) List 中)。這種并行的異步處理比較麻煩唐瀑,不過用了 zip() 之后就會(huì)簡單明了群凶。
上一個(gè)效果圖:
可以看出,recyclerView中使用了一個(gè)數(shù)據(jù)集合介褥,但左側(cè)的一列展示的是GankApi中的數(shù)據(jù)座掘,右側(cè)一列展示的是ZhuangbiApi 中的數(shù)據(jù)。
場景四:CombineLatest操作符的使用(結(jié)合)
結(jié)合多個(gè)Observable發(fā)射的最近數(shù)據(jù)項(xiàng)柔滔,當(dāng)原始Observables的任何一個(gè)發(fā)射了一條數(shù)據(jù)時(shí)溢陪,CombineLatest使用一個(gè)函數(shù)結(jié)合它們最近發(fā)射的數(shù)據(jù),然后發(fā)射這個(gè)函數(shù)的返回值睛廊,具體實(shí)現(xiàn)步驟如下:
-
使用場景形真,用一個(gè)簡單明了的圖片來表示吧
-
上圖簡單演示了CombineLatest的使用場景,看代碼吧:
/** * 將3個(gè)EditText的事件進(jìn)行結(jié)合 */ private void combineLatestEvent() { usernameObservable = RxTextView.textChanges(mUsername).skip(1); emailObservable = RxTextView.textChanges(mEmail).skip(1); passwordObservable = RxTextView.textChanges(mPassword).skip(1); subscription = Observable.combineLatest(usernameObservable, emailObservable, passwordObservable, new Func3<CharSequence, CharSequence, CharSequence, Boolean>() { @Override public Boolean call(CharSequence userName, CharSequence email, CharSequence password) { boolean isUserNameValid = !TextUtils.isEmpty(userName) && (userName.toString().length() > 2 && userName.toString().length() < 9); if (!isUserNameValid) { mUsername.setError("用戶名無效"); } boolean isEmailValid = !TextUtils.isEmpty(email) && Patterns.EMAIL_ADDRESS.matcher(email).matches(); if (!isEmailValid) { mEmail.setError("郵箱無效"); } boolean isPasswordValid = !TextUtils.isEmpty(password) && (password.toString().length() > 6 && password.toString().length() < 11); if (!isPasswordValid) { mPassword.setError("密碼無效"); } return isUserNameValid && isEmailValid && isPasswordValid; } }) .subscribe(getObserver()); }
/** * 獲取訂閱者 * @return */ private Observer<Boolean> getObserver() { return new Observer<Boolean>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Boolean aBoolean) { //更改注冊按鈕是否可用的狀態(tài) mButton.setEnabled(aBoolean); } }; }
-
詳解
CombineLatest操作符行為類似于zip,但是只有當(dāng)原始的Observable中的每一個(gè)都發(fā)射了一條數(shù)據(jù)時(shí)zip才發(fā)射數(shù)據(jù)咆霜。CombineLatest則在原始的Observable中任意一個(gè)發(fā)射了數(shù)據(jù)時(shí)發(fā)射一條數(shù)據(jù)邓馒。
當(dāng)原始Observables的任何一個(gè)發(fā)射了一條數(shù)據(jù)時(shí),CombineLatest使用一個(gè)函數(shù)結(jié)合它們最近發(fā)射的數(shù)據(jù)蛾坯,然后發(fā)射這個(gè)函數(shù)的返回值光酣。
本例中,含用戶名脉课、郵箱救军、密碼、注冊按鈕的注冊頁面的場景非常常見倘零,當(dāng)然可以使用普通的處理方式能夠達(dá)成唱遭,注冊按鈕的是否可用更改的效果,以及輸入是否合法的及時(shí)提示呈驶。
但是使用RxJava的方式拷泽,代碼明顯簡潔、易懂袖瞻。
小結(jié):
雖然司致,上面四個(gè)使用場景主要介紹四個(gè)操作符的使用,但其實(shí)demo中穿插了不少其他操作符的使用虏辫,想要詳細(xì)了解的話蚌吸,代碼在這里锈拨。
暫時(shí)先寫到這里砌庄,后面會(huì)把其他自己學(xué)會(huì)的的操作符,寫成系列文章奕枢。如有興趣娄昆,請關(guān)注我的github。