Clean Architecture - 清晰簡潔的Android 應用架構

在我這幾年的學習和成長中,深入的意識到搭建一個Android應用架構是件很是痛苦的事,它不只要知足不斷增加的業務需求,還要保證架構自身的整潔,這讓事情變得很是具備挑戰,但咱們必須這樣作,由於健壯的Android架構是一款優秀APP的基礎。本文的代碼示例能夠從github中得到,倉庫地址是*android-easy-cleanarchitecturehtml

Why we need an architecture?

Android入門要求始終不高,由於Android Framework會幫咱們作不少事,甚至不須要經過深刻的學習就能寫出一個簡單的APP,好比說在ActivityFragment中擺放幾個View用來展現到屏幕上,後臺耗時任務放在Service中執行,組件之間使用Broadcast傳遞數據,由此看來「人人都能成爲Android工程師」,真的是這樣嗎?前端

固然不是!!!java

若是咱們如此天真的開始編程,早晚會爲此付出代價。那些依賴關係混亂,靈活性不夠高的代碼將會成爲咱們最大的阻礙,任由發展的後果就是,致使項目一片狼藉,咱們很難加入新的功能,只能對它進行重構甚至推翻重作。在開始編程前,咱們不該該低估一個應用程序的複雜性,應該將你的APP看作一個擁有前端,後端和存儲特性的複雜系統。android

另外,在軟件工程領域,始終都有一些值得咱們學習和遵照的原則,好比:單一職責原則依賴倒置原則避免反作用等等。Android Framework不會強制咱們遵照這些原則,或者說它對咱們沒有任何限制,試想那些耦合緊密的實現類,處理大量業務邏輯的Activity或Fragment,隨處可見的EventBus,難以閱讀的數據流傳遞和混亂的回調邏輯等等,它們雖然不會致使系統立刻崩潰,但隨着項目的發展,它們會變得難以維護,甚至很難添加新的代碼,這無疑會成爲業務增加的可怕障礙。git

因此說,對於開發者們來說,一個好的架構指導規範,相當重要。github

從事Android工做以來,我始終認爲咱們能將APP作的更好,我也遇到過不少好的壞的軟件設計,本身也作過不少不一樣的嘗試,我不斷地吸收教訓並作出改變,直到我遇到了Clean Architecture,我肯定這就是我想要的,我決定使用它。本文的目標是分享我使用clean Architecture構建項目時所收穫的經驗,但願可以爲你的項目改進帶來靈感。數據庫

Avoid God Activity

多是出於「快速迭代」,因而你集成了這個萬能的Activity,它無所不能:編程

  • 管理自身生命週期(在正確的生命週期中處理任務)
  • 維持UI狀態(配置變動時保存/回覆視圖狀態)
  • 處理Intent(接收和發送正確的Intent)
  • 數據更新(與遠程API同步數據,本地存儲)
  • 線程切換
  • 業務邏輯 ......

甚至突破了全部的約束壁壘:在Android世界裏面加入了業務代碼;在BaseActivity中定義了全部子類可能用到的變量等等。它如今的確就是個「上帝」,方便且萬能的「上帝」!json

隨着項目的發展,它已經龐大到沒法再添加代碼了,因而爲它寫了不少幫助類,你想重構它:後端

god activity

不經意間,你已經埋下了黑色炸彈

看上去,業務邏輯被轉移到了幫助類中,Activity中的代碼減小了,再也不那麼臃腫,幫助類緩解了「萬能類」的壓力,但隨着項目的成長,業務的擴大,同時這些幫助類也變多,那個時候又要按照業務繼續拆分它們,APIHelperThisAPIHelperThat等等。原來的問題又出現了,測試成本還在,維護成本好像又增長了,那些混亂而且難以複用的程序又回來了,咱們的努力好像都白費了。

然而你寫這個萬能類的初衷是什麼,想快捷、方便的使用一些功能函數嗎,尤爲但願在子類中可以很快的拿到。

固然,一部分人會根據不一樣的業務功能分離出不一樣的抽象類,但相對那種業務場景下,它們還是萬能的。

不管什麼理由這種創造「上帝類」的作法都應該儘可能避免,咱們不該該把重點放在編寫那些大而全的類,而是投入精力去編寫那些易於維護和測試的低耦合類,若是能夠的話,最好不要讓業務邏輯進入純淨的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,以下圖所示:

Clean architecture modules

Domain中定義業務邏輯規則,在UI中實現界面交互,Model則是業務邏輯的具體實現方式(Android framework)。箭頭方向表明依賴關係,內層抽象,外層具體,外層知道內層,內層不瞭解外層。

具體到Android中的框架結構以下圖所示:

