paulwong

          Android Application Architecture 安卓APP架構(gòu)[譯]

          本文介紹了文章作者從事了幾年android應(yīng)用的開(kāi)發(fā),經(jīng)歷2次架構(gòu)變革,第一次集成了RxJava第二次集成了MVP,并將RxJava與MVP完美結(jié)合,實(shí)現(xiàn)了低耦合,代碼簡(jiǎn)單,測(cè)試方便的架構(gòu)。

          其實(shí)我們?cè)陂_(kāi)發(fā)中也遇到過(guò),Android入門(mén)門(mén)檻較低,如果前期對(duì)APP規(guī)劃不清晰,Coder們對(duì)未來(lái)變化把握不準(zhǔn),技術(shù)架構(gòu)經(jīng)驗(yàn)不夠強(qiáng)大,最終導(dǎo)致就是一個(gè)Activity幾千行,里面寫(xiě)了大量的Private方法,拆成幾個(gè)Fragment、封裝出來(lái)幾個(gè)類(lèi)都是無(wú)法解決,結(jié)果就是看Activity難受的要死,糾結(jié),看了不爽改也不是不改也不是,嚴(yán)重影響看的人的心情。并且怨天尤人這個(gè)是產(chǎn)品人員規(guī)劃App不好,沒(méi)有前瞻性,改來(lái)改去。。。

          這篇文章就是使用新的結(jié)構(gòu)解決該問(wèn)題。

          安卓APP架構(gòu)

          Android Application Architecture

          Our journey from standard Activities and AsyncTasks to a modern MVP-based architecture powered by RxJava.

          這篇文章主要目的是講述如何將傳統(tǒng)的Activities 與 AsyncTasks 模式向目前主流的MVP架構(gòu)基礎(chǔ)的響應(yīng)式編程框架過(guò)度。

          1*HrE2lljEfsCu1X_OUDfHYA

          Different parts of a software codebase should be independent, yet perfectly work together like a well-oiled machine — photo by Chester Alvarez.

          先暢享一下:~~~如果松耦合架構(gòu),分工明確,然后完美的組合在一起工作是一個(gè)很吊的事情。
          (轉(zhuǎn)個(gè)圖片還要寫(xiě)明白誰(shuí)拍的,版權(quán)意識(shí)真強(qiáng))

          The Android dev ecosystem moves very quickly. Every week new tools are created, libraries are updated, blog posts are written and talks are given. If you go on holiday for a month, by the time you come back there will be a new version of the support library and/or Play Services.

          最近幾年Android的生態(tài)鏈變化非常迅速,從底層的Android Api到應(yīng)用層的各種開(kāi)源的類(lèi)庫(kù)、工具更新非常迅速。一不留神就落后了。

          I’ve been making Android apps with the ribot team for over three years. During this time, the architecture and technologies we’ve used to build Android apps have been continuously evolving. This article will take you through this journey by explaining our learnings, mistakes and the reasoning behind these architectural changes.

          我在Ribot團(tuán)隊(duì)從事Android應(yīng)用開(kāi)發(fā)工作三年多,伴隨著公司技術(shù)的不斷創(chuàng)新,積累了很多經(jīng)驗(yàn)、錯(cuò)誤以及在技術(shù)選型背后的故事。

          舊的應(yīng)用架構(gòu)

          The old times
          Back in 2012 our codebases used to follow a basic structure. We didn’t use any networking library and AsyncTasks were still our friends. The diagram below shows approximately how the architecture was.

          2012年那個(gè)時(shí)候,我們的代碼都是用的原生Android,沒(méi)有使用任何的網(wǎng)絡(luò)請(qǐng)求框架,而是基于AsyncTasks書(shū)寫(xiě)。
          1*TTtpcT4H80THBofnCtQ_L>The code was structured in two layers: the data layer that was in charge of retrieving/saving data from REST APIs and persistent data stores; and the view layer, whose responsibility was handling and displaying the data on the UI.
          The APIProvider provides methods to enable Activities and Fragments to easily interact with the REST API. These methods use URLConnection and AsyncTasks to perform network calls in a separate thread and return the result to the Activities via callbacks.

          代碼分為兩層,Data與View,Data層主要是用來(lái)從API獲取數(shù)據(jù),保存到持久化的db當(dāng)中。View層主要就是把Data的數(shù)據(jù)顯示到UI上。APIProvider提供方法出來(lái),用于在Activity或者Fragment中方便的進(jìn)行控制與交互。技術(shù)上將,使用URLConnection與AsyncTasks實(shí)現(xiàn)了一個(gè)異步的網(wǎng)絡(luò)請(qǐng)求并將結(jié)果返回到調(diào)用的回調(diào)方法里面。

          In a similar way, the CacheProvider contains methods that retrieve and store data from SharedPreferences or a SQLite database. It also uses callbacks to pass the result back to the Activities.

          相同的原理CacheProvider提供一系列方法,將SharedPreferences或者SQLite的數(shù)據(jù)取出來(lái),并且返回給到Activity

          問(wèn)題

          The problems
          The main issue with this approach was that the View layer had too many responsibilities. Imagine a simple common scenario where the application has to load a list of blog posts, cache them in a SQLite database and finally display them on a ListView. The Activity would have to do the following:

          主要問(wèn)題是View層有太多的累贅,以一個(gè)博客列表為例來(lái)講述,比如博客需要顯示一個(gè)ListView,從SQLite讀取數(shù)據(jù),Activity需要做到以下幾點(diǎn):

          1. Call a method loadPosts(callback) in the APIProvider
          2. Wait for the APIProvider success callback and then call savePosts(callback) in the CacheProvider.
          3. Wait for the CacheProvider success callback and then display the posts on the ListView.
          4. Separately handle the two potential errors callback from the APIProvider and CacheProvider.
          1. 執(zhí)行APIProvider里面的loadPosts的方法,里面?zhèn)魅牖卣{(diào)參數(shù)內(nèi)容。
          2. 等待loadPosts執(zhí)行成功后,執(zhí)行回調(diào)里面的CacheProvider中的savePosts方法,savePosts也要傳入回調(diào)參數(shù)。
          3. 等待savePosts執(zhí)行成功后,執(zhí)行回調(diào)里面的方法刷新ListView
          4. 分別書(shū)寫(xiě)代碼處理2 3 兩步的錯(cuò)誤回調(diào)內(nèi)容。

          This is a very simple example. In a real case scenario the REST API will probably not return the data like the view needs it. Therefore, the Activity will have to somehow transform or filter the data before showing it. Another common case is when the loadPosts() method takes a parameter that needs to be fetched from somewhere else, for example an email address provided by the Play Services SDK. It’s likely that the SDK will return the email asynchronously using a callback, meaning that we now have three levels of nested callbacks. If we keep adding complexity, this approach will result into what is known as callback hell.

          這還是一個(gè)比較簡(jiǎn)單的例子,在一些真實(shí)的場(chǎng)景中,遠(yuǎn)程的API可能沒(méi)有返回程序的必須值,但是activity必須把數(shù)據(jù)處理完成之后才能顯示結(jié)果。再一個(gè)例子就是如果loadPosts方法需要借助一些其他地方的返回參數(shù)時(shí),類(lèi)似用多線程去實(shí)現(xiàn)同步請(qǐng)求,為保證數(shù)據(jù)正常請(qǐng)求,意味著必須做一個(gè)三層的回調(diào),如果再?gòu)?fù)雜一些,想理清楚這些回調(diào)就是很蛋疼的事情。

          In summary:
          Activities and Fragments become very large and difficult to maintain
          Too many nested callbacks means the code is ugly and difficult to understand so painful to make changes or add new features.
          Unit testing becomes challenging, if not impossible, because a lot of the logic lives within the Activities or Fragments that are arduous to unit test.

          總之,回調(diào)多了之后,Activity與Fragment會(huì)亂的要死,并且一般人無(wú)法直視。

          牛逼的新架構(gòu)出來(lái)了

          A new architecture driven by RxJava
          We followed the previous approach for about two years. During that time, we made several improvements that slightly mitigated the problems described above. For example, we added several helper classes to reduce the code in Activities and Fragments and we started using Volley in the APIProvider. Despite these changes, our application code wasn’t yet test-friendly and the callback hell issue was still happening too often.

          我們?cè)诘疤鄣募軜?gòu)中煎熬了2年,當(dāng)然也嘗試過(guò)很多方式,最終也只能是緩和一下亂的問(wèn)題。我們?cè)贏PIProvider使用了Volley,代替了AsyncHttpClient,但是其實(shí)是一個(gè)吊樣。

          It wasn’t until 2014 when we started reading about RxJava. After trying it on a few sample projects, we realised that this could finally be the solution to the nested callback problem. If you are not familiar with reactive programming you can read this introduction. In short, RxJava allows you to manage data via asynchronous streams and gives you many operators that you can apply to the stream in order to transform, filter or combine the data.

          不到2014年我們就開(kāi)始進(jìn)行RxJava的預(yù)研,然后嘗試了一批簡(jiǎn)單的項(xiàng)目,感覺(jué)RxJava的方式是解決我們嵌套回調(diào)的終極解決辦法。簡(jiǎn)單的說(shuō),RxJava允許你通過(guò)異步流的方式管理你的數(shù)據(jù),并且還可以通過(guò)操作符(Operators)對(duì)Observable對(duì)象的變換

          Taking into account the pains we experienced in previous years, we started to think about how the architecture of a new app would look. So we came up with this.

          我們用了幾年的經(jīng)驗(yàn)痛定思痛,搞了下面這么個(gè)東西,新的APP的架構(gòu)圖

          1*kCynNIa5PscRl41V2scosA-200

          Similar to the first approach, this architecture can be separated into a data and view layer. The data layer contains the DataManager and a set of helpers. The view layer is formed by Android framework components like Fragments, Activities, ViewGroups, etc.

          與第一種方法相似,這個(gè)架構(gòu)也是分為Data層與View層,Data層包含DataManager與一堆Helper;View層是包含F(xiàn)ragments, Activities, ViewGroups等。

          Helper classes (third column on diagram) have very specific responsibilities and implement them in a concise manner. For example, most projects have helpers for accessing REST APIs, reading data from databases or interacting with third party SDKs. Different applications will have a different number of helpers but the most common ones are:

          Helper主要是集成第三方的類(lèi)庫(kù),以便于在代碼中幾行代碼就可以清晰的實(shí)現(xiàn)某個(gè)功能,比如請(qǐng)求API,訪問(wèn)數(shù)據(jù)庫(kù)等,雖然不同的應(yīng)用程序都有不同的類(lèi)庫(kù),但是他們無(wú)非就是以下這些內(nèi)容:

          • PreferencesHelper: reads and saves data in SharedPreferences.
          • DatabaseHelper: handles accessing SQLite databases.
          • Retrofit services: perform calls to REST APIs. We started using Retrofit instead of Volley because it provides support for RxJava. It’s also nicer to use.
          • 從SharedPreferences中讀取或者寫(xiě)入數(shù)據(jù)
          • 讀寫(xiě)SQLite數(shù)據(jù)庫(kù)
          • 類(lèi)似與square的Retrofit服務(wù),也就是Http Client,我們用Restrofit替代了Volley因?yàn)樗С諶xjava,并且更吊。

          Most of the public methods inside helper classes will return RxJava Observables.
          The DataManager is the brain of the architecture. It extensively uses RxJava operators to combine, filter and transform data retrieved from helper classes. The aim of the DataManager is to reduce the amount of work that Activities and Fragments have to do by providing data that is ready to display and won’t usually need any transformation.

          RxJava最核心的兩個(gè)東西是Observables(被觀察者,事件源)和Subscribers(觀察者),在Helper類(lèi)中的Public方法,一般都會(huì)返回一個(gè)RxJava的Observables;DataManager是整個(gè)架構(gòu)的大腦,他大量的使用Rxjava的operators對(duì)Helper返回來(lái)的數(shù)據(jù)進(jìn)行的整合過(guò)濾、二次處理。

          The code below shows what a DataManager method would look like. This sample method works as follows:

          下面用一個(gè)例子來(lái)說(shuō)明DataManager是做什么的:

          1. Call the Retrofit service to load a list of blog posts from a REST API
          2. Save the posts in a local database for caching purposes using the DatabaseHelper.
          3. Filter the blog posts written today because those are the only ones the view layer wants to display.
          1. 調(diào)用Retrofit的服務(wù),去請(qǐng)求一個(gè)博客列表的API
          2. 用DatabaseHelper保存這些數(shù)據(jù)到數(shù)據(jù)庫(kù)
          3. 過(guò)濾出這些BLOG哪些是今天寫(xiě)的,然后顯示到UI界面上。

          Components in the view layer such as Activities or Fragments would simply call this method and subscribe to the returned Observable. Once the subscription finishes, the different Posts emitted by the Observable can be directly added to an Adapter in order to be displayed on a RecyclerView or similar.

          Observables發(fā)出一系列事件,Subscribers(例如 Activities or Fragments)處理這些事件,可以直接將數(shù)據(jù)顯示到一些可以回收、重用的View上面。
          【BTW:如果一個(gè)Observerble沒(méi)有任何的的Subscriber,那么這個(gè)Observable是不會(huì)發(fā)出任何事件的】

          The last element of this architecture is the event bus. The event bus allows us to broadcast events that happen in the data layer, so that multiple components in the view layer can subscribe to these events. For example, a signOut() method in the DataManager can post an event when the Observable completes so that multiple Activities that are subscribed to this event can change their UI to show a signed out state.

          這個(gè)架構(gòu)的另外一個(gè)模塊是event bus,event bus可以讓我們?cè)贒ata層發(fā)出廣播(不是Android的Broadcast)然后不同的模塊去注冊(cè)并接收不同的廣播事件

          Why was this approach better?
          RxJava Observables and operators remove the need for having nested callbacks.
          1*BIsOCzJnc-SSU8fPXTiP1A

          為什么這個(gè)方式這么牛逼,是因?yàn)镺bservables與operators可以去掉那一堆必須的回調(diào)方法

          The DataManager takes over responsibilities that were previously part of the view layer. Hence, it makes Activities and Fragments more lightweight.
          Moving code from Activities and Fragments to the DataManager and helpers means that writing unit tests becomes easier.

          DataManager替代了傳統(tǒng)架構(gòu)中很多代碼,從而使得Activity與Fragment變得更加輕量級(jí)。并且使得單元測(cè)試變得更加簡(jiǎn)單。

          Clear separation of responsibilities and having the DataManager as the only point of interaction with the data layer, makes this architecture test-friendly. Helper classes or the DataManager can be easily mocked.

          DataManager成為了唯一的數(shù)據(jù)交互部分,這樣清晰的架構(gòu)使得更方便進(jìn)行代碼自測(cè)。

          What problems did we still have?
          For large and very complex projects the DataManager can become too bloated and difficult to maintain.
          Although view layer components such as Activities and Fragments became more lightweight, they still have to handle a considerable amount of logic around managing RxJava subscriptions, analysing errors, etc.

          我們還有什么問(wèn)題?
          - 如果對(duì)于非常龐大并且復(fù)雜的項(xiàng)目來(lái)說(shuō),DataManger也會(huì)變得非常臃腫并且難以維護(hù)。
          - 盡管Activity與Fragment已經(jīng)變得更加輕量級(jí),但是對(duì)于錯(cuò)誤異常的處理還是要在subscriptions的地方去書(shū)寫(xiě)。

          一體化的MVP模式

          Integrating Model View Presenter
          In the past year, several architectural patterns such as MVP or MVVM have been gaining popularity within the Android community. After exploring these patterns on a sample project and article, we found that MVP could bring very valuable improvements to our existing approach. Because our current architecture was divided in two layers (view and data), adding MVP felt natural. We simply had to add a new layer of presenters and move part of the code from the view to presenters.

          前幾年開(kāi)始,很多類(lèi)似MVP與MVVM在Android的一些社區(qū)比較流行,經(jīng)過(guò)研究之后,我們發(fā)現(xiàn)MVP模式是對(duì)我們目前的方案最有價(jià)值的改動(dòng)。我們的兩層架構(gòu)View-Data與MVP的 Model-View架構(gòu)天然融合,理念一致。我們只需要增加一個(gè)presenters層,然后把之前在view實(shí)現(xiàn)的代碼移到上面就可以了。
          1*NonRJ0uzzN9o1ygT6J421g

          The data layer remains as it was but it’s now called model to be more consistent with the name of the pattern.
          Presenters are in charge of loading data from the model and calling the right method in the view when the result is ready. They subscribe to Observables returned by the data manager. Therefore, they have to handle things like schedulers and subscriptions. Moreover, they can analyse error codes or apply extra operations to the data stream if needed. For example, if we need to filter some data and this same filter is not likely to be reused anywhere else, it may make more sense to implement it in the presenter rather than in the data manager.

          之前的Data層就是現(xiàn)在的MVP中的Model,Presenter現(xiàn)在負(fù)責(zé)從Model中加載數(shù)據(jù),加載完成后后再去調(diào)用左邊的在Activity、ViewGroup中的方法。Presenters的subscribe去接收data manager中的Observables廣播出來(lái)的數(shù)據(jù)。
          舉例說(shuō)明,如果我們需要增加數(shù)據(jù)的過(guò)濾操作但是并不是所有地方都需要的那種,那就可以在presenter里面寫(xiě)這些代碼,而不用寫(xiě)在公共的datamanager里面。

          Below you can see what a public method in the presenter would look like. This code subscribes to the Observable returned by the dataManager.loadTodayPosts() method we defined in the previous section.

          我們定義的dataManager.loadTodayPosts()會(huì)廣播出數(shù)據(jù)給到對(duì)應(yīng)的subscribes

          The mMvpView is the view component that this presenter is assisting. Usually the MVP view is an instance of an Activity, Fragment or ViewGroup.

          MVP的View并不是指的Android的View,而是一個(gè)界面組件的的實(shí)例,例如Activity, Fragment , ViewGroup 在注冊(cè)presenter的時(shí)候,需要把自己當(dāng)前的實(shí)例傳遞進(jìn)去。

          // Activity onCreate 中的代碼段  if (presenter == null)         presenter = new Presenter1();         presenter.onTakeView(this);  

          Like the previous architecture, the view layer contains standard framework components like ViewGroups, Fragments or Activities. The main difference is that these components don’t subscribe directly to Observables. They instead implement an MvpView interface and provide a list of concise methods such as showError() or showProgressIndicator(). The view components are also in charge of handling user interactions such as click events and act accordingly by calling the right method in the presenter. For example, if we have a button that loads the list of posts, our Activity would call presenter.loadTodayPosts() from the onClick listener.

          這個(gè)架構(gòu)與上一個(gè)架構(gòu)不同的是,ViewLayer 也就是Activity這些,不會(huì)直接去訂閱接收Observables發(fā)出的這些事件。而是只在Activity實(shí)現(xiàn)幾個(gè)簡(jiǎn)單的顯示錯(cuò)誤、顯示進(jìn)度的方法(用接口interface來(lái)規(guī)范統(tǒng)一),然后把當(dāng)前實(shí)例以參數(shù)形式傳遞給到對(duì)應(yīng)事件的Presenter,由Presenter去執(zhí)行這些顯示錯(cuò)誤、顯示進(jìn)度的方法。
          當(dāng)然對(duì)于用戶交互部分的按鈕點(diǎn)擊事件還是要在Activity中進(jìn)行處理。

          If you want to see a full working sample of this MVP-based architecture, you can check out our Android Boilerplate project on GitHub. You can also read more about it in the ribot’s architecture guidelines.

          關(guān)于MVP的文章可以自行百度一下,MVP Android 關(guān)鍵詞

          Why is this approach better?

          為什么這個(gè)又最吊

          • Activities and Fragments become very lightweight. Their only responsibilities are to set up/update the UI and handle user events. Therefore, they become easier to maintain.
          • We can now easily write unit tests for the presenters by mocking the view layer. Before, this code was part of the view layer so we couldn’t unit test it. The whole architecture becomes very test-friendly.
          • If the data manager is becoming bloated, we can mitigate this problem by moving some code to the presenters.
          • Activity與Fragment代碼量大大降低,邏輯代碼全部都丟給了Presenter,結(jié)果就是Activity只需要負(fù)責(zé)UI交互的按鈕等代碼。
          • 對(duì)于Presenter可以寫(xiě)單獨(dú)的單元測(cè)試代碼,只需要對(duì)Presenter提供的方法測(cè)試即可
          • 如果DataManager變得臃腫龐大了,我們可以分離這些代碼到各自的Presenter中去。

          What problems do we still have?
          現(xiàn)在還有遺留什么問(wèn)題

          Having a single data manager can still be an issue when the codebase becomes very large and complex. We haven’t reached the point where this is a real problem but we are aware that it could happen.

          只有一個(gè)DataManager仍舊是一個(gè)問(wèn)題,尤其是當(dāng)代碼項(xiàng)目比較龐大的時(shí)候,當(dāng)然我們還沒(méi)有到達(dá)這個(gè)龐大的地步,盡管我們知道這個(gè)將來(lái)某天會(huì)發(fā)生。

          It’s important to mention that this is not the perfect architecture. In fact, it’d be naive to think there is a unique and perfect one that will solve all your problems forever. The Android ecosystem will keep evolving at a fast pace and we have to keep up by exploring, reading and experimenting so that we can find better ways to continue building excellent Android apps.

          如果想有個(gè)完美的架構(gòu)解決你所有問(wèn)題是不可能的。TMD Android的整個(gè)生態(tài)圈變化太快,又TM的不標(biāo)準(zhǔn),就導(dǎo)致我們不斷的去探索探索。。。以致于去找到更吊的方法去做Android apps。

          I hope you enjoyed this article and you found it useful. If so, don’t forget to click the recommend button. Also, I’d love to hear your thoughts about our latest approach.

          希望讀了之后對(duì)我們的最新解決方案能有些建議想法。

          【本文翻譯的目的是在閑暇時(shí)間,研究新技術(shù),用通俗技術(shù)語(yǔ)言寫(xiě)給自己看,便于日后方便查閱為目】
          原文:https://medium.com/ribot-labs/android-application-architecture-8b6e34acda65
          MVP介紹:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0425/2782.html
          RxAndroid:https://github.com/ReactiveX/RxAndroid
          Eventbus:https://github.com/greenrobot/EventBus

          posted on 2015-12-18 13:07 paulwong 閱讀(652) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): ANDROID

          主站蜘蛛池模板: 阿勒泰市| 铜鼓县| 武定县| 同德县| 曲水县| 安泽县| 吉木萨尔县| 永寿县| 仙居县| 仪陇县| 玉门市| 航空| 闻喜县| 南靖县| 莱阳市| 蓝山县| 襄樊市| 沭阳县| 永昌县| 安阳县| 信阳市| 托克逊县| 香港 | 拉萨市| 铜山县| 花莲县| 南澳县| 萝北县| 乐至县| 涿鹿县| 汨罗市| 仙居县| 武隆县| 凤城市| 栾川县| 从化市| 满城县| 平湖市| 抚松县| 砀山县| 德清县|