原文鏈接:Architecting Android…The clean way?
? ? ? 在過去的幾個(gè)月中我與公司Tuenti的@pedro_g_s和@flipper83(兩位android技術(shù)大牛)兩位同事討論Android應(yīng)用架構(gòu),我決定抽出一點(diǎn)時(shí)間寫一篇關(guān)于Android應(yīng)用架構(gòu)的文章。
? ? ? 通過我過去幾個(gè)月的學(xué)習(xí)和實(shí)踐總結(jié)了一些方法,它們將向你展示如何去實(shí)現(xiàn)這種清晰的Android架構(gòu)。
開始前的一些準(zhǔn)備
? ? ? 我們知道開發(fā)一個(gè)高質(zhì)量的軟件是非常困難和復(fù)雜的,它不僅是為了滿足功能實(shí)現(xiàn),也應(yīng)該是健壯、易維護(hù)、易測(cè)試和適于伸縮構(gòu)建。這就是“清晰架構(gòu)”的由來,這可能是開發(fā)任意軟件程序的最好方法。
? ? ? “清晰架構(gòu)”的概念很簡(jiǎn)單,產(chǎn)品系統(tǒng)中遵循一系列的習(xí)慣原則:
1、與框架分離
2、易測(cè)試
3、與UI分離
4、與數(shù)據(jù)庫分離
5、與其它外部組件分離

上圖中你所看見的不一定非要4個(gè)圓環(huán)來表示,因?yàn)檫@只是原理圖但你應(yīng)該考慮到相關(guān)規(guī)則
源碼依賴只能指向內(nèi)環(huán)而內(nèi)環(huán)不能知道它所在外環(huán)的任何事情。
下面的一些術(shù)語可以讓你明白和了解這是種非常好的架構(gòu)方式:
? ? ? 實(shí)體:他們是程序的業(yè)務(wù)對(duì)象
? ? ? 用例:他們是鏈接數(shù)據(jù)流和實(shí)體間的紐帶,也可叫作Interactor(翻譯成:交互器和耦合器,感覺都不對(duì))
? ? ? 接口適配器:這些適配器為用例和實(shí)體提供相應(yīng)格式的數(shù)據(jù)。Presenter(展現(xiàn)層)和Controller(控制層)都奴屬于這里。
? ? ? 框架和驅(qū)動(dòng):這里是所有的具體實(shí)現(xiàn),如:界面,工具類,框架等等
更加詳盡的解釋參見這篇文章The Clean Architecture和視頻Robert C. Martin - Clean Architecture?(貌似需要翻墻)
我們的方案
? ? ? 我們將從一個(gè)簡(jiǎn)單的案例開始:簡(jiǎn)單的創(chuàng)建一個(gè)app,這個(gè)app顯示一個(gè)從云端取回的朋友或用戶數(shù)據(jù),當(dāng)單擊列表中的任意一個(gè)條目時(shí),然后打開一個(gè)新的窗口顯示這個(gè)用戶的具體細(xì)節(jié)。
? ? ? 給你們看一個(gè)視頻,你們就知道我講的什么:Clean Architecture on Android - Sample App(需要翻墻)
Android架構(gòu)
? ? ? 遵循“關(guān)注分離”的原則來讓內(nèi)環(huán)的業(yè)務(wù)操作不知道外環(huán)的一切,不依賴任何外部元素所以他們易于測(cè)試。
? ? ? 要做到這一點(diǎn),“我的建議是將這個(gè)項(xiàng)目分離成3個(gè)不同的層”,這樣每個(gè)層只關(guān)心自己的目標(biāo)和工作以至獨(dú)立于其它層。
? ? ? 值得一提的是,每個(gè)層只使用它自己的數(shù)據(jù)模塊來達(dá)到獨(dú)立性(你將在代碼中見到那個(gè) data mapper被用來實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換,如果你不在整個(gè)應(yīng)用中跨模塊使用只需小的代價(jià))
這兒是一個(gè)我建議的三層模式:

注意:我沒有使用任何第三方庫(除了解析json數(shù)據(jù)的GSON、用于測(cè)試的junit,mockito、robolectri、espresso)這是為了使這個(gè)示例更加整潔。在你的開發(fā)中,無論如何不要猶豫去添加你所熟悉的存儲(chǔ)數(shù)據(jù)的ORMs框架、依賴注入框架、任何工具、第三方庫,這樣在編寫整個(gè)示例過程中可以省掉不少功夫(記?。褐貜?fù)造輪子不是個(gè)好習(xí)慣)。
展現(xiàn)層(Presentation Layer)
? ? ? ?這兒是視圖和動(dòng)畫相關(guān)邏輯呈現(xiàn)的地方,它就是 Model View Presenter模式的一部分(參見:MVP),但你可以使用其它模式像MVC或MVVM。我不會(huì)進(jìn)一步闡述他們,但這里的Fragments 與Activities僅僅是一個(gè)Views,這里不會(huì)有邏輯存在于UI邏輯中,并且這里是視圖渲染填充的地方。這層中的Presenters結(jié)合interactors (用例)在UI線程之外的新線程中執(zhí)行任務(wù)隨后使用回調(diào)函數(shù)傳參的形式來渲染視圖。

如果你想要一個(gè)很酷的MVP和MVVM使用例子,你可以查看我朋友Pedro Gómez的Effective Android UI。
領(lǐng)域?qū)樱?b>Domain Layer)
這里是業(yè)務(wù)規(guī)則:所有邏輯發(fā)生在這層. 對(duì)于Android 項(xiàng)目你也會(huì)在這里見到所有的interactors(用例)實(shí)現(xiàn)。
這層是一個(gè)純java模塊而不是其它Android依賴。所有外部組件使用接口連接業(yè)務(wù)對(duì)象

