我們對這種新的可能性感到興奮無比,但卻很難真的投入大量時間重新編寫原有應用以充分發揮新型編程風格帶來的種種潛力。然而,如果我們從全新項目起步,結果又會如何?我們能夠向其中引入哪些突破性的思維?哪些解決方案可以帶來理想的穩定性?我們是否應該廣泛使用RxJava,并將響應式至上思路作為應用的結構指導?
Cycle.js庫(來自André Staltz)對于響應式至上思維作出了很好的詮釋: Cycle.js — Streams。
Rx具備良好的可組合特性,因此擁有巨大的發展潛力,然而,其與常規的面向對象編程風格又存在著顯著區別。事實上,對于毫無RxJava使用經驗的開發人員而言,其確實存在著難以理解的問題。
在開始新項目之前,我們面臨著更多需要回答的問題。舉例來說:
我們應該利用Kotlin替代Java嗎?(簡單來講,答案是肯定的。)
我們應該使用實驗性的Kotlin Coroutines嗎?(其帶來了全新的編程風格。)
我們是否該使用谷歌提供的實驗性庫:Android Architecture Components?
要為這些問題找到答案,我們需要首先創建一款小小應用,從而做出明智的決定。而這正是本文的內容所在——依托于整個流程得出有用的見解。如果大家希望了解更多細節,咱們馬上進入主題。
關于這款應用
這次實驗的目標在于創建一款應用,其能夠下載用戶所選定城市的相關天氣數據,并將預報結果通過圖形化圖表的形式顯示出來(再配上一些酷炫的動畫)。要求很簡單,但其中已經囊括了各類Android項目中會涉及到的大多數功能。
事實證明,coroutines與archtiecture components確實能夠良好協作,并給我們帶來能夠順利解決大量問題的整潔應用架構。
Cortoutines幫助我們以自然簡潔的方式表達思路。如果大家希望代碼能夠逐行體現您所希望執行的確切邏輯(即使您可能也需要在其間進行一些異步調用),suspendable函數也能很好地完成任務。
另外需要強調的是:無需在回調之間往來跳轉。在本示例應用當中,coroutines還徹底消除了對RxJava的依賴性。配合suspendable點的各函數在閱讀及理解難度方面遠低于部分RxJava運算符鏈——這些鏈可以快速實現函數化轉換。
話雖如此,但我個人認為并不是在每個用例中都能將RxJava替換為coroutines。我們發現,observalbes就是一類無法被逐一映射為suspendable函數的表達類型。特別是如果observable運算符鏈允許多個事件從其中流經,且其中每個suspendable點只能在每次調用時恢復一次,則無法實現一對一映射。
回到我們這款天氣應用中來:您可以查看其運作效果——但請注意,我并不是設計師,所以相關成果可能比較簡陋。
圖表動畫顯示您能夠輕松利用簡單的cortoutine以手動方式加以實現——其中不涉及任何ObjectAnimators、Interpolators、Evaluators或者PropertyValuesHolders等等。
最重要源代碼片段已經展示如下。不過如果您希望查看完整項目,請參閱 GitHub。
代碼量并不大,相信大家能夠輕松完成瀏覽。
我將從網絡層開始介紹這款應用的具體結構。接下來,我會探討業務邏輯(在MainModel.kt文件中),其幾乎不受限于Android系統平臺。最后,則是UI部分(這顯然僅適用于Android系統)。
為了方便起見,我在這里為總體架構圖添加了文本參考編號。我會特別關注其中綠色元素——即suspendable函數與actors(一個actor實際上就是一種非常實用的coroutine builder)。
總體來講,actor模型屬于一種并行計算數學模型——我將在下一篇博文中就此展開詳細探討。
01天氣服務
這項服務負責從Open Weather Map REST API處下載特定城市的天氣預報數據。
在這里,我使用了一套來自Square的簡單但強大的庫——Retrofit。我猜如今的Android開發人員不會沒聽說過它,但對于那些尚未接觸過的朋友,這里再解釋幾句:這是一套非常流行的Andoird平臺HTTP客戶端。其能夠面向POJO執行網絡調用與響應解析。這里我們直接使用典型的Retrofit配置。我還插入了Moshi轉換器將JSON響應轉換為數據類。
這里需要強調的一點是,我將由Retrofit生成的函數類型返回至另一新函數: Call。
我利用 Call.enqueue(回調)以面向Open Weather Map實際執行調用。這里我并沒有使用由Retrofit提供的任何調用適配工具,這是為了保證自己能夠將Call對象打包在suspendable函數當中。
02實用工具
從這里,我們正式邁入了全新的coroutines世界:我們希望創建一條打包有Call對象的suspendable函數。
要完成這部分內容,您需要掌握coroutines的一部分基礎知識。如果您尚不了解,請參閱《Coroutines指南》的第一章內容(由Roman Elizarov撰寫)。
這將是一條擴展函數:suspend fun Call.await,其負責調用Call.enqueue(…)(以實際執行網絡調用),而后調用suspends,再然后是resumes(當響應返回時)。
要將任意異步計算轉化為suspendable函數,我們需要使用The Kotlin標準庫當中的suspendCoroutine函數。其能夠為我們提供一個Continuation對象,這屬于一類通用回調。我們只需要在對新的suspendable函數進行恢復時(通常是在出現異常的情況下),調用其resume方法(或者resumeWithException方法)即可。
下一步是使用我們的新suspend fun Call.await函數,其負責將由Retrofit生成的異步函數轉換為便捷的suspendable函數。
03 Repository
Repository對象屬于我們在應用中需要顯示的數據(圖表)的實際來源。
在這里,我們會使用一些由suspend fun Call.await擴展生成的專用suspendable函數,用以實現天氣服務功能。如此一來,其返回的將全部為可立即使用的Forecast預報等數據,而非Call。接下來,我們在一條公共suspendable函數中使用這些數據:suspend fun getCityCharts(city:String):List。其能夠將來自API的數據轉化為可隨時顯示的圖表清單。這里我使用List上的部分定制化擴展屬性以將數據實際轉換至List。需要強調的是,只有suspendable函數能夠調用其它suspendable函數。
為了簡單起見,我們這里對此appid進行硬編碼。如果大家希望對應用進行測試,請點擊此處生成新的appid——這是因為如果此硬編碼appid被多人頻繁使用,則會自動被屏蔽24小時。
在下一步中,我們將創建主應用模型(采用Android ViewModel架構組件),其利用一個actor(coroutine builder)以實現應用邏輯。
04 模型
在這款應用中,我們只使用一套簡單的模型: MainModel : ViewModel,且僅供一種活動使用: MainActivity。
這個類代表著應用本身。其將由我們的活動(實際上是由Android系統的ViewModelProvider)進行實例化,但能夠在配置變更(例如屏幕旋轉)后繼續存在,且保證新的活動實例仍擁有相同的模型實例。我們完全不必擔心活動生命周期問題。相較于實現與各方法(onCreate、onDestroy等)相關的活動生命周期,這里我們只擁有一項onCleared()方法,其會在用戶退出此應用時進行調用。
更確切地講,當活動finished時,onCleared方法將得到調用。
盡管并沒有與活動生命周期緊密結合,我們仍然需要采取某種方式以發布應用模型的當前狀態,從而將其顯示在某些特定位置(在活動中)。在這方面,LiveData 能夠發揮良好作用。
LiveData類似于對RxJava BehaviorSubject的二次創造……其擁有一項observable可變值。其最重要的差異體現在訂閱方式以及隨后在MainActivity當中進行查看的方式上。
不過LiveData也不具備像Observable那樣強大的可組合運算符。大家可以查看LiveData提供的部分簡單轉換信息。
不過LiveData也不具備像Observable那樣強大的可組合運算符。大家可以點擊此處查看LiveData提供的部分簡單轉換信息。
另一項區別在于,LiveData僅適用于Android,但RxJava主題則更具普適性; 因此我們可以利用常規非Android JUnit對后者進行輕松測 試。
最后一項不同是,LiveData具有“生命周期意識”——我將在下一篇介紹MainActivity類的博文當中對此作出詳盡探討。
在本示例中,我們選擇使用MutableLiveData:各LiveData對象允許直接向其中隨意添加新值。應用state由4個LiveData對象負責體現:city、charts、loading以及message。其中最重要的自然是charts:LiveData>對象,其代表著需要顯示的當前圖表清單。
所有會引發應用狀態變化以及響應用戶操作的任務皆由ACTOR負責執行。
Actors 非常強大,我將在下一篇博文中進行具體解釋。
總結
我們已經為主actor作好了一切籌備。如果大家認真查看actor代碼內容,那么即使您并不了解coroutines或者actors理論,應該也能夠看懂其工作原理。雖然只有寥寥數行,但其中實際包含著本款應用的全部重要業務邏輯。最值得強調的就是我們調用 suspendable函數的位置(由綠線加灰色箭頭所指定的部分)。第一個位置為suspendable點,即隨用戶操作進行迭代的部分; 第二個位置則為網絡調用。歸功于coroutines,這里的內容更近似于同步阻塞代碼,但實際上卻并不會擁塞該線程。
請大家期待我的下一篇博文,屆時我將詳細講解關于actors與channels的一切。