本文由 小鄧子 原創並授權發布,未經原作者允許請勿轉載。
本文主要介紹了,如何構建一個整潔乾淨,並且健壯的 Android 架構。寫的非常好,希望大家會喜歡。
小鄧子 的博客地址:
http://www.jianshu.com/p/3edcf85539a6
在我這幾年的學習和成長中,深刻的意識到搭建一個 Android 應用架構是件非常痛苦的事,它不僅要滿足不斷增長的業務需求,還要保證架構自身的整潔,這讓事情變得非常具有挑戰,但我們必須這樣做,因為健壯的 Android 架構是一款優秀 APP 的基礎。
本文的代碼示例可以從 Github 中獲得,倉庫地址是:
https://github.com/SmartDengg/android-easy-cleanarchitecture
Why we need an architecture?Android 入門要求始終不高,因為 Android Framework 會幫我們做很多事,甚至不需要通過深入的學習就能寫出一個簡單的 APP ,比如說在 Activity 或 Fragment 中擺放幾個 View 用來展示到屏幕上,後臺耗時任務放在 Service 中執行,組件之間使用 Broadcast 傳遞數據,由此看來 「人人都能成為 Android 工程師」 ,真的是這樣嗎?
當然不是!!!
如果我們如此天真的開始編程,遲早會為此付出代價。那些依賴關係混亂,靈活性不夠高的代碼將會成為我們最大的阻礙,任由發展的後果就是,導致項目一片狼藉,我們很難加入新的功能,只能對它進行重構甚至推翻重做。在開始編程前,我們不應該低估一個應用程式的複雜性,應該將你的APP看做一個擁有前端,後端和存儲特性的複雜系統。
另外,在軟體工程領域,始終都有一些值得我們學習和遵守的原則,比如:單一職責原則,依賴倒置原則,避免副作用 等等。Android Framework 不會強制我們遵守這些原則,或者說它對我們沒有任何限制,試想那些耦合緊密的實現類,處理大量業務邏輯的 Activity 或 Fragment ,隨處可見的 EventBus ,難以閱讀的數據流傳遞和混亂的回調邏輯等等,它們雖然不會導致系統馬上崩潰,但隨著項目的發展,它們會變得難以維護,甚至很難添加新的代碼,這無疑會成為業務增長的可怕障礙。
所以說,對於開發者們來講,一個好的架構指導規範,至關重要。
從事 Android 工作以來,我始終認為我們能將 APP 做的更好,我也遇到過很多好的壞的軟體設計,自己也做過很多不同的嘗試,我不斷地吸取教訓並做出改變,直到我遇到了 Clean Architecture ,我確定這就是我想要的,我決定使用它。
本文的目標是分享我使用 clean Architecture 構建項目時所收穫的經驗,希望能夠為你的項目改進帶來靈感。
Avoid God Activity我想你一定見過這樣的萬能 Activity ,它們無所不能:
你在 Android 世界裡面加入了業務代碼,你在 BaseActivity 中定義了所有子類可能用到的變量,它看起來就像一個萬能 「上帝」 。
某一天你良心發現,或是出於其他原因,已經無法再添加代碼了,於是為它寫了很多幫助類,看起來就像這樣:
不經意間,你已經埋下了黑色的炸彈。
看上去,業務邏輯被轉移到了幫助類中,Activity中的代碼減少了,不再那麼臃腫,幫助類緩解了「萬能類」的壓力,但隨著項目的成長,業務的擴大,同時這些幫助類也變多,那個時候又要按照業務繼續拆分它們, APIHelperThis 、 APIHelperThat 等等。原來的問題又出現了,測試成本還在,維護成本好像又增加了,那些混亂並且難以復用的程序又回來了,我們的努力好像都白費了。
然而你寫這個萬能類的初衷是什麼,想快捷、方便的使用一些功能函數嗎,尤其希望在子類中能夠很快的拿到。
無論什麼理由這種創造 「上帝類」 的做法都應該儘量避免,我們不應該把重點放在編寫那些大而全的類,而是投入精力去編寫那些易於維護和測試的低耦合類,如果可以的話,最好不要讓業務邏輯進入純淨的 Android 世界,這也是我一直努力的目標。
Clean architecture and The Clean rule這種看起來像 「洋蔥」 的環形圖就是 Clean Architecture ,不同顏色的 「環」 代表了不同的系統結構,它們組成了整個系統,箭頭則代表了依賴關係,
關於它的組成細節,在這裡就不做深入的介紹了,因為有太多的文章講的比我好,比我詳盡。另外值得一提的是 Architecture 是面向軟體設計的,它不應該做語言差異,而本文將主要講述如何結合 Clean Architecture 構建你的 Android 應用程式。
在使用 clean 架構搭建項目前,我閱讀了大量的文章,並付諸了很多實踐,我的收穫很大,經驗和教訓告訴我一個架構的清晰和整潔離不開這三個原則:
接下來我就分別闡述一下,我對這些原則的理解,以及背後的原因。
分層原則首先,值得一提的是框架不會限制你對應用程式的具體分層,你可以擁有任意的層數,但是在 Android 中通常情況下我會劃分為3層:
接下來,介紹下這三層所應包含的內容。
實現層一句話:實現層就是Android框架層。這個地方應該是 Android framework 的具體實現,它應該包括所有 Android 的東西,也就是說這裡的代碼應該是解決 Android 問題的,是與平臺特性相關的,是具體的實現細節,如,Activity的跳轉,創建並加載 Fragment ,處理 Intent或者開啟 Service 等。
接口適配層接口適配層的目的是連接業務邏輯與框架特定代碼,擔任外層與內層之間的橋梁。
業務邏輯層最重要的是業務邏輯層,我們在這裡解決所有業務邏輯,這一層不應該包含 Android 代碼,應該能夠在沒有 Android 環境的情況下測試它,也就是說我們的業務邏輯能夠被獨立測試,開發和維護,這就是 clean 架構的主要好處。
依賴規則依賴規則與箭頭方向保持一致,外層」依賴「內層,這裡所說的「依賴」並不是指你在gradle中編寫的那些 dependency 語句,應該將它理解成 「看到」 或者 「知道」 ,外層知道內層,相反內層不知道外層,或者說外層知道內層是如何定義抽象的,而內層卻不知道外層是如何實現的。如前所述,內層包含業務邏輯,外層包含實現細節,結合依賴規則就是:業務邏輯既看不到也不知道實現細節 。
對於項目工程來講,具體的依賴方式完全取決於你。你可以將他們劃入不同的包,通過包結構來管理它們,需要注意的是不要在內部包中使用外部包的代碼。使用包來進行管理十分的簡單,但同時也暴露了致命的問題,一旦有人不知道依賴規則,就可能寫出錯誤的代碼,因為這種管理方式不能阻止人們對依賴規則的破壞,所以我更傾向將他們歸納到不同的 Android module 中,調整 module 間的依賴關係,使內層代碼根本無法知道外層的存在。
另外值得一提的是,儘管沒人能夠阻止你跳過相鄰的層去訪問其它層的代碼,但我還是強烈建議只與相鄰層進行數據訪問。
抽象原則在依賴原則中,我已經暗示了抽象原則,順著箭頭方向由兩邊朝中間移動時,東西就越抽象,相對的,朝兩邊移動時,東西就越具體。這也是我一直反覆強調的,內圈包含業務邏輯,外圈包含實現細節。
接下來我會用一個例子來解釋抽象原則:
在內層定一個抽象接口 Notification ,一方面,業務邏輯可以直接使用它來向用戶顯示通知,另一方面,我們也可以在外層實現該接口,使用Android framework提供的 NotificationManager來顯示通知。業務邏輯使用的只是通知接口,它不了解實現細節,不知道通知是如何實現的,甚至不知道實現細節的存在。
這很好演示了如何使用抽象原則。當抽象與依賴結合後,就會發現使用抽象通知的業務邏輯看不到也不知道使用Android通知管理器的具體實現,這就是我們想要的:業務邏輯不會注意到具體的實現細節,更不知道它何時會改變 。抽象原則很好的幫我們做到了這一點。
Apply on Android按照上面提到的分層原則,我把項目分為了三層,也就是說它有三個 Android module ,如下圖所示:
在 Domain 中定義業務邏輯規則,在 UI 中實現界面交互, Model 則是業務邏輯的具體實現方式( Android framework )。箭頭方向代表依賴關係,內層抽象,外層具體,外層知道內層,內層不了解外層。
具體到 Android 中的框架結構如下圖所示:
你可能有些困惑,為什麼 Domain 指向 Data ?既然 Domain 包含業務邏輯,它就應該是應用程式的中心,它不應該依賴 Model ,按照前面提到的原則, Domain 是抽象的, Model 是具體的,應該是 Model 依賴 Domain ,而不是 Domain 依賴 Model 。
其實這很好理解,也是我始終強調的,這裡所說的「依賴」並不是指配置在 gradle 中的 dependency ,你應該將它理解為 「知道」,「了解」,「意識」 ,圖中的箭頭代表了調用關係,而非模塊間的依賴關係。我們應該能夠理解:抽象是理論,依賴是實踐,抽象是應用的邏輯布局,依賴是應用的組合策略。對於框架結構的理解,我們應該跳出代碼層面,不要局限在慣性思維中,否則很快就會陷入邏輯混亂的怪圈。
與調用關係對應的就是數據流的走向:
在 App 中接受用戶的行為,根據 domain 中定義的業務規則,訪問 model 中的真實數據,然後依次返回,最終更新界面,這就是一個完整的數據流走向。
為了更方便理解,我對項目進行了簡單的拆解,並在圖中加上了類的用例描述,它看起來就像這樣:
對上圖所表示內容做一下總結:
首先,項目被分為三層:
其次,更細節的子模塊劃分:
UI視圖,包含所有的 Android 控制項,負責UI展示。
Presenter處理用戶交互,調用適當的業務邏輯,並將數據結果發送到UI進行渲染。也就是說 Presenter 將擔任著接口適配層的責任,連接 Android 實現和業務邏輯,負責數據的傳遞和回調。
Entity實體,也就是業務對象,是應用的核心,它代表了應用的主要功能,你應該能夠通過查看這些應用來判斷這款應用的功能,例如,如果你有一個新聞應用,這些實體將是體育、汽車或者財經等實體類。
Use case用例,即 interactor ,也就是業務服務,是實體的擴展,同時也是業務邏輯的擴展。它們包含的邏輯並不僅針對於一個實體,而是能處理更多的實體。一個好的用例,應該可以用通俗的語言來描述所做的事情,例如,轉帳可以叫做 TransferMoneyUseCase 。
Repository抽象的核心,它們應該被定義為接口,為 UseCase 提供相應的輸入和輸出,能夠直接對實體進行 CRUD 等操作。或者它們可以暴露一些更複雜的操作行為,如過濾,聚合等,具體的實現細節可以由外層來實現。
DB&API資料庫和 API 的實現都應該放在這裡,比如上面示例中,可以將 DAO,Retrofit,json 解析等放在這裡。它們應該能夠實現在 Repository 中定義的接口,是具體的實現細節,能夠對實體類進行直接操作。
Show code你可以像前面 UML 圖中演示的那樣,組合你的 MVPView 和 MVPPresenter ,讓它們更容易被管理和維護。
首先定義 BaseView 和 BasePresenter ,在 BaseView 中我是用了 RxJava 的 Observable作為結果類型。
假設你有一個根據城市ID獲取該城市已上映電影的需求,那麼你可以這樣組合你的 MovieView和 MoviePresenter 接口:
泛型的加入,有效保證了數據的類型安全。
接下來實現你自己的 XXXPresenter 和 XXXView 接口的實現類,就像這樣:
關於示例中的 UseCase.Request來自於Clean Architecture: Dynamic Parameters in Use Cases:在 XXXUseCase 中創建靜態內部類 Request 作為動態請求參數的容器。其實這很好理解,而且也完全正確,因為UseCase就是你定義業務規則的地方,把 業務(請求)條件與業務規則 定義組合在一起不僅容易理解也更方便管理。不過我會在下篇文章中介紹另一種動態參數方式,也是我一直在使用的。
總結我相信你和我一樣,在搭建框架的過程中遭遇著各式各樣的挑戰,從錯誤中吸取教訓,不斷優化代碼,調整依賴關係,甚至重新組織模塊結構,這些你做出的改變都是想讓架構變得更健壯,我們一直希望應用程式能夠變得易開發易維護,這才是真正意義上的團隊受益。
不得不說,搭建應用架構的方式多種多樣,而且我認為,沒有萬能的,一勞永逸的架構,它應該是不斷迭代更新,適應業務的。所以說,你可以按照文中提供的思路,嘗試著結合業務來構建你的應用程式。
另外值得一提的是,如果你想做的更好,可以為你的項目加入模板化,組件化等策略,因為並沒有說一個項目只能使用一種框架結構。
本文參考:
Clean Architecture。https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
Clean Architecture:Dynamic Parameters in Use Cases。 https://fernandocejas.com/2016/12/24/clean-architecture-dynamic-parameters-in-use-cases/
推薦閱讀: