原文發表於:Rocko's blog(rocko.xyz)] - MVVM_Android-CleanArchitecturehtml
"Architecture is About Intent, not Frameworks" - Robert C. Martin (Uncle Bob)android
Uncle Bob 的這句話套在 MVVM 上也是適用的, MVVM 也僅僅是架構模式
(Architectural pattern),其有一套本身的理論概念(pattern)而不是規定的具體實現(或 Frameworks)。早以前在知乎上相關問題的回答(android UI設計MVVM設計模式討論?)中也簡單提到過 MVVM 了,M-V-X 的關係如上圖,那麼這一次博主把 Fernando Cejas(android10) 的 Android-CleanArchitecture 項目中的 MVP 實現重構成了用 MVVM 來實現。因此看這篇文章最好是先搞清了 Fernando Cejas(android10) 的 Android-CleanArchitecture sample app 和對應的兩篇文章(見參考)。整個歷程也算比較愉快,沒什麼不良反應,這篇文章理所固然會重點說說 MVVM 的實現、 Data Binding 等相關的東西。那爲何擁抱 MVVM 呢。固然是 Google 推出官方的 data binding 啦,下一次的 Android MVVM 熱潮應該就是 data binding 放出正式版了。git
首先仍是先來講說 分層架構
與 MVC
or M-V-X
之間的關係。分層架構是一種常見的軟件應用架構,在 Java 程序中能夠算是一種應用標準了,一般又叫 N 層架構,而最多見的是 3 層架構,它包含以下 3 層:github
展現層(Presentation tier),也稱爲 UI 層,也就是程序的界面部分。編程
業務層(business logic(domain) tier), 業務層,是最爲核心的一層。設計模式
持久層(Data tier),數據持久層。架構
3 層架構是存在物理上分層概念的,從上往下即展現層、業務層、持久層,也從上往下由上一層依賴下一層。不一樣層之間也是 高內聚低耦合
的體現,層內高內聚,層間低耦合,層
是層內具體工做的高度抽象。低耦合則是依賴倒轉原則體現出來,高層依賴於下層的抽象而不是具體。併發
接下來先說 M-V-X 的鼻祖 MVC,Model–View–Controller (MVC) is a software architectural pattern for implementing user interfaces.,因此 MVC 模式是爲用戶界面設計的,在 3 層架構中,MVC 是屬於展示層的部分,因此 MVVM 做爲 MVC 的演進在與分層架構的關係上也是同樣。常常會看到 3 層架構與 M-V-X 混爲一談的內容,這是不正確的,雖然都是 3 部分的內容可是不能 簡單地 把二者的每一部分對應起來,咱們應該理解爲,在分層架構中 M-V-X 是在展現層(Presentation tier)的應用。這些軟件工程理論的東西就這樣了,都是大師們留下來的東西,不要隨便套上本身的概念。app
Fernando Cejas(android10) 的 Android-CleanArchitecture 項目中也是採用典型的 3 層架構,其中 Presentation 展示層採用了 MVP 模式,若是還未了解過 MVP,能夠看看我以前寫的文章:Android中的MVP。不過 Google 推出官方的 data binding
以後我以爲基本能夠不用採用 MVP 了,在 M-V-X 中, MVP 與 MVVM 算是比較接近的了但 MVP 中的一堆 View 接口也是讓人頭疼的,而擁有 data binding 的 MVVM 則解決了這個問題,因此請大膽擁抱 MVVM。So,下面幾點是當中除 MVVM 外涉及到的東西,MVVM 放到下一節再講。框架
自帶 Dagger
信仰光環者障眼之術開啓, 哈哈,大家看不到接下來的這句話了。。我如今也是持不同意 di 的觀點的人(在 Android 中、、、),能夠看看這場撕逼:依賴注入是否值得?。結果就是我把原項目的依賴注入模塊去掉了,對於測試中的類中的成員變量來講原本就是 Mock 抽象接口,那就直接對接口或抽象類直接 Mock 操做就可,畢竟依賴注入的解耦依然是取決於須要注入的對象的抽象,維護依賴注入模塊(Module)也是負擔,測試代碼中又要多寫一套注入控制的 Dagger Module 代碼。。
先說 AsyncTask,對其已經再也不想吐槽,這麼重要的異步實現,版本間(Android Api)代碼改來改去,又順序又無序、又單線程執行又併發執行、內存泄露、、、。因此對於採用 RxJava 即便不採用函數響應式編程的大概念,用它來替換 AsyncTask 和 Thread + Handler 也是推薦的。此外使用 Rx 後也不須要事件總線的框架了,對於回調監聽直接在數據操做的 Observable 上註冊觀察者便可,相對於事件總線來講是更精準的(單線)的監聽。而事件總線的話則是更加鬆耦合的,出錯的話會更加難排查,這裏就再也不展開了。
我的、團隊喜愛,代碼簡潔了不少可是代碼的邏輯比較很差直觀理解了,原項目也只有 3 處代碼用到,故而去掉了。
此外,對於領域驅動設計(DDD)中 Repository,原項目中把其實現放到了 data 層去實現(接口),形成 data 層會依賴其上一層(domain 業務層),做爲分層架構我的認爲不合適因此從新把它調整了。根據 DDD,我的認爲 Repository 它的存在讓領域層(domain 業務)感受不到數據訪問層的存在,它提供一個相似集合的接口提供給領域層進行領域對象的訪問。Repository 是倉庫管理員,領域層須要什麼東西只需告訴倉庫管理員,由倉庫管理員把東西拿給它,並不須要知道東西實際放在哪。
按照常理,先來講基本概念:
Model,domain model(領域模型)或是數據層表明的數據模型,也能夠理解爲用戶界面須要顯示數據的抽象(數據)
View, 應用的界面
ViewModel,binder 所在之處,是 View 的抽象,對外暴露出公共屬性和命令,是 View 與 Model 的(綁定)鏈接器
此外還有必不可少的一部分:Binder,Android 中也就是 Data binding 了,提供 View 與 Model 的綁定功能。下面是結構圖:
目前 Android 的 data binding 仍是 beta,還只是 one-way
單向綁定,功能上還有所欠缺、控制性也還不強,可是把它寫出來仍是沒問題的。對於 Activity、Fragment 而言僅僅是做爲 Java View 看待,與 XML 對應,因此裏面只有 View 的展示邏輯,此外沒有其它代碼。一個 Activity 或 Fragment(通常都 with XML) 對應一個 ViewModel,對於一個基礎 View(XML)能夠經過繼承對應的 ViewModel 實現重用,本文的代碼也有體現。對於 Activity 和 Fragment 的View 狀態保存恢復也經過 ViewModel 處理。由於 binding 的入口在 Activity 或 Fragment 中,因此爲了方便寫個基類處理 ViewModel 和 Binding 的初始化,而後在 對應的 XML 里加上 ViewModel 的 variable, XML 裏再也不有其它數據對象的 variable。
public abstract class BaseActivity<VM extends ViewModel, B extends ViewDataBinding> extends Activity { private VM viewModel; private B binding; public void setViewModel(@NonNull VM viewModel) { this.viewModel = viewModel; } public VM getViewModel() { if (viewModel == null) { throw new NullPointerException("You should setViewModel first!"); } return viewModel; } public void setBinding(@NonNull B binding) { this.binding = binding; } public B getBinding() { if (binding == null) { throw new NullPointerException("You should setBinding first!"); } return binding; } }
ViewModel 中經過 ObservableField 來達到細粒度的控制,綁定操做都放在 ViewModel 裏,而後 ViewModel 裏能夠有多個 domain 中的 Interator(UseCase) 來獲得 View 須要渲染的數據 Model。對於 ObservableField 的綁定操做和命令操做(Command)都是暴露的,也易於測試。binding 如今缺乏手動在 Java 代碼中註冊通知事件的功能,好比有些 model 的渲染必須經過 Java 代碼來操做的話就須要了,在 Activity(Java View) 中經過向 Binding 註冊通知回調,而目前只能在 XML 中知道,固然目前也能夠本身實現,方法也有多種:接口回調、EventBus、RxBus。。
除了 Persenter 改爲 ViewModel 的邏輯、Repository 的抽象和具體都在 domain 外,其餘部分基本一致,採用的測試也一致。
Clean Architecture:
MVVM_Clean-Architecture tier:
MVVM_Clean-Architecture put all:
Talk is cheap. Show you the code. ↓↓↓
MVVM_Android-CleanArchitecture
須要注意的是 include 標籤的 XML 節點中要使用到根節點中 data 標籤裏設置的 viewModel variable 的話須要這樣設置;
<include layout="@layout/view_retry" bind:viewModel="@{viewModel}"/>
抽象類 ViewModel 中設置了 @Command 和 @BindView 註解,只起到清晰提醒做用。
具體重構更改能夠查看 commit 記錄:MVVM_Android-CleanArchitecture commits。能夠看到 Activity 和 Fragment 的代碼是很清爽的,比 MVP 更清爽,由於 View 的數據渲染操做交給 binder 去處理了。對 Activity 或其對應界面進行 UI 測試的話,Mock 出 model 表明的數據而後傳遞給 ViewModel 中 @BindView 暴露出的方法,而後檢驗視圖對數據的正確顯示就好了,也就是 View 對 Model 作了正確的渲染。
企業應用架構模式
Architecting Android…The clean way? (Android-CleanArchitecture 的文章,譯文略)
Architecting Android…The evolution (Android-CleanArchitecture 的文章,譯文略)
Approaching Android with MVVM
ANDROID DATABINDING: GOODBYE PRESENTER, HELLO VIEWMODEL!
本文源碼:MVVM_Android-CleanArchitecture or Rocko-Android-Demo(-MVVM_Android-CleanArchitecture)