數(shù)據(jù)層(Data Layer)
? ? ? 應(yīng)用需要的所有數(shù)據(jù)都來自這層,通過一個(gè)UserRepository實(shí)現(xiàn)(這個(gè)接口在領(lǐng)域?qū)樱?,它使用一個(gè)Repository Pattern和一個(gè)策略相結(jié)合,通過一個(gè)工廠,依賴某些條件來獲取不同的數(shù)據(jù)源。例如通過一個(gè)id來獲取用戶,隨后獲取該用戶在磁盤上的緩存數(shù)據(jù),如果該用戶存在的話,否則將從云端檢索數(shù)據(jù)隨后保存在磁盤緩存中。
? ? ? 這個(gè)背后的理念是所有數(shù)據(jù)對(duì)于客戶端來說都是透明的,它并不關(guān)心來自內(nèi)存、磁盤、還是云端的數(shù)據(jù)是怎樣傳遞和獲取的。

注意:我的代碼實(shí)現(xiàn)非常簡(jiǎn)單,使用文件系統(tǒng)和Android Preferences作為磁盤緩存,這是為了達(dá)到學(xué)習(xí)的目的。重申:如果已有第三方庫能勝任這種工作就不要重復(fù)的造輪子。
錯(cuò)誤處理(Error Handling)
? ? ? 這總是一個(gè)值得討論的話題,如果你有更好的解決方案請(qǐng)共享在這兒。
? ? ? 我的策略是使用回調(diào)接口, 以數(shù)據(jù)存儲(chǔ)響應(yīng)為例,回調(diào)接口中有onResponse()和onError()兩個(gè)方法。這里封裝了一個(gè)叫“ErrorBundle”異常處理類:這種方法也帶來一些困難,因?yàn)檫@兒是一個(gè)回調(diào)鏈直接將錯(cuò)誤拋給展現(xiàn)層(Presentation Layer)。代碼可讀性不太好。
? ? ? 另外,我的主張是實(shí)現(xiàn)一個(gè)事件總線系統(tǒng)去拋事件當(dāng)某些錯(cuò)誤發(fā)生時(shí),但這類似使用GOTO。當(dāng)你訂閱幾個(gè)事件時(shí),如果你不善于控制處理它們,你會(huì)感到很困惑。
測(cè)試(Testing)
? ? ? 對(duì)于測(cè)試,根據(jù)不同層我選擇了幾個(gè)不同的方案:
? ? ? 展現(xiàn)層(Presentation Layer):使用android instrumentation、espresso集成測(cè)試、功能性測(cè)試。
? ? ? 領(lǐng)域?qū)樱―omain Layer):使用JUnit+mockito(java模擬測(cè)試框架)進(jìn)行單元測(cè)試
? ? ? 數(shù)據(jù)層(Data Layer):Robolectric(因?yàn)檫@層有Android依賴)+junit+mockito進(jìn)行集成和單元測(cè)試
我的代碼(Show me the code)
? ? ?我知道你在想我的代碼在哪里,沒錯(cuò)?這里是github鏈接代碼,在里邊你將知道我是怎么做的,關(guān)于項(xiàng)目結(jié)構(gòu)不得不提一句,使用模塊來區(qū)分不同的層:
? ? 展現(xiàn)層(presentation):它是一個(gè)Android module,它代表展現(xiàn)層。
? ? 領(lǐng)域?qū)樱╠omain):一個(gè)純java對(duì)象模塊而不是 Android Dependencies
? ? 數(shù)據(jù)層(data):一個(gè)Android Module,所有數(shù)據(jù)都從這里檢索
? ? data-test:為數(shù)據(jù)層提供測(cè)試,使用Robolectric時(shí)的某些局限性,不得不在一個(gè)單獨(dú)的java模塊中使用它
結(jié)論(Conclusion)
正如鮑勃叔說,“Architecture is About Intent, not Frameworks”,我完全同意這個(gè)說法當(dāng)然這兒有許多不同的做法(不同的實(shí)現(xiàn))我非常肯定,你和我一樣每天面臨很多挑戰(zhàn),但使用這些技術(shù),你開發(fā)的軟件將:
? ? ? 1、易于維護(hù)(Easy to maintain)
? ? ? 2、易于測(cè)試(Easy to test)
? ? ? 3、內(nèi)聚性強(qiáng)(Very cohesive)
? ? ? 4、解耦(Decoupled)
我強(qiáng)烈建議你嘗試并分享你的結(jié)果和體驗(yàn),也許你能找到其它工作得更好的方法:我們知道持續(xù)改進(jìn)是一件非常好的事。
我希望這篇文章能幫到你,非常歡迎并期待你的反饋!
源碼(Source code):
? ? ? ?1、Clean architecture github repository – master branch
? ? ? ?2、Clean architecture github repository –?releases
下載源碼的朋友注意咯:
? ? ? ? 請(qǐng)進(jìn)入第二個(gè)鏈接去下載:我下載的v0.5.0版,其它版本原作者在里邊使用了Dagger 2注解框架,如果你沒有使用過dagger 2, 當(dāng)你在看源碼的時(shí)候可能很茫然(比如說我)
? ? ? ? 我用UML的形式分析了下作者的這個(gè)源碼(v0.5.0版)并把圖上傳在這里:希望能幫助你快速閱讀?
延伸閱讀(Further reading)
? ? ? ?1、Architecting Android..the evolution
? ? ? ?2、Tasting Dagger 2?on Android
? ? ? ?3、The Mayans Lost Guide to RxJava on Android
? ? ? ?4、It is about philosophy: Culture of a good programmer
鏈接和資源(Links and Resources)
? ? ? 1、The clean architecture by Uncle Bob
? ? ? 2、Architecture is about Intent, not Frameworks
? ? ? 3、Model View Presenter