clean architecture structure

你可能有些困惑,爲何Domain指向Data?既然Domain包含業務邏輯,它就應該是應用程序的中心,它不該該依賴Model,按照前面提到的原則,Domain是抽象的,Model是具體的,應該是Model依賴Domain,而不是Domain依賴Model

其實這很好理解,也是我始終強調的,這裏所說的「依賴」並非指配置在gradle中的dependency,你應該將它理解爲「知道」,「瞭解」,「意識」,圖中的箭頭表明了調用關係,而非模塊間的依賴關係。咱們應該可以理解:抽象是理論,依賴是實踐,抽象是應用的邏輯佈局,依賴是應用的組合策略。對於框架結構的理解,咱們應該跳出代碼層面,不要侷限在慣性思惟中,不然很快就會陷入邏輯混亂的怪圈。

與調用關係對應的就是數據流的走向:

clean architecture data stream

app中接受用戶的行爲,根據domain中定義的業務規則,訪問model中的真實數據,而後依次返回,最終更新界面,這就是一個完整的數據流走向。

爲了更方便理解,我對項目進行了簡單的拆解,並在圖中加上了類的用例描述,它看起來就像這樣:

clean architecture UML

對上圖所表示內容作一下總結:

首先,項目被分爲三層:

  • app:UI,Presenter ...
  • domain:Entity,Use case,Repository ...
  • model:DB,API ...

其次,更細節的子模塊劃分:

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圖中演示的那樣,組合你的MVPViewMVPPresenter,讓它們更容易被管理和維護。

首先定義BaseViewBasePresenter,在BaseView中我是用了RxJavaObservable做爲結果類型。:

public interface BaseView<T> {

  void showData(Observable<T> data);

  void showError(String errorMessage);
}
複製代碼
public interface BasePresenter<V> {

  void attachView(V view);

  void detachView();
}
複製代碼

假設你有一個根據城市ID獲取該城市已上映電影的需求,那麼你能夠這樣組合你的MovieViewMoviePresenter接口:

interface MovieContract {

  interface Presenter<Request, Result> extends BasePresenter<View<Result>> {
    void loadData(Request request);
  }

  interface View<Result> extends BaseView<Result> {
    void showProgress();
  }
}
複製代碼

泛型的加入,有效保證了數據的類型安全

接下來實現你本身的XXXPresenterXXXView接口的實現類,就像這樣:

class MoviePresenterImp implements MovieContract.Presenter<MovieUseCase.Request, List<MovieEntity>> {

  @Override public void attachView(UserContract.View<List<MovieEntity>> view) {
     /*subscribe MovieUseCase and do some initialization*/
  }

  @Override public void detachView() {
    /*unsubscribe MovieUseCase and release resources*/
  }

  @Override public void loadData(MovieUseCase.Request request) {
     /*load data from MovieUseCase*/
  }

}


class MovieActivity extends AppCompatActivity implements MovieContract.View<List<MovieEntity>> {

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    /*also initialize the corresponding presenter*/
  }

  @Override public void showData(Observable<List<MovieEntity>> data) {
    /*show data and hide progress*/
  }

  @Override public void showError(String errorMessage) {
    /*show error message and hide progress*/
  }

  @Override public void showProgress() {
    /*show progress*/
  }
}

複製代碼

關於示例中的UseCase.Request來自於Clean Architecture: Dynamic Parameters in Use Cases:在XXXUseCase中建立靜態內部類Request做爲動態請求參數的容器。其實這很好理解,並且也徹底正確,由於UseCase就是你定義業務規則的地方,把業務(請求)條件業務規則定義組合在一塊兒不只容易理解也更方便管理。不過我會在下篇文章中介紹另外一種動態參數方式,也是我一直在使用的。

總結:

我相信你和我同樣,在搭建框架的過程當中遭遇着各式各樣的挑戰,從錯誤中吸收教訓,不斷優化代碼,調整依賴關係,甚至從新組織模塊結構,這些你作出的改變都是想讓架構變得更健壯,咱們一直但願應用程序可以變得易開發易維護,這纔是真正意義上的團隊受益。

不得不說,搭建應用架構的方式多種多樣,並且我認爲,沒有萬能的,一勞永逸的架構,它應該是不斷迭代更新,適應業務的。因此說,你能夠按照文中提供的思路,嘗試着結合業務來構建你的應用程序。

另外值得一提的是,若是你想作的更好,能夠爲你的項目加入模板化,組件化等策略,由於並無說一個項目只能使用一種框架結構。: )

最後,但願這篇文章可以對你有所幫助,若是你有其餘更好的架構思路,歡迎分享或與我交流。

相關文章
相關標籤/搜索