Android應用架構變更背後的經驗、失誤與推論
軟體程式碼庫各個不同的部分應當彼此獨立,其整體卻猶如一部運轉良好的機器
Android的開發生態系統發展迅速,每週都有變化,人們不停地建立新工具、更新資源庫、撰寫博文、發表演講。只要享受一個月的假期,回來的時候支援庫和/或Play Services都更新換代了。
筆者與 ribot團隊 合作開發Android應用已有超過三年時間。在這段時間裡,我們用來構建Android應用的架構與技術一直在不斷進化。在本文中,我們將具體闡述這些架構變更背後的經驗、失誤還有推論。
過去
早在2012年,我們的程式碼庫總是採用基礎架構,並未使用任何網路庫,還是用老一套的AsyncTasks。下面的圖表粗略地演示了這個架構。
初始架構
程式碼共分兩層:控制從REST API檢索/儲存資料的資料層(data layer),還有負責在UI上控制與展示資料的檢視層(view layer)。
APIProvider提供方法,讓Activities和Fragments能夠很容易地與REST API互動。運用URLConnection和AsyncTasks來執行單獨執行緒中的網路呼叫,並通過回撥向Activities返回結果。
類似地,CacheProvider包含了從SharedPreferences或SQLite資料庫檢索儲存資料的方式,通過回撥將結果返回給Activities。
問題
這個辦法的主要問題在於,檢視層責任過大。試想一個簡單的通用場景:應用程式在載入文章列表時,將其快取到SQLite資料庫中,並最終展示在ListView中。具體執行如下:
- 呼叫APIProvider中的loadPosts(回撥)方法;
- 等待APIProvider成功回撥,然後呼叫CacheProvider中的savePosts(回撥);
- 等待CacheProvider成功回撥,然後在ListView中顯示文章;
- 分別處理APIProvider和CacheProvider的回撥錯誤。
這是個簡單的例子。在真實案例場景中,REST API可能不會按照瀏覽所需的那樣返回資料,因此Activity會設法在展示資料之前對其進行轉換或過濾。另一個常見案例:在使用 loadPosts() 方法獲取需要從別處拿到的引數時,比如由Play Services SDK提供的電子郵件地址,很有可能SDK會通過回撥非同步返回郵件,也就是說我們現在有三層巢狀回撥(nested callbacks)。如果複雜性繼續增加,這個方法會導致所謂的回撥地獄(callback hell)。
總結:
- Activities和Fragments逐漸過大而難以維護;
- 巢狀回撥太多,導致程式碼醜陋不堪,難以理解與修改,也不好增加新功能;
- 單元測試也頗有難度,即便勉強進行,由於Activities或Fragments中包含有大量邏輯,相關工作也會相當費勁。
由RxJava驅動的新架構
差不多在兩年時間中,我們都在採用前面描述的那種架構。在那段時間裡,我們做了一些修正,但是解決問題時收效甚微。例如,我們增加了一些helper類,以減少Activities和Fragments中的程式碼,並開始在APIProvider中使用 Volley 。儘管如此,在應用程式碼測試時還是面臨測試友好性問題與回撥地獄頻繁出現的問題。
直到2014年我們發現了 RxJava ,在嘗試了幾個樣例專案後,我們發現這可能是解決巢狀回撥問題的終極解決辦法。如果對響應式程式設計不熟悉的話,可以參考 這篇簡介 。簡單來講,RxJava允許使用者通過非同步流管理資料,並提供很多可用在事件流中的 operator ,方便使用者修改、篩選或合併資料。
考慮到前些年遭受的痛苦,我們開始考慮新應用的架構是什麼樣的,然後得出了這個。
與頭一個方法類似,這個架構也可以分為兩層,分別是資料層與檢視層。資料層包含DataManager,還有一系列helper。檢視層由諸如Fragments、Activities、ViewGroups等Android框架元件構成。
Helper類(圖表第三列)包含具體的職責,同時執行方式也很簡潔。例如大多專案包含訪問REST API的helper,從資料庫讀取資料的helper或者與第三方SDK互動的helper。不同的應用程式包含不同數量的helper,不過最常見的helper有:
- PreferencesHelper:在SharedPreferences中讀取與儲存資料。
- DatabaseHelper:處理SQLite資料庫的訪問。
- Retrofit 服務:從REST API執行呼叫。我們使用Retrofit來代替Volley,因為它提供了對RxJava的支援,也更好用。
大多數helper類中的公共方法會返回RxJava Observables。
DataManager是這個架構的核心,它廣泛運用了RxJava operator來合併、篩選與轉換從helper類中獲得的資料。DataManager的目標是通過提供準備顯示的資料,來減少Activities 和Fragments的工作量,而且這些資料一般無需任何轉換。
下面的程式碼就是DataManager方法的例項。
- 呼叫Retrofit服務來載入從REST API獲取的文章列表。
- 用DatabaseHelper在本地資料庫中儲存文章,做快取使用。
- 按照檢視層的需求,篩選出今天撰寫的文章。
public Observable<Post> loadTodayPosts() { return mRetrofitService.loadPosts() .concatMap(new Func1<List<Post>, Observable<Post>>() { @Override public Observable<Post> call(List<Post> apiPosts) { return mDatabaseHelper.savePosts(apiPosts); } }) .filter(new Func1<Post, Boolean>() { @Override public Boolean call(Post post) { return isToday(post.date); } }); }
像Activities或Fragments之類的檢視層元件會簡單呼叫這個方法,並訂閱返回的Observable。一旦訂閱完成,Observable所發出的不同文章就能直接加入到Adapter中,以便在RecyclerView或類似元件中顯示。
這個架構的最後一個元素是Eventbus(事件匯流排),它允許我們將資料層的事件進行廣播,因此檢視層的多個元件能夠訂閱這些事件。例 如,DataManager中的signOut()方法可以在Observable完成時釋出一個事件,讓多個訂閱這個事件的Activities修改 UI,顯示為登出狀態。
為什麼這個方法更好?
RxJava Observables和operators使得巢狀回撥不再有必要。
- DataManager接管了之前檢視層的部分職責,從此Activities和Fragments更為輕量。
- 將程式碼從Activities和Fragments中轉移到DataManager和helpers中,意味著單元測試寫起來更簡單。
- 明確的職責分離,加上使用DataManager作為唯一與資料層的互動點,這些做法讓這個架構測試時更為友好。Helper類或DataManager很容易模擬。
還有什麼問題呢?
- 對於非常複雜的大型專案來說,DataManager可能會過於龐大而難以維護。
- 儘管Activities與Fragments之類的檢視層元件逐漸更為輕量級,仍然需要處理相當數量的邏輯,比如管理RxJava訂閱、分析錯誤等。
整合模型檢視顯示
在過去的一年中,像MVP、MVVM這樣的一些架構模型在Android社群受到了熱捧。在 樣例專案 與 文章 中研究過這些模型之後,我們發現MVP能夠對我們目前的方法帶來很有價值的改進。由於我們目前的架構分為兩層(檢視與資料層),加上MVP也很自然。我們 只需增加一個新的展示層(a new layer of presenters),將一部分程式碼從檢視層移過去就可以了。
基於MVP的架構
資料層保持不變,不過現在改名為模型層(Model),以便名符其實。
展示層控制載入來自模型層的資料,並在結果準備好之後呼叫檢視層的正確方法來顯示。它訂閱DataManager返回的Observables,因此必須處理類似 排程 與 訂閱 之類的工作。此外,它可以分析錯誤程式碼,或者在需要時在資料流中應用額外操作。例如,如果我們需要篩選一些資料,而這個篩選無法在其他地方複用,那麼用展示層來實現會比在DataManager實現要更好。
下面是在展示層中公共方法的案例。這部分程式碼訂閱了從dataManager.loadTodayPosts()方法返回的Observable。
public void loadTodayPosts() { mMvpView.showProgressIndicator(true); mSubscription = mDataManager.loadTodayPosts().toList() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new Subscriber<List<Post>>() { @Override public void onCompleted() { mMvpView.showProgressIndicator(false); } @Override public void onError(Throwable e) { mMvpView.showProgressIndicator(false); mMvpView.showError(); } @Override public void onNext(List<Post> postsList) { mMvpView.showPosts(postsList); } }); }
mMvpView是這個展示層正在assist的檢視層元件。一般MVP檢視是Activity、Fragment或ViewGroup例項。
就像之前的架構那樣,檢視層包含像ViewGroups、Fragments或Activities這樣的標準框架元件。這些元件的主要區別在於 沒有直接訂閱Observables,而是執行MVP檢視,提供一系列類似showError() 或showProgressIndicator()之類的簡明方法。檢視元件還控制處理類似點選事件之類的與使用者互動,並通過呼叫展示層的正確方法來執 行。例如,如果我們有一個載入文章列表的按鈕,Activity就會從onClick監聽那裡呼叫 presenter.loadTodayPosts()。
想要檢視基於MVP的完整架構,請檢視 Android Boilerplate project on GitHub 或者 ribot’s architecture guidelines 。
為什麼這個方法更好?
- Activities和Fragments都很輕量。只需負責建立/更新UI,處理使用者事件。因此更容易維護。
- 我們現在能夠通過模擬檢視層,從展示層書寫簡單的單元測試了。之前這些程式碼是檢視層的一部分,沒辦法進行單元測試。而且整體架構對測試更加友好。
- 如果DataManager過於龐大,我們可以通過將一些程式碼挪到presenter中緩解這個問題。
還有什麼問題?
- 在程式碼庫變得非常龐大與複雜時,單一的DataManager仍是個問題。我們尚未觸及到真實問題點,不過遲早會碰到。
需要注意的是,這個架構並不完美。事實上,認為它是唯一而且完美的架構,能夠一勞永逸的解決問題這樣的想法太過天真。Android的生態系統會繼續保持高速發展,我們必須持續探索、閱讀、實驗,才能找到構建優秀Android應用的更佳途徑。
相關文章
- Hulu大資料架構與應用經驗大資料架構
- 機器學習-演算法背後的理論與優化(part3)--經驗風險與泛化誤差概述機器學習演算法優化
- Android應用架構Android應用架構
- iOS應用架構談:架構設計的方法論iOS應用架構
- 架構與體驗,在戰棋遊戲關卡設計背後的雜談架構遊戲
- LAMP架構的安裝與經驗技巧LAMP架構
- iOS應用架構談(一):架構設計的方法論iOS應用架構
- Android架構系列-MVP架構的實際應用Android架構MVP
- 變更管理與汽車研發應用
- 視訊直播秒開背後的技術與優化經驗優化
- HBase 與 Cassandra 架構對比分析的經驗分享架構
- 我的架構經驗系列文章-前端架構架構前端
- 論軟體架構設計及應用架構
- Android應用開發架構概述Android架構
- 通俗易懂的Android應用架構思想Android應用架構
- 後端應用分層經驗總結後端
- MySQL在大型網站的應用架構演變MySql網站應用架構
- 軟考論文論湖倉一體架構及其應用架構
- UNIX 安全構架經驗(轉)
- 在Android應用中使用Clean架構Android架構
- Android應用架構之MVP實現Android應用架構MVP
- 【分享貼】需求變更、專案延誤,專案經理應該如何應對?
- Android應用架構的發展和實踐Android應用架構
- MySQL 企業常用架構與調優經驗分享MySql架構
- 微信支付商戶系統架構背後的故事架構
- 高可用可伸縮架構實用經驗談架構
- ERP軟體應用的誤區與謬論(轉)
- 有道詞典Flutter架構與應用Flutter架構
- 大型網站應用中MySQL的架構演變史網站MySql架構
- Android 開發軟體架構思考以及經驗總結Android架構
- Cortana小娜失敗背後,微軟的傲慢與偏見微軟
- Android應用中Clean架構使用詳解Android架構
- Clean Architecture - 清晰簡潔的Android 應用架構Android應用架構
- UNIX安全構架的九點經驗(轉)
- 攝像頭背後的應用–資訊圖
- 在 Android 12 中構建更現代的應用 WidgetAndroid
- 使用Kotlin構建更適合Android的MVVM應用程式KotlinAndroidMVVM
- 軟考論文之論企業整合架構設計及其應用架構