NotRxJava懶人專用指南
- 原文鏈接 : NotRxJava guide for lazy folks
- 原文作者 : Yaroslav Heriatovych
- 譯文出自 : 開發技術前線 www.devtf.cn
- 譯者 : Rocko
- 校對者: Mr.Simple
- 狀態 : 完成校對
如果你是一位 Android 開發者,那么這些天你可能已經聽到或看到一些關于 RxJava 滿天飛的宣傳了。RxJava 是一個能讓你擺脫編寫一些復雜繁瑣的代碼去處理異步事件的庫。一旦開始在你的項目中使用,你會對它愛不釋手的。
然而,RxJava 有個缺陷,它需要一個陡峭的學習過程。對于一個從未接觸使用過 RxJava 的人來說,是很難一次就領會到它的精髓所在的,對于它的一些使用方法你也可能會很迷惑。在項目中使用它意味著你需要稍微地改變一下你的代碼編寫思路,另 外,這樣的學習曲線會使得在項目中因為大規模的使用RxJava而引發一些問題。
當然,關于如何去使用 RxJava 已經有許多的教程和代碼范例了。感興趣的開發者可以訪問 RxJava 的官方 Wiki,里面有關于什么是 Observable 以及它和 Iterable、Future 之間關系的很好的解釋。Wiki 里有一篇很有用的文章:How To Use RxJava,這篇文章包含怎么去發送事件流并且打印出它們的介紹以及它的樣例代碼。
但我們要明確的是在還沒有學習什么是 Observable 的前提下了解 RxJava 用來解決什么問題以及它是怎么幫助我們組織起異步代碼的。
我這篇文章的定位就是 RxJava 官方文檔的“前篇”,讀完這篇文章能更好地去理解 RxJava 所解決的問題。文章中也有一個小 Demo,就是自己怎么去整理那些凌亂的代碼,然后看看我們在沒有使用 RxJava 的情況下是怎么去遵循 RxJava 基本原則的。
所以,如果你仍有足夠的好奇的話就讓我們開始吧!
Cat 應用程序
讓我們來創建一個真實世界的例子。我們都知道貓是我們技術發展的引擎,所以就讓我們也來創建這么一個用來下載貓圖片的典型應用吧。
任務描述
我們有個 Web API,能根據給定的查詢請求搜索到整個互聯網上貓的圖片。每個圖片包含可愛指數的參數(描述圖片可愛度的整型值)。我們的任務將會下載到一個貓列表的集合,選擇最可愛的那個,然后把它保存到本地。
我們只關心下載、處理和保存貓的數據。
我們開始吧~
模型和 API
下面是描述“貓”的簡單數據結構:
public class Cat implements Comparable<Cat>{ Bitmap image; int cuteness; @Override public int compareTo(Cat another) { return Integer.compare(cuteness, another.cuteness); } }
還有我們傳統阻塞式風格的 API,它被打包進 cat-sdk.jar 中了:
public interface Api { List<Cat> queryCats(String query); Uri store(Cat cat); }
這足夠清楚了嗎?當然!那就讓我們開始編寫業務邏輯吧:
public class CatsHelper { Api api; public Uri saveTheCutestCat(String query){ List<Cat> cats = api.queryCats(query); Cat cutest = findCutest(cats); Uri savedUri = api.store(cutest); return savedUri; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
唉,這樣清晰簡單的代碼帥到讓我窒息啊。來理清一下代碼的炫酷之處吧。主方法 saveTheCutestCat 只包含了 3 個其它方法,然后花個幾分鐘來看看代碼和思考這些方法。你給方法提供了輸入參數然后就能得到結果返回了,在這個方法工作的時候我們需要等待它的完成。
簡潔而有用,讓我們再看看組合方法的其它優勢:
組合
正如我們看到的,根據其它 3 個方法而新創建了一個方法(saveTheCutestCat),因此我們組合了它們。像樂高積木那樣,我們把方法之間連接起來組成了樂高積木(實際上可以在之后組合起來)。組合方法是很簡單的,從一個方法得到返回結果然后再把它傳遞給另外的方法做為輸入參數,這不簡單嗎?
錯誤的傳遞
另外一個好處就是我們處理錯誤的方式了。任何一個方法都可能因執行時發生錯誤而被終止,這個錯誤能在任何層次上被處理掉,Java 中我們叫它拋出了異常,然后這個錯誤在 try/catch 代碼塊中做處理。這里的關鍵點是我們不需要為組合方法里的每個方法都做異常處理,僅需要對這些組合起來的方法做統一處理,像下面這樣:
try{ List<Cat> cats = api.queryCats(query); Cat cutest = findCutest(cats); Uri savedUri = api.store(cutest); return savedUri; } catch (Exception e) { e.printStackTrace() return someDefaultValue; }
這個情況下,我們處理了所有執行時的錯誤,或者說如果我們沒有使用 try/catch 代碼塊我們能夠把錯誤傳遞到下一個層次上。
走向異步
要知道我們身在一個對等待很敏感的世界里,我們也知道不可能只有阻塞式的調用。在 Android 中我們也總需要處理異步代碼。
拿 Android 的OnClickListener舉個例子,當你需要處理一個控件的點擊事件時,你必須提供一個監聽器(回調)以供在用戶點擊控件時被調用。這沒有理由使用阻塞的方式去接受點擊事件的回調,所以對點擊來說總是異步的。現在,讓我們也使用異步編程吧。
異步的網絡調用
開始想象下使用沒有阻塞的 HTTP client(例如Ion),還有就是我們的cats-sdk.jar已經更新。它的 API 也換成了異步的方式調用。
新 API 的接口:
public interface Api { interface CatsQueryCallback { void onCatListReceived(List<Cat> cats); void onError(Exception e); } void queryCats(String query, CatsQueryCallback catsQueryCallback); Uri store(Cat cat); }
所以現在我們能異步的獲取貓的信息集合列表了,返回正確或錯誤的結果時都會通過CatsQueryCallback回調接口。
因為 API 改變了所以我們也不得不改變我們的CatsHelper:
public class CatsHelper { public interface CutestCatCallback { void onCutestCatSaved(Uri uri); void onQueryFailed(Exception e); } Api api; public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){ api.queryCats(query, new Api.CatsQueryCallback() { @Override public void onCatListReceived(List<Cat> cats) { Cat cutest = findCutest(cats); Uri savedUri = api.store(cutest); cutestCatCallback.onCutestCatSaved(savedUri); } @Override public void onQueryFailed(Exception e) { cutestCatCallback.onError(e); } }); } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
現在我們已經不能使用阻塞的 API 了,我們也不能把我們的客戶端上寫成阻塞式的調用(實際上是可以的,但需要明確的在線程中使用synchronized、CountdownLatch、等等,也需要在下層中使用異步處理)。所以我們不能在saveTheCutestCat方法中直接返回一個值,我們需要對它進行異步回調處理。
讓我們再深入一點,如果我們 API 的兩個方法調用都是異步的,舉個例子,我們正在使用非阻塞IO去寫進一個文件。
public interface Api { interface CatsQueryCallback { void onCatListReceived(List<Cat> cats); void onQueryFailed(Exception e); } interface StoreCallback{ void onCatStored(Uri uri); void onStoreFailed(Exception e); } void queryCats(String query, CatsQueryCallback catsQueryCallback); void store(Cat cat, StoreCallback storeCallback); }
還有我們的 helper:
public class CatsHelper { public interface CutestCatCallback { void onCutestCatSaved(Uri uri); void onError(Exception e); } Api api; public void saveTheCutestCat(String query, CutestCatCallback cutestCatCallback){ api.queryCats(query, new Api.CatsQueryCallback() { @Override public void onCatListReceived(List<Cat> cats) { Cat cutest = findCutest(cats); api.store(cutest, new Api.StoreCallback() { @Override public void onCatStored(Uri uri) { cutestCatCallback.onCutestCatSaved(uri); } @Override public void onStoreFailed(Exception e) { cutestCatCallback.onError(e); } }); } @Override public void onQueryFailed(Exception e) { cutestCatCallback.onError(e); } }); } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
現在再來看看代碼,跟之前一樣優雅嗎?明顯不是了,這很糟糕!現在它有了更多無關代碼和花括號,但是邏輯是一樣的。
那么組合在哪呢?他已經不見了!現在你不能像之前那樣組合操作了。對于每一個異步操作你都必須創建出回調接口并在代碼中插入它們,每一次都需要手動地加入!
錯誤傳遞又在哪?又是一個否定!在這樣的代碼中錯誤不會自動地傳遞,我們需要在更深一層上通過自己手動地再讓它傳遞下去(請看onStoreFailed和onQueryFailed方法)。
我們很難對這樣的代碼進行閱讀和找出潛在的 bugs。
結束了?
結束了又怎樣?我們能拿它來干嘛?我們被困在這個沒有組合回調的地獄里了嗎?前方高能,請抓緊你的安全帶哦,我們將努力的去把這些干掉!
更美好的世界!
泛型回調
可以從我們的回調接口中找到共同的模式:
- 都有一個分發結果的方法(onCutestCatSaved,onCatListReceived,onCatStored)
-
它們中大多數(在我們的例子中是全部)有一個用于錯誤處理的方法(onError,onQueryFailed,onStoreFailed)
所以我們可以創建一個泛型回調接口去替代原來所有的接口。但是我們不能去改變 API 的調用方法的簽名,我們必須創建包裝類來間接調用。
所以我們的泛型回調接口看起來是這樣的:
public interface Callback<T> { void onResult(T result); void onError(Exception e); }
然后我們來創建ApiWrapper來改變一下調用方法的簽名:
public class ApiWrapper { Api api; public void queryCats(String query, Callback<List<Cat>> catsCallback){ api.queryCats(query, new Api.CatsQueryCallback() { @Override public void onCatListReceived(List<Cat> cats) { catsCallback.onResult(cats); } @Override public void onQueryFailed(Exception e) { catsCallback.onError(e); } }); } public void store(Cat cat, Callback<Uri> uriCallback){ api.store(cat, new Api.StoreCallback() { @Override public void onCatStored(Uri uri) { uriCallback.onResult(uri); } @Override public void onStoreFailed(Exception e) { uriCallback.onError(e); } }); } }
所以這僅僅是對于Callback的一些傳遞 resuts/errors 的調用轉發邏輯。
最后我們的CatsHelper:
public class CatsHelper{ ApiWrapper apiWrapper; public void saveTheCutestCat(String query, Callback<Uri> cutestCatCallback){ apiWrapper.queryCats(query, new Callback<List<Cat>>() { @Override public void onResult(List<Cat> cats) { Cat cutest = findCutest(cats); apiWrapper.store(cutest, cutestCatCallback); } @Override public void onError(Exception e) { cutestCatCallback.onError(e); } }); } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
可以看到比之前的簡明了一些。我們可以通過直接傳遞一個頂級的cutestCatCallback回調接口給apiWrapper.store來減少回調間的層級調用,此外作為回調方法的簽名是一樣的。
但是我們可以做的更好!
你必須把它分開
讓我們來看看我們的異步操作(queryCats,queryCats,還有saveTheCutestCat),它們都遵循了相同的模式。調用它們的方法有一些參數(query、cat)也包括一個回調對象。再次說明:任何異步操作需要攜帶所需的常規參數和一個回調實例對象。如果我們試圖去分開這幾個階段,每個異步操作僅僅將會攜帶一個參數對象,然后返回一些攜帶著回調(信息)的臨時對象。
我們來應用下這樣的模式,看看是否對我們有所幫助。
如果在異步操作中返回一些臨時對象,我們需要定義一個出來。這樣的一個對象需要包括常見的行為(以回調為單一參數),我們將定義這樣的類給所有的異步操作使用,這個類就叫它AsyncJob。
P.S. 稱之為AsyncTask更合適一點,但是我不希望你混淆了異步操作跟另外一個存在的抽象概念之間的關系(這是不好的一點)。
所以:
public abstract class AsyncJob<T> { public abstract void start(Callback<T> callback); }
非常的簡單,Callback傳進方法后就會開始它的工作任務。
然后更改包裝 API 的調用:
public class ApiWrapper { Api api; public AsyncJob<List<Cat>> queryCats(String query) { return new AsyncJob<List<Cat>>() { @Override public void start(Callback<List<Cat>> catsCallback) { api.queryCats(query, new Api.CatsQueryCallback() { @Override public void onCatListReceived(List<Cat> cats) { catsCallback.onResult(cats); } @Override public void onQueryFailed(Exception e) { catsCallback.onError(e); } }); } }; } public AsyncJob<Uri> store(Cat cat) { return new AsyncJob<Uri>() { @Override public void start(Callback<Uri> uriCallback) { api.store(cat, new Api.StoreCallback() { @Override public void onCatStored(Uri uri) { uriCallback.onResult(uri); } @Override public void onStoreFailed(Exception e) { uriCallback.onError(e); } }); } }; } }
目前一切順利,我們只是部分運用我們的包裝過的 API 調用在程序之中。現在我們可以開始使用AsyncJob去做我們想要的了,當然也到更改CatsHelper的時間了。
public class CatsHelper { ApiWrapper apiWrapper; public AsyncJob<Uri> saveTheCutestCat(String query) { return new AsyncJob<Uri>() { @Override public void start(Callback<Uri> cutestCatCallback) { apiWrapper.queryCats(query) .start(new Callback<List<Cat>>() { @Override public void onResult(List<Cat> cats) { Cat cutest = findCutest(cats); apiWrapper.store(cutest) .start(new Callback<Uri>() { @Override public void onResult(Uri result) { cutestCatCallback.onResult(result); } @Override public void onError(Exception e) { cutestCatCallback.onError(e); } }); } @Override public void onError(Exception e) { cutestCatCallback.onError(e); } }); } }; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
哇,之前的版本更簡單些啊,我們現在的優勢是什么?答案就是現在我們可以給客戶端返回“組合”操作的AsyncJob<Uri>。所以一個客戶端(在 activity 或者 fragment 處)可以用組合起來的工作來操作。
Breaking things
這是我們的邏輯數據流:
(async) (sync) (async) query ===========> List<Cat> -------------> Cat ==========> Uri queryCats findCutest store
為了讓我們的代碼擁有之前的可讀性,我們從這個事件流中強行進入到里面的操作里。但有件事需要注意,如果某些操作(方法)是異步的,然后調用它的操 作(方法)又是異步的,就比如,查詢貓的操作是異步的,然后尋找出最可愛的貓(即使有一個阻塞調用)也是一個異步操作(客戶端希望接收的結果)。
所以我們可以使用 AsyncJobs 把我們的方法分解成更小的操作:
public class CatsHelper { ApiWrapper apiWrapper; public AsyncJob<Uri> saveTheCutestCat(String query) { AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query); AsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() { @Override public void start(Callback<Cat> callback) { catsListAsyncJob.start(new Callback<List<Cat>>() { @Override public void onResult(List<Cat> result) { callback.onResult(findCutest(result)); } @Override public void onError(Exception e) { callback.onError(e); } }); } }; AsyncJob<Uri> storedUriAsyncJob = new AsyncJob<Uri>() { @Override public void start(Callback<Uri> cutestCatCallback) { cutestCatAsyncJob.start(new Callback<Cat>() { @Override public void onResult(Cat cutest) { apiWrapper.store(cutest) .start(new Callback<Uri>() { @Override public void onResult(Uri result) { cutestCatCallback.onResult(result); } @Override public void onError(Exception e) { cutestCatCallback.onError(e); } }); } @Override public void onError(Exception e) { cutestCatCallback.onError(e); } }); } }; return storedUriAsyncJob; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
代碼量多了許多,但是更加清晰了。低層次嵌套的回調,利于理解的變量名(catsListAsyncJob、cutestCatAsyncJob、storedUriAsyncJob)。
看起來好了許多,但我們要再做一些事情:
簡單映射
現在看看AsyncJob<Cat> cutestCatAsyncJob的部分:
AsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() { @Override public void start(Callback<Cat> callback) { catsListAsyncJob.start(new Callback<List<Cat>>() { @Override public void onResult(List<Cat> result) { callback.onResult(findCutest(result)); } @Override public void onError(Exception e) { callback.onError(e); } }); } };
這 16 行代碼只有一行是對我們有用(對于邏輯來說)的操作:
findCutest(result)
剩下的僅僅是開啟另外一個AsyncJob和傳遞結果與錯誤的樣板代碼。此外,這些代碼并不用于特定的任務,我們可以把其移動到其它地方而不影響編寫我們真正需要的業務代碼。
我們該怎么寫呢?我們必須做下面的兩件事情:
- AsyncJob是我們轉換的結果
-
轉換方法
這又有另外一個問題,因為在 Java 中不能直接傳遞方法(函數)所以我們需要通過類(和接口)來間接實現這樣的功能,然后我們就來定義這個 “方法”:
public interface Func<T, R> { R call(T t); }
相當簡單,Func接口有兩個類型成員,T對應于參數類型而R對應于返回類型。
當我們從一個AsyncJob中裝換處結果后我們就需要做一些值之間的映射,這樣的方法我們就叫它map。定義這個方法實例(Func 類型)最好的地方就在AsyncJob類中,所以AsyncJob代碼里看起來就是這樣了:
public abstract class AsyncJob<T> { public abstract void start(Callback<T> callback); public <R> AsyncJob<R> map(Func<T, R> func){ final AsyncJob<T> source = this; return new AsyncJob<R>() { @Override public void start(Callback<R> callback) { source.start(new Callback<T>() { @Override public void onResult(T result) { R mapped = func.call(result); callback.onResult(mapped); } @Override public void onError(Exception e) { callback.onError(e); } }); } }; } }
贊,這時CatsHelper就是下面這樣了:
public class CatsHelper { ApiWrapper apiWrapper; public AsyncJob<Uri> saveTheCutestCat(String query) { AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query); AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() { @Override public Cat call(List<Cat> cats) { return findCutest(cats); } }); AsyncJob<Uri> storedUriAsyncJob = new AsyncJob<Uri>() { @Override public void start(Callback<Uri> cutestCatCallback) { cutestCatAsyncJob.start(new Callback<Cat>() { @Override public void onResult(Cat cutest) { apiWrapper.store(cutest) .start(new Callback<Uri>() { @Override public void onResult(Uri result) { cutestCatCallback.onResult(result); } @Override public void onError(Exception e) { cutestCatCallback.onError(e); } }); } @Override public void onError(Exception e) { cutestCatCallback.onError(e); } }); } }; return storedUriAsyncJob; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
現在好多了,創建AsyncJob<Cat> cutestCatAsyncJob只需要 6 行代碼而回調也只有一個層級了。
高級映射
前面的那些已經很贊了,但是創建AsyncJob<Uri> storedUriAsyncJob的部分還有些不忍直視。能在這里創建映射嗎?我們來試試吧:
public class CatsHelper { ApiWrapper apiWrapper; public AsyncJob<Uri> saveTheCutestCat(String query) { AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query); AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() { @Override public Cat call(List<Cat> cats) { return findCutest(cats); } }); AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.map(new Func<Cat, Uri>() { @Override public Uri call(Cat cat) { return apiWrapper.store(cat); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ will not compile // Incompatible types: // Required: Uri // Found: AsyncJob<Uri> } }); return storedUriAsyncJob; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
呃呃。。。并不容易哦,我們來修改下結果的類型變量,試下其它方法:
public class CatsHelper { ApiWrapper apiWrapper; public AsyncJob<Uri> saveTheCutestCat(String query) { AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query); AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() { @Override public Cat call(List<Cat> cats) { return findCutest(cats); } }); AsyncJob<AsyncJob<Uri>> storedUriAsyncJob = cutestCatAsyncJob.map(new Func<Cat, AsyncJob<Uri>>() { @Override public AsyncJob<Uri> call(Cat cat) { return apiWrapper.store(cat); } }); return storedUriAsyncJob; //^^^^^^^^^^^^^^^^^^^^^^^ will not compile // Incompatible types: // Required: AsyncJob<Uri> // Found: AsyncJob<AsyncJob<Uri>> } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
在目前這點上我們只能有AsyncJob<AsyncJob<Uri>>。我們需要往更深處挖嗎?我們希望的是,去把AsyncJob在一個級別上的兩個異步操作扁平化成一個單一的異步操作。
現在我們需要的是得到能使方法返回映射成R類型也是AsyncJob<R>類型的操作。這個操作應該像map,但在最后應該flatten我們嵌套的AsyncJob。我們叫它為flatMap吧,然后就是來實現它:
public abstract class AsyncJob<T> { public abstract void start(Callback<T> callback); public <R> AsyncJob<R> map(Func<T, R> func){ final AsyncJob<T> source = this; return new AsyncJob<R>() { @Override public void start(Callback<R> callback) { source.start(new Callback<T>() { @Override public void onResult(T result) { R mapped = func.call(result); callback.onResult(mapped); } @Override public void onError(Exception e) { callback.onError(e); } }); } }; } public <R> AsyncJob<R> flatMap(Func<T, AsyncJob<R>> func){ final AsyncJob<T> source = this; return new AsyncJob<R>() { @Override public void start(Callback<R> callback) { source.start(new Callback<T>() { @Override public void onResult(T result) { AsyncJob<R> mapped = func.call(result); mapped.start(new Callback<R>() { @Override public void onResult(R result) { callback.onResult(result); } @Override public void onError(Exception e) { callback.onError(e); } }); } @Override public void onError(Exception e) { callback.onError(e); } }); } }; } }
FlatMap 的粗略實現,但這些東西的實現都在一個地方了,在客戶端的業務代碼中不會再見到它。接下來我們修復下CatsHelper:
public class CatsHelper { ApiWrapper apiWrapper; public AsyncJob<Uri> saveTheCutestCat(String query) { AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query); AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() { @Override public Cat call(List<Cat> cats) { return findCutest(cats); } }); AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.flatMap(new Func<Cat, AsyncJob<Uri>>() { @Override public AsyncJob<Uri> call(Cat cat) { return apiWrapper.store(cat); } }); return storedUriAsyncJob; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
哈哈!它能用了,讀和寫也簡單了不少。
最后的要點
再來看看我們編寫的代碼,眼熟嗎?如果我們使用 Java 8 的 lambdas(邏輯是一樣的但是看起來更爽一些) 代碼會更加地簡潔。
public class CatsHelper { ApiWrapper apiWrapper; public AsyncJob<Uri> saveTheCutestCat(String query) { AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query); AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(cats -> findCutest(cats)); AsyncJob<Uri> storedUriAsyncJob = cutestCatAsyncJob.flatMap(cat -> apiWrapper.store(cat)); return storedUriAsyncJob; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
它看起來會更好嗎?我認為這樣的代碼跟我們第一次阻塞的版本差不多:
public class CatsHelper { Api api; public Uri saveTheCutestCat(String query){ List<Cat> cats = api.queryCats(query); Cat cutest = findCutest(cats); Uri savedUri = api.store(cutest); return savedUri; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
是的,就是這樣,邏輯是相似的!也有可能會復雜些(語義是一樣的)。
我們這樣的代碼有組合性嗎?請大聲的說有!我們組合了所有的異步操作然后作為返回結果我們僅需一個組合后的結果對象而已。
錯誤傳遞呢?當然也有!所有的錯誤都會傳遞到最后的回調中。
接下來的最后呢。。。
RxJava
嘿,你不需要把那些代碼拷到你的項目中,因為我們還是實現地不夠完全的,僅僅算是非線程安全的 RxJava 的一小部分而已。
它們之間只有一些差異:
- AsyncJob<T>就是實際上的 Observable,它不僅可以只分發一個單一的結果也可以是一個序列(可以為空)。
-
Callback<T>就是 Observer,除了 Callback 少了onNext(T t)方法。Observer 中在onError(Throwable t)方法被調用后,會繼而調用onCompleted(),然后 Observer 會包裝好并發送出事件流(因為它能發送一個序列)。
-
abstract void start(Callback<T> callback)對應 Subscription subscribe(final Observer<? super T> observer),這個方法也返回 Subscription ,在不需要它時你可以決定取消接收事件流。
-
除了map和flatMap方法,Observable在 Observalbes 之上也有一些其它有用的操作。
<
p>下面的代碼是使用 RxJava 來完成我們前面自己寫的代碼的功能:
public class ApiWrapper { Api api; public Observable<List<Cat>> queryCats(final String query) { return Observable.create(new Observable.OnSubscribe<List<Cat>>() { @Override public void call(final Subscriber<? super List<Cat>> subscriber) { api.queryCats(query, new Api.CatsQueryCallback() { @Override public void onCatListReceived(List<Cat> cats) { subscriber.onNext(cats); } @Override public void onQueryFailed(Exception e) { subscriber.onError(e); } }); } }); } public Observable<Uri> store(final Cat cat) { return Observable.create(new Observable.OnSubscribe<Uri>() { @Override public void call(final Subscriber<? super Uri> subscriber) { api.store(cat, new Api.StoreCallback() { @Override public void onCatStored(Uri uri) { subscriber.onNext(uri); } @Override public void onStoreFailed(Exception e) { subscriber.onError(e); } }); } }); } } public class CatsHelper { ApiWrapper apiWrapper; public Observable<Uri> saveTheCutestCat(String query) { Observable<List<Cat>> catsListObservable = apiWrapper.queryCats(query); Observable<Cat> cutestCatObservable = catsListObservable.map(new Func1<List<Cat>, Cat>() { @Override public Cat call(List<Cat> cats) { return CatsHelper.this.findCutest(cats); } }); Observable<Uri> storedUriObservable = cutestCatObservable.flatMap(new Func1<Cat, Observable<? extends Uri>>() { @Override public Observable<? extends Uri> call(Cat cat) { return apiWrapper.store(cat); } }); return storedUriObservable; } private Cat findCutest(List<Cat> cats) { return Collections.max(cats); } }
你可以看到代碼是相同的,除了使用Observable來替代AsyncJob。
總結
我們看到,通過簡單的轉化我們可以把異步操作給抽象出來。這個抽象出來的東西可以被用來操作和組合異步操作就像簡單的方法那樣。通過這種方法我們可以擺脫嵌套的回調,在處理異步結果時也能手動處理錯誤的傳遞。
如果你看到了這里的話建議你放松下,思考下 sync/async 之間的二元關系,然后看看這個很棒的來自Erik Meijer的視頻。
一些有用的鏈接
- http://reactivex.io
- https://github.com/ReactiveX/RxJava
- https://github.com/ReactiveX/RxJava/wiki
- http://queue.acm.org/detail.cfm?id=2169076
- https://www.coursera.org/course/reactive
- http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1
感謝
感謝我的朋友 Alexander Yakushev 幫忙翻譯。