Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android資訊 >> Android應用架構變更背後的經驗、失誤與推論

Android應用架構變更背後的經驗、失誤與推論

編輯:Android資訊

軟件代碼庫各個不同的部分應當彼此獨立,其整體卻猶如一部運轉良好的機器

Android的開發生態系統發展迅速,每周都有變化,人們不停地創建新工具、更新資源庫、撰寫博文、發表演講。只要享受一個月的假期,回來的時候支持庫和/或Play Services都更新換代了。

Android應用架構變更背後的經驗、失誤與推論

筆者與 ribot團隊 合作開發Android應用已有超過三年時間。在這段時間裡,我們用來構建Android應用的架構與技術一直在不斷進化。在本文中,我們將具體闡述這些架構變更背後的經驗、失誤還有推論。

過去

早在2012年,我們的代碼庫總是采用基礎架構,並未使用任何網絡庫,還是用老一套的AsyncTasks。下面的圖表粗略地演示了這個架構。

Android應用架構變更背後的經驗、失誤與推論

初始架構

代碼共分兩層:控制從REST API檢索/保存數據的數據層(data layer),還有負責在UI上控制與展示數據的視圖層(view layer)。

APIProvider提供方法,讓Activities和Fragments能夠很容易地與REST API交互。運用URLConnection和AsyncTasks來執行單獨線程中的網絡調用,並通過回調向Activities返回結果。

類似地,CacheProvider包含了從SharedPreferences或SQLite數據庫檢索存儲數據的方式,通過回調將結果返回給Activities。

問題

這個辦法的主要問題在於,視圖層責任過大。試想一個簡單的通用場景:應用程序在加載文章列表時,將其緩存到SQLite數據庫中,並最終展示在ListView中。具體執行如下:

  1. 調用APIProvider中的loadPosts(回調)方法;
  2. 等待APIProvider成功回調,然後調用CacheProvider中的savePosts(回調);
  3. 等待CacheProvider成功回調,然後在ListView中顯示文章;
  4. 分別處理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 ,方便用戶修改、篩選或合並數據。

考慮到前些年遭受的痛苦,我們開始考慮新應用的架構是什麼樣的,然後得出了這個。

Android應用架構變更背後的經驗、失誤與推論

與頭一個方法類似,這個架構也可以分為兩層,分別是數據層與視圖層。數據層包含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使得嵌套回調不再有必要。

Android應用架構變更背後的經驗、失誤與推論

  • DataManager接管了之前視圖層的部分職責,從此Activities和Fragments更為輕量。
  • 將代碼從Activities和Fragments中轉移到DataManager和helpers中,意味著單元測試寫起來更簡單。
  • 明確的職責分離,加上使用DataManager作為唯一與數據層的交互點,這些做法讓這個架構測試時更為友好。Helper類或DataManager很容易模擬。

還有什麼問題呢?

  • 對於非常復雜的大型項目來說,DataManager可能會過於龐大而難以維護。
  • 盡管Activities與Fragments之類的視圖層組件逐漸更為輕量級,仍然需要處理相當數量的邏輯,比如管理RxJava訂閱、分析錯誤等。

集成模型視圖顯示

在過去的一年中,像MVP、MVVM這樣的一些架構模型在Android社區受到了熱捧。在 樣例項目 與 文章 中研究過這些模型之後,我們發現MVP能夠對我們目前的方法帶來很有價值的改進。由於我們目前的架構分為兩層(視圖與數據層),加上MVP也很自然。我們 只需增加一個新的展示層(a new layer of presenters),將一部分代碼從視圖層移過去就可以了。

Android應用架構變更背後的經驗、失誤與推論

基於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應用的更佳途徑。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved