原文鏈接:Architecting Android…The clean way?
在過去的幾個(gè)月里,在Tuenti和同事@pedro_g_s和@flipper83(順帶一提,這兩個(gè)都是Android開發(fā)高手)探討之后,我覺得是時(shí)候?qū)懸黄P(guān)于構(gòu)建Android應(yīng)用架構(gòu)方面的文章了。
本文的目的在于告訴你我過去幾個(gè)月腦中的一些想法,還有我研究和實(shí)現(xiàn)后總結(jié)的一些資料。
Getting Started
我們知道編寫高質(zhì)量的軟件很困難也很復(fù)雜:它不僅僅是符合需求,還要有魯棒性、可維護(hù)、可測試和足夠靈活,以適應(yīng)代碼增長和改變的需要。因此,我們提出了“the clean architecture“(清爽架構(gòu))的概念,它對(duì)于開發(fā)任何軟件應(yīng)用都是一個(gè)絕佳的方案。
這個(gè)概念很簡單:clean architecture主張通過一系列的實(shí)踐,來構(gòu)建一個(gè)具備下面特性的系統(tǒng):
- Independent of Frameworks(框架獨(dú)立)
- Testable(可測試)
- Independent of UI(用戶界面獨(dú)立)
- Independent of Database(數(shù)據(jù)庫獨(dú)立)
- Independent of any external agency(所有外部代理獨(dú)立)
這并不是說一定只用這4層(如圖所示),因?yàn)檫@張?jiān)韴D只是要讓你考慮Dependency Rule(依賴規(guī)則):源代碼中的依賴只能指向里面,內(nèi)圈代碼必須對(duì)外圈的一無所知。
下面有一些相關(guān)的詞匯能讓你更好地熟悉和理解該方法:
- Entities:應(yīng)用的業(yè)務(wù)對(duì)象所在
- Use Cases:協(xié)調(diào)數(shù)據(jù)從entities中流進(jìn)流出。也可以叫Interactors。(備注:用例,就是一系列的步驟,典型地說就是用戶為達(dá)到一種目的,與系統(tǒng)進(jìn)行的交互)
- Interface Adapters:轉(zhuǎn)換數(shù)據(jù)以方便use cases和entities使用。Presenters和Controllers都屬于此處
- Frameworks and Drivers:細(xì)節(jié)處理,比如UI、tools、frameworks等等
Our Scenario
我會(huì)從一個(gè)簡單的方案開始講起:一個(gè)簡單的app,顯示一個(gè)朋友或用戶列表,數(shù)據(jù)從云端獲取;當(dāng)點(diǎn)擊任意一項(xiàng),會(huì)打開一個(gè)新界面,顯示用戶詳情。
我錄制了一個(gè)視頻,這樣你就可以對(duì)我談?wù)摰臇|西有個(gè)大概了。
Android Architecture
我們的目標(biāo)是:通過讓業(yè)務(wù)邏輯對(duì)外部世界一無所獲來進(jìn)行解耦,這樣,業(yè)務(wù)邏輯就可以毫無依賴地使用外部元素進(jìn)行測試了。為了達(dá)到該目的,我的解決方案是劃分項(xiàng)目成3個(gè)不同的層次,每個(gè)層都有各自的功能,并且各自獨(dú)立工作。
值得一提的是,每個(gè)層使用各自的數(shù)據(jù)模型(data model),因?yàn)檫@種獨(dú)立是可達(dá)的(你可以在代碼中有一個(gè)data mapper,它用來完成數(shù)據(jù)的轉(zhuǎn)換,如果你不想在整個(gè)應(yīng)用中交叉使用models的話是要支付一定的代價(jià)的)
看看下面的圖就知道了:
注意:我沒有使用任何外部類庫(除了使用gson來解析json數(shù)據(jù),使用junit、mockito、robolectric和espresso來測試)。這是為了讓示例代碼更清晰點(diǎn)。不管如何,你可以毫不猶豫地添加ORM進(jìn)行數(shù)據(jù)存儲(chǔ)或者任何依賴注入框架或者任何你熟悉的工具和類庫,那樣你的生活很更美好。(記住,不要重新造輪子)
Presentation Layer
這里放置的是關(guān)聯(lián)視圖和動(dòng)畫相關(guān)的邏輯代碼。它不僅僅可以使用MVP模式(Model View Presenter),也可以使用MVC或MVVM。關(guān)于這點(diǎn)我不會(huì)展開來說。在這里,fragments和activities都只是views,里面沒有任何邏輯,當(dāng)然除了UI邏輯。所有需要渲染或展示的東西都在這里進(jìn)行。
本層的Presenters由interactors(use cases)構(gòu)成,在UI線程之外的新線程進(jìn)行工作,然后通過回調(diào)將數(shù)據(jù)提供給view進(jìn)行渲染或展示。
如果你在找一個(gè)使用MVP和MVVM的酷酷的案例,推薦看看我朋友 Pedro Gómez 寫的
Domain Layer
這里是業(yè)務(wù)邏輯層:所有的邏輯都在這里進(jìn)行。環(huán)視整個(gè)android項(xiàng)目,你會(huì)發(fā)現(xiàn)所有的interactors(use cases)都是在這里實(shí)現(xiàn)的。
該層屬于純java模塊,沒有一丁點(diǎn)的android依賴。所有的外部組件(components)都是以接口形式跟business objects聯(lián)系。

Data Layer
應(yīng)用所需的所有數(shù)據(jù)都來自該層,通過一個(gè)UserRepository的實(shí)現(xiàn)(其接口在domain layer),使用一個(gè)存儲(chǔ)庫模式(Repository Pattern),使用這樣的策略:通過一個(gè)工廠依賴于具體的條件,獲取不同的數(shù)據(jù)。
例如,當(dāng)需要通過id獲取user,如果user已經(jīng)存在于緩存中,那么將會(huì)從磁盤中緩存的數(shù)據(jù)源中獲取;否則,從云端獲取,然后再緩存到本地磁盤。
這一切背后的理念是數(shù)據(jù)源對(duì)于客戶端透明,就是說客戶端不用關(guān)心數(shù)據(jù)是從內(nèi)存、磁盤還是云端獲取的,只要知道數(shù)據(jù)終會(huì)被獲取就行了。
注意:在代碼方面,我已經(jīng)實(shí)現(xiàn)了一個(gè)非常簡單和原始的磁盤緩存,使用的是文件系統(tǒng)和android preferences,僅僅是出于學(xué)習(xí)目的。再次提醒,如果已有類庫可以搞定需求就不要重復(fù)造輪子。
Error Handling
這個(gè)話題仍在探討中,如果你可以在這里分享下你的解決方案的話就更棒了。
我的策略是使用回調(diào),因此,假如在data repository有事件發(fā)生,回調(diào)接口有兩個(gè)方法onResponse()和onError()來進(jìn)行處理。最后一個(gè)使用了一個(gè)叫“ErrorBundle”的包裝類來封裝異常:這種方法帶來了一些困境,因?yàn)榇嬖谥粭l直達(dá)顯示層的回調(diào)鏈。代碼可讀性難以保證。
另一方面,我已經(jīng)實(shí)現(xiàn)了一個(gè)事件總線系統(tǒng),如果有錯(cuò)誤發(fā)生會(huì)拋出一個(gè)事件。這個(gè)解決方案類似于GOTO。我的觀點(diǎn)是,如果你訂閱了多個(gè)事件而沒有緊接著處理好的話,你會(huì)丟失一些事件。
Testing
關(guān)注于測試,我根據(jù)各個(gè)層的特點(diǎn)選擇了幾個(gè)解決方案:
- Presentation Layer:使用android instrumentation和espresso進(jìn)行綜合測試和功能測試
- Domain Layer:使用JUnit+mockito進(jìn)行單元測試
- Data Layer:Robolectric(因?yàn)樵搶佑衋ndroid依賴)+ JUnit + mockito進(jìn)行綜合和單元測試
Show me the code
我知道你一定想知道代碼放哪里了,對(duì)吧?諾,這里有個(gè)github鏈接,我代碼都放上面了。關(guān)于其中的文件結(jié)構(gòu),我要說幾句,不同的層表示的使用的模塊如下:
- presentation:屬于android 模塊,代表presenttation層
- domain:屬于java模塊,沒有android依賴
- data:屬于android模塊,數(shù)據(jù)源
- data-test:data層的測試代碼。由于使用Robolectric的一些限制,我不得不獨(dú)立出一個(gè)java模塊
Conclusion
正如Uncle Bob說的 "Architecture is About Intent, not Frameworks" ,我完全同意這種說法。當(dāng)然,達(dá)到目的的途徑有很多且各異(不同的實(shí)現(xiàn)),我們每天都面臨著許許多多的挑戰(zhàn)。但是,通過使用該技術(shù),你可以確保你的應(yīng)用:
- 易于維護(hù)
- 易于測試
- 高內(nèi)聚
- 低耦合
總而言之,我強(qiáng)烈推薦你試一下,然后把你的結(jié)果和經(jīng)歷分享出來,包括你發(fā)現(xiàn)的其他更好的方法:我們知道持續(xù)改進(jìn)是一件非常棒喝積極的事。
我希望本文對(duì)于有所裨益,并且我一如既往地歡迎任何反饋。
Source code
- Clean architecture github repository – master branch
- Clean architecture github repository – releases
Further reading:
- Architecting Android..the evolution
- Tasting Dagger 2 on Android
- The Mayans Lost Guide to RxJava on Android
- It is about philosophy: Culture of a good programmer