聊聊架構:Easy Clean architecture on Android

2021-12-09 承香墨影

作者簡介

本文由 小鄧子 原創並授權發布,未經原作者允許請勿轉載。

本文主要介紹了,如何構建一個整潔乾淨,並且健壯的 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/

推薦閱讀:

相關焦點

  • 整潔架構(Clean Architecture)的Go微服務: 程序結構
    code blog[18][7]go at google: language design in the service of software engineering[19][8]go microservice with clean architecture: application
  • Android架構學習資料
    MVP,感覺使用Retrofit+RxJava+MVP開發還是非常方便的~由於沒有本地資料庫,在某些界面我去掉了M層,只留了VP,感覺也沒什麼不妥~話說,你用什麼架構呢?~Google官方出品Google官方出品:android-architecture其他的一些文章、Demo、App:文章淺談Andorid開發中的MVP模式  MVP IN ANDROID, PART 1MVP IN ANDROID, PART 2MVP IN ANDROID, PART 3
  • 架構組件之 ViewModel | 中文教學視頻
    更多詳細內容介紹,請訪問以下文檔連結> 架構組件的官方開發者文檔:https://developer.android.google.cn/arch> ViewModel 的文檔: https://developer.android.google.cn/topic/libraries/
  • App 組件化/模塊化—Android 框架組件(Android Architecture Components)使用指南
    熱文導讀 | 點擊標題閱讀Android 高新技術之SVG矢量動畫機制Android高級進階架構系列教程視頻分享通用的框架準則官方建議在架構 App 的時候遵循以下兩個準則:關注分離其中早期開發 App 最常見的做法是在 Activity 或者 Fragment 中寫了大量的邏輯代碼,導致 Activity 或 Fragment 中的代碼很臃腫,十分不易維護。
  • 最新出爐的值得學習和提升你水平的Android開源App和庫(持續更新)
    首先,重點推薦一個使用乾淨架構的android樣板工程。 它是100%基於Kotlin開發用戶界面和單元測試的。可以作為項目開發和架構的樣板工程和架構android-clean-architecture-boilerplate(***)
  • 【第1989期】前端架構101:整潔(Clean Architecture)架構是歸宿
    正文從這開始~~整潔架構如果你對整潔架構(Clean Architecture)有所了解的話,回想一下我們前幾篇中描述的內容,你會發現整潔架構對前端,對 MVP 來說也是同樣適用的。關於什麼是整潔架構完全可以通過閱讀 Uncle Bob 原版圖書中文版《整潔架構之道》來了解,或者可以通過閱讀他的一個簡短版本博客 The Clean Architecture 一探端倪。但我還是推薦閱讀圖書,圖書全面而且淺顯易懂,沒有和某一門程式語言強行綁定,即使你沒有後端背景也能流暢的通讀下來。
  • Android 中的 MVP 模式(帶實例)
    ; import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.EditText;import android.widget.ProgressBar
  • Android開發必備的「80」個開源庫
    detail/155美團 WebView性能、體驗分析與優化https://tech.meituan.com/WebViewPerf.htmlMVC,MVP 和 MVVM 的圖示http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html不容錯過,最全的 Android 架構合集
  • 5 Safe and Easy-to-Clean Kitchen Items for You and Your Kids
    That’s why we made a list of 5 products that are safe for your little helpers to use and easy to clean after their cooking adventures are over.1.
  • Go 整潔架構模版,建議收藏
    替代方法除了整潔架構之道,洋蔥架構和六邊形架構 ( 埠適配器模式 ) 是類似的。兩者都是基於依賴倒置的原則。埠和適配器模式非常接近於整潔架構之道,差異主要在術語上。類似的項目https://github.com/bxcodec/go-clean-archhttps://github.com/zhashkevych/courses-backend擴展閱讀連結參考資料[1] 原則: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html[2]
  • 簡述3種CQRS架構模式
    這種架構給軟體的查詢端帶來了數量級的性能提升,這是有利的,因為一般系統在讀數據上花費的時間一般比寫數據要更多。最後一種是最複雜的 CQRS 架構。與前面兩種方式相比,事件源存儲數據的思路完全不同。在事件源方法中,我們並不只存儲實體的當前狀態,而且將實體發生的每一個狀態作為快照來存儲。實體並不是以標準化數據的形式保存,而是通過事件的時間戳來保存它們的變更。
  • 《clean architecture》第二部分編程範式讀書筆記
    面向對象編程Chap6.函數式編程總結前言上次土撥鼠水了一篇第一部分的讀書筆記,《clean architecture》第一部分筆記。今天再來水一下第二部分關於編程範式的筆記。這些編程範式的歷史知識與軟體架構有關係嗎?當然有,而且關係相當密切。譬如說多態是我們跨越架構邊界的手段,函數式編程是我們規範和限制數據存放位置與訪問權限的手段,結構化編程則是各模塊的算法實現基礎。 這和軟體架構的三大關注重點不謀而合:功能性、組件獨立性以及數據管理。Chap4.
  • MDH 前端周刊第 18 期:stitches 1、ultra、7GUIs、Clean 架構
    計數器,點一次按鈕 +1溫度轉換器,做攝氏度和華氏度的互轉,挑戰點是雙向數據流機票預定,支持單程和雙程,挑戰點是約束,比如選單程不能設置返程日期,比如去的日期必須比回的日期早計時器,可實時調整時間的時間沙漏,挑戰點是並發
  • App工程搭建:幾種常見Android代碼架構分析
    導語本文算是一篇漫談,談一談關於android開發中工程初始化的時候如何在初期我們就能搭建一個好的架構。
  • 聊聊clean code
    記住原則後,我們開始進入實踐環節,先來看下有哪些促成clean code的常見手段。code review很多大公司會用git的pull request機制來做code review。我們重點應該review什麼?是代碼的格式、業務邏輯還是代碼風格?我想說的是,凡是能通過機器檢查出來的事情,無需通過人。
  • Android JetPack架構篇,一個實戰項目帶你學懂JetPack
    本篇來自 walker不抽菸 的投稿,給大家介紹一下 Android JetPack架構相關知識。一起來看看!希望大家喜歡。/#0架構組件的目的是提供對應用程式體系結構的指導,並為諸如生命周期管理和數據持久化等常見任務提供開發庫。
  • Android官方架構組件:Lifecycle詳解&原理分析
    Paging:分頁庫的設計美學Android官方架構組件Navigation:大巧不工的Fragment管理框架Android官方架構組件Lifecycle:生命周期組件詳解&原理分析概述在過去的谷歌IO大會上,Google官方向我們推出了 Android Architecture Components,其中談到Android組件處理生命周期的問題,
  • Android單元測試與模擬測試詳解
    【威哥說】 測試驅動式編程(Test-Driven-Development)在RoR中已經是非常普遍的開發模式,是一種十分可靠、優秀的編程思想,可是在Android領域中這塊還沒有普及,今天主要聊聊
  • 從零開始學Android架構(一)——什麼是設計模式?
    前言不少人會覺得架構師是一個高大上的崗位,只有技術頂尖的人才能勝任,但其實它並沒有這麼高大上,大部分的架構師,都只是開發經驗非常豐富,並且熱愛學習
  • 一個 2 年 Android 開發者的 18 條忠告
    使用一個恰當的架構你永遠都會慶幸自己從一開始就選擇了一個恰當的架構。你可以使用MVP (Model-View-Presenter)架構,它可以把你的代碼解耦成不同的層便於管理,從而提高代碼的靈活性並極大的減小維護的時間成本。可以參考一個demo項目(注11)。如果覺得很難掌握,可以看看這篇針對初學者的指南(注12)。