RxJava開發精要3-向響應式世界問好
向響應式世界問好
在上一章中,我們對觀察者模式有個理論上的快速概述。我們也看了從頭開始、從列表、或者從已經存在的函數來建立Observables。在本章中,我們將用我們學到的來建立我們第一個響應式Android應用程式。首先,我們需要搭建環境,匯入需要的庫和有用的庫。然後我們將建立一個簡單的應用程式,在不同的flavors中包含幾個用RxJava填充的RecycleView items。
啟動引擎
我們將使用IntelliJ IDEA/Android Studio來建立這個工程,因此你會對看起來比較熟悉。
讓我們開始建立一個新的Android工程。你可以建立你自己的工程或者用本書中提供的匯入。選擇你自己喜歡的建立方式這取決於你。
依賴
很明顯,我們將使用Gradle來管理我們的依賴列表。我們的build.gradble檔案看起來像這樣:
正如你看到的我們引入了RxAndroid。RxAndroid是RxJava的增強版,尤其是針對Android設計的。
RxAndroid
RxAndroid是RxJava家族的一部分。它基於RxJava1.0.x,在普通的RxJava基礎上添加了幾個有用的類。大多數情況下,它為Android添加了特殊的調度器。我們將在第七章Schedulers-Defeating the Android MainThread Issue再討論它。
工具
出於實用,我們引入了Lombok 和 Butter Knife。這兩個可以協助我們在Android應用程式中少寫許多模板類代碼。
Lombok
Lombok使用註解的方式為你產生許多代碼。我們將使用它老產生getter/setter、toString()、equals()、hashCode()。它藉助於Gradle依賴和一個Android Studio外掛程式。
Butter Knife
Butter Knife使用註解的方式來協助我們免去寫findViewById()和設定點擊監聽的痛苦。至於Lombok,我們可以通過匯入依賴和安裝Android Studio外掛程式來獲得更好的體驗。
Retrolambda
最後,我們匯入Retrolambda,是因為我們開發的Android是基於Java 1.6,然後我們可以藉助它來實現Java 8 Lambda函數從而減少許多模板代碼。
我們的第一個Observable
在我們的第一個列子裡,我們將檢索安裝的應用列表並填充RecycleView的item來展示它們。我們也設想一個下拉重新整理的功能和一個進度條來告知使用者當前任務正在執行。
首先,我們建立Observable。我們需要一個函數來檢索安裝的應用程式列表並把它提供給我們的觀察者。我們一個接一個的發射這些應用程式資料,將它們分組到一個單獨的列表中,以此來展示響應式方法的靈活性。
private Observable getApps(){ return Observable.create(subscriber -> { List apps = new ArrayList(); final Intent mainIntent = new Intent(Intent.ACTION_MAIN,null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); List infos = getActivity().getPackageManager().queryIntentActivities(mainIntent, 0); for(ResolveInfo info : infos){ apps.add(new AppInfoRich(getActivity(),info)); } for (AppInfoRich appInfo:apps) { Bitmap icon = Utils.drawableToBitmap(appInfo.getIcon()); String name = appInfo.getName(); String iconPath = mFilesDir + "/" + name; Utils.storeBitmap(App.instance, icon,name); if (subscriber.isUnsubscribed()){ return; } subscriber.onNext(new AppInfo(name,iconPath,appInfo.getLastUpdateTime())); } if (!subscriber.isUnsubscribed()){ subscriber.onCompleted(); } });}
AppInfo對象如下:
@Data@Accessors(prefix = "m")public class AppInfo implements Comparable { long mLastUpdateTime; String mName; String mIcon; public AppInfo(String nName, long lastUpdateTime, String icon) { mName = nName; mIcon = icon; mLastUpdateTime = lastUpdateTime; } @Override public int compareTo(Object another) { AppInfo f = (AppInfo)another; return getName().compareTo(f.getName()); }}
需要重點注意的是在發射新的資料或者完成序列之前要檢測觀察者的訂閱情況。這樣的話代碼會更高效,因為如果沒有觀察者等待時我們就不產生沒有必要的資料項目。
此時,我們可以訂閱Observable並觀察它。訂閱一個Observable意味著當我們需要的資料進來時我們必須提供對應的操作來執行它。
當前的情境是什嗎?我們展示一個進度條來等待資料。當資料到來時,我們需要隱藏掉進度條,填充list,最終展示列表。現在,我們知道當一切都準備好了該做什麼。那麼錯誤的情境呢?對於錯誤這種情況,我們僅僅是用Toast展示一個錯誤的資訊。
使用Butter Knife,我們得到list和下拉重新整理組件的引用:
@InjetcView(R.id.fragment_first_example_list)RecyclerView mRecycleView;@InjectView(R.id.fragment_first_example_swipe_container)SwipeRefreshLayout mSwipeRefreshLayout;
我們使用Android 5的標準組件:RecyclerView和SwipeRefreshLayout。截屏展示了我們這個簡單App的list Fragment的layout檔案:
我們使用一個下拉重新整理方法,因此列表資料可以來自初始化載入,或由使用者觸發的一個重新整理動作。針對這兩個情境,我們用同樣的行為,因此我們把我們的觀察者放在一個易被複用的函數裡面。下面是我們的觀察者,定義了成功、失敗、完成要做的事情:<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">private void refreshTheList() { getApps().toSortedList() .subscribe(new Observable>() { @Override public void onCompleted() { Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); mSwipeRefreshLayout.setRefreshing(false); } @Override public void onNext(List appInfos) { mRecyclerView.setVisibility(View.VISIBLE); mAdapter.addApplications(appInfos); mSwipeRefreshLayout.setRefreshing(false); } });}
定義一個函數使我們能夠用同樣一個block來處理兩種情境成為了可能。當fragment載入時我們只需調用refreshTheList()方法並設定refreshTheList()方法作為使用者下拉這一行為所觸發的方法。
mSwipeRefreshLayout.setOnRefreshListener(this::refreshTheList);
我們第一個例子現在完成了,運行跑一下。
從列表建立一個Observable在這個例子中,我們將引入from()函數。使用這個特殊的“建立”函數,我們可以從一個列表中建立一個Observable。Observable將發射出列表中的每一個元素,我們可以通過訂閱它們來對這些發出的元素做出響應。
為了實現和第一個例子同樣的結果,我們在每一個onNext()函數更新我們的適配器,添加元素並通知插入。
我們將複用和第一個例子同樣的結構。主要的不同的是我們不再檢索已安裝的應用列表。列表由外部實體提供:
mApps = ApplicationsList.getInstance().getList();
獲得列表後,我們僅需將它響應化並填充RecyclerView的item:
private void loadList(List apps) { mRecyclerView.setVisibility(View.VISIBLE); Observable.from(apps) .subscribe(new Observable() { @Override public void onCompleted() { mSwipeRefreshLayout.setRefreshing(false); Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); mSwipeRefreshLayout.setRefreshing(false); } @Override public void onNext(AppInfo appInfo) { mAddedApps.add(appInfo); mAdapter.addApplication(mAddedApps.size() - 1,appInfo); } });}
正如你看到的,我們將已安裝的應用程式列表作為參數傳進from()函數,然後我們訂閱產生的Observable。觀察者和我們第一個例子中的觀察者十分相像。一個主要的不同是我們在onCompleted()函數中停掉進度條是因為我們一個一個的發射元素;第一個例子中的Observable發射的是整個list,因此在onNext()函數中停掉進度條的做法是安全的。
再多幾個例子在這一節中,我們將基於RxJava的just(),repeat(),defer(),range(),interval(),和timer()方法展示一些例子。
just()假如我們只有3個獨立的AppInfo對象並且我們想把他們轉化為Observable並填充到RecyclerView的item中:
List apps = ApplicationsList.getInstance().getList();AppInfo appOne = apps.get(0);AppInfo appTwo = apps.get(10);AppInfo appThree = apps.get(24);loadApps(appOne,appTwo,appThree);
我們可以像我們之前的例子那樣檢索列表並提取出這三個元素。然後我們將他們傳到這個loadApps()函數裡面:
private void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) { mRecyclerView.setVisibility(View.VISIBLE); Observable.just(appOne,appTwo,appThree) .subscribe(new Observable() { @Override public void onCompleted() { mSwipeRefreshLayout.setRefreshing(false); Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); mSwipeRefreshLayout.setRefreshing(false); } @Override public void onNext(AppInfo appInfo) { mAddedApps.add(appInfo); mAdapter.addApplication(mAddedApps.size() - 1,appInfo); } });}
正如你看到的,代碼和之前的例子很像。這種方法讓我們有機會來考慮一下代碼的複用。
你可以將一個函數作為參數傳給just()方法,你將會得到一個已存在代碼的原始Observable版本。在一個新的響應式架構的基礎上遷移已存在的代碼,這個方法可能是一個有用的開始點。
repeat()假如你想對一個Observable重複發射三次資料。例如,我們用just()例子中的Observable:
private void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) { mRecyclerView.setVisibility(View.VISIBLE); Observable.just(appOne,appTwo,appThree) .repeat(3) .subscribe(new Observable() { @Override public void onCompleted() { mSwipeRefreshLayout.setRefreshing(false); Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); mSwipeRefreshLayout.setRefreshing(false); } @Override public void onNext(AppInfo appInfo) { mAddedApps.add(appInfo); mAdapter.addApplication(mAddedApps.size() - 1,appInfo); } });}
正如你看到的,我們在just()建立Observable後追加了repeat(3),它將會建立9個元素的序列,每一個都單獨發射。
defer()有這樣一個情境,你想在這聲明一個Observable但是你又想延遲這個Observable的建立直到觀察者訂閱時。看下面的getInt()函數:
private Observable getInt(){ return Observable.create(subscriber -> { if(subscriber.isUnsubscribed()){ return; } App.L.debug("GETINT"); subscriber.onNext(42); subscriber.onCompleted(); });}
這比較簡單,並且它沒有做太多事情,但是它正好為我們服務。現在,我們可以建立一個新的Observable並且應用defer():
Observable deferred = Observable.defer(this::getInt);
這次,deferred存在,但是getInt() create()方法還沒有調用:logcat日誌也沒有“GETINT”列印出來:
deferred.subscribe(number -> { App.L.debug(String.valueOf(number));});
但是一旦我們訂閱了,create()方法就會被調用並且我們也可以在logcat日誌中得到下賣弄兩個:GETINT和42。
range()你需要從一個指定的數字X開始發射N個數字嗎?你可以用range:
Observable.range(10,3) .subscribe(new Observable() { @Override public void onCompleted() { Toast.makeText(getActivity(), "Yeaaah!", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); } @Override public void onNext(Integer number) { Toast.makeText(getActivity(), "I say " + number, Toast.LENGTH_SHORT).show(); } });
range()函數用兩個數字作為參數:第一個是起始點,第二個是我們想發射數位個數。
interval()interval()函數在你需要建立一個輪詢程式時非常好用。
Subscription stopMePlease = Observable.interval(3,TimeUnit.SECONDS) .subscribe(new Observable() { @Override public void onCompleted() { Toast.makeText(getActivity(), "Yeaaah!", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show(); } @Override public void onNext(Integer number) { Toast.makeText(getActivity(), "I say " + number, Toast.LENGTH_SHORT).show(); } });
interval()函數的兩個參數:一個指定兩次發射的時間間隔,另一個是用到的時間單位。
timer()如果你需要一個一段時間之後才發射的Observable,你可以像下面的例子使用timer():
Observable.timer(3,TimeUnit.SECONDS) .subscribe(new Observable() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Long number) { Log.d("RXJAVA", "I say " + number); } });
它將3秒後發射0,然後就完成了。讓我們使用timer()的第三個參數,就像下面的例子:
Observable.timer(3,3,TimeUnit.SECONDS) .subscribe(new Observable() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Long number) { Log.d("RXJAVA", "I say " + number); } });
用這個代碼,你可以建立一個以初始值來延遲(上一個例子是3秒)執行的interval()版本,然後每隔N秒就發射一個新的數字(前面的例子是3秒)。
總結在本章中,我們建立了第一個由RxJava強化的Android應用程式。我們從頭、從已有的列表、從已有的函數來建立Observable。我們也學習了如何建立重複發射的Observables,間隔發射的Observables以及延遲發射的Observables。
在下一章中,我們將掌握過濾操作,能夠從我們接收到的序列中建立我們需要的序列。