Android 架構設計 --- 關於 MVVM 模式的探討

###1、MVP模式優缺點javascript

在說MVVM以前,簡單回顧一下MVP分層,MVP總共分紅三層:html

  • a 、View: 視圖層,對應xml文件與Activity/Fragment;
  • b 、Presenter: 邏輯控制層,同時持有View和Model對象;
  • c 、Model: 實體層,負責獲取實體數據。

MVP模式序列圖

MVP模式有其很大的優勢java

  • 1.解耦合,業務邏輯和視圖分離;
  • 2.項目代碼結構(文件夾)清晰,一看就知道什麼類幹什麼事情;
  • 3.便於單元測試(其實仍是第一點);
  • 4.協同工做(例如在設計師沒出圖以前能夠先寫一些業務邏輯代碼或者其餘人接手代碼改起來比較容易);

可是也有美中不足的部分,MVP模式的缺點以下:react

  • 1.Presente層與View層是經過接口進行交互的,接口粒度很差控制。粒度過小,就會存在大量接口的狀況,使代碼太過碎版化;粒度太大,解耦效果很差。由於View定義的方法並不必定所有要用到,可能只是後面要用到先定義出來(後面要不要刪也未知),並且若是後面有些方法要刪改,Presenter和Activity都要刪改,比較麻煩;
  • 2.V層與P層仍是有必定的耦合度。一旦V層某個UI元素更改,那麼對應的接口就必須得改,數據如何映射到UI上、事件監聽接口這些都須要轉變,牽一髮而動全身。若是這一層也能解耦就更好了。android

  • 3.複雜的業務同時也可能會致使P層太大,代碼臃腫的問題依然不能解決,這已經不是接口粒度把控的問題了,一旦業務邏輯愈來愈多,View定義的方法愈來愈多,會形成Activity和Fragment實現的方法愈來愈多,依然臃腫。git

###2、MVVM模式github

####2.一、數據的雙向綁定
OK,如今開始介紹MVVM,MVVM模式不是四層,同MVP同樣也是三層,可是我不一樣意MVVM是MVP的升級版,兩者有相同的地方,可是MVP的一些優勢,MVVM也沒法取代,MVVM的三層模型以下:網絡

Model :負責數據實現和邏輯處理,相似MVP。
View : 對應於Activity和XML,負責View的繪製以及與用戶交互,相似MVP。
ViewModel : 建立關聯,將model和view綁定起來。如此以後,咱們model的更改,經過viewmodel反饋給view。(view的xml佈局文件,通過特定的編寫,編譯工具處理後,生成的代碼會接收viewmodel的數據通知消息,自動刷新界面)。架構

能夠看到,MVVM模式的最大亮點是雙向綁定app

單向綁定上,數據的流向是單方面的,只能從代碼流向UI;雙向綁定的數據流向是雙向的,當業務代碼中的數據改變時,UI上的數據可以獲得刷新;當用戶經過UI交互編輯了數據時,數據的變化也能自動的更新到業務代碼中的數據上。對於雙向綁定,恰好可使用DataBinding,DataBinding是一個實現數據和UI綁定的框架,是構建MVVM模式的一個關鍵的工具。因此Android中實現MVVM就方便多了,IOS中還要使用block回調,或者使用reactiveCocoa庫。

####2.二、DataBinding基本用法

#####- Gradle配置

只要在Gradle中的android域裏面,將dataBinding打開就OK了。

#####- 建立實體類

public class User {
    private String name;
    private String age;

    public void onItemClick(View pView) {
        Toast.makeText(pView.getContext(), getName(), Toast.LENGTH_SHORT).show();
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User(String name, String age) {
        this.name = name;
        this.age = age;
    }
}複製代碼

實現綁定的話,佈局編寫和傳統的xml有區別

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="user"
            type="mvvm.wangjing.com.mvvm.User.User" />
    </data>
    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="mvvm.wangjing.com.mvvm.MainActivity">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:onClick="@{user.onItemClick}"
            android:text="@{`My name is `+  user.name+`  I'm   `+user.age+`  years old `}" />
    </RelativeLayout>
</layout>複製代碼

使用DataBinding後,佈局都是以 標籤做爲根節點,這個佈局 最終會生成一個Binding類,命名規則是:單詞首字母大寫,移除下劃線,並在最後添加上Binding。我這裏是activity_main.xml,因此生成的是ActivityMainBinding。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User("Looperjing", "20");
        viewDataBinding.setUser(user);
    }
}複製代碼

把setContentView(R.layout.activity_main)換成DataBindingUtil.setContentView(this, R.layout.activity_main),返回的是生成的綁定類ActivityMainBinding,而後將user進行綁定。運行效果以下。


可是這還不能實現雙向綁定,要實現雙向綁定的話。須要修改咱們的實體類。

public class User extends BaseObservable {

    public ObservableField<String> name = new ObservableField<>();

    public ObservableField<String> age = new ObservableField<>();


    public User(String pName, String pAge) {
        name.set(pName);
        age.set(pAge);
    }

    @Bindable
    public String getName() {
        return name.get();
    }

    public void setName(String name) {
        this.name.set(name);
        notifyPropertyChanged(mvvm.wangjing.com.mvvm.BR.name);
    }

    @Bindable
    public String getAge() {
        return age.get();
    }

    public void setAge(String age) {
        this.age.set(age);
    }

    public void onItemClick(View pView) {
        Toast.makeText(pView.getContext(), name.get(), Toast.LENGTH_SHORT).show();
        setName("June");
    }

}複製代碼

用 public ObservableField name = new ObservableField<>()這種方式來建立屬性,ObservableField的做用是,當咱們實體類中的值發生改變時會自動通知View刷新。用 name.get()獲取屬性值,用name.set()設置屬性值。若想改變一個字段,須要該字段的get方法添加上@Bindable註解,而後給該字段的set方法加上 notifyPropertyChanged(mvvm.wangjing.com.mvvm.BR.name),上面的代碼就演示了點擊View的時候,修改name的值。關於dadabinding更高級的用法見: 戳我

對於ObservableField這些字段是能夠稍微作一下分類和包裹的。好比說可能一些字段是綁定到控件的一些Style屬性上(如長度、顏色、大小),對於這類針對View Style的的字段能夠聲明一個ViewStyle類包裹起來,這樣整個代碼邏輯會更清晰一些,否則閱讀性較差。而對於其餘一些字段,好比說title、imageUrl、name這些屬於數據源類型的字段,這些字段也叫數據字段,是和業務數據和邏輯息息相關的,這些字段能夠放在一塊。

上面演示了DataBinding是如何雙向綁定的,這個是實現MVVM模式的中ViewModel的關鍵部分。

2.二、Android中的MVVM模式

####a、View層
view層就是xml和Activity

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="model"
            type="mvvm.wangjing.com.mvvm.User.UserViewModel" />
    </data>
    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="mvvm.wangjing.com.mvvm.MainActivity">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:onClick="@{model.onItemClick}"
            android:text="@{`My name is `+  model.user.name+`  I'm   `+model.user.age+`  years old `}" />
    </RelativeLayout>
</layout>複製代碼

請注意 ,此次 中導入的是UserViewModel,這也就是MVVM的VM層,當Model業務數據發生變化時候,通知UI更新,UI更新的時候,通知Model發生變化。

<data>
        <variable name="model" type="mvvm.wangjing.com.mvvm.User.UserViewModel" /> </data>複製代碼
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        UserViewModel userViewModel=new UserViewModel(this,viewDataBinding);
    }
}複製代碼

能夠發現,View層作的就是和UI相關的工做,咱們只在XML、Activity和Fragment寫View層的代碼,View層不作和業務相關的事,也就是咱們在Activity不寫業務邏輯和業務數據相關的代碼,更新UI經過數據綁定實現,儘可能在ViewModel裏面作。

####b、ViewModel層

public class UserViewModel {

    //注意,這裏都須要定義成public,不然這個字段讀取不到

    public User user;

    public ActivityMainBinding mainBinding;

    public Activity activity;


    public UserViewModel(Activity pActivity, ActivityMainBinding pMainBinding) {
        this.activity = pActivity;
        this.mainBinding = pMainBinding;
        mainBinding.setModel(this);
        init();
    }

    private void init() {
       user=new User("LooperJing","20");
    }

    public void onItemClick(View pView) {
        Toast.makeText(pView.getContext(), "通知Medel層,異步請求,獲取用戶信息", Toast.LENGTH_SHORT).show();
    }
}複製代碼

ViewModel僅僅專一於業務的邏輯處理,只作和業務邏輯和業務數據相關的事,UI相關的事情不要寫在這裏面,ViewModel 層不會持有任何控件的引用,更不會在ViewModel中經過UI控件的引用去作更新UI的事情。可是ViewModel可能會改變數據,因爲數據和UI已經綁定到一塊兒了,因此相應的控件上會自動去更新UI。

####c、Model層

Model層就是職責數據獲取的,網絡請求的邏輯在這裏面寫,相似於MVP。因此我以爲ViewModel層能夠持有一個Model的引用,通知Model獲取數據,同時Model在獲取到數據以後,回調通知ViewModel進行數據更改,進而使UI獲得更新。

總結一下:View層的Activity經過DataBinding生成Binding實例,把這個實例傳遞給ViewModel,ViewModel層經過把自身與Binding實例綁定,從而實現View中layout與ViewModel的雙向綁定。若是不引入ViewModel這一層,還會有一個缺點:一個xml中可能會涉及到多個數據對象,那麼咱們只有把這個多個數據對象都引入進來,xml佈局的清晰程度胡降低,經過這種方法,咱們的layout文件中data標籤中只須要引入ViewModel就能夠了,其它的數據對象統一在ViewModel中一併處理。關於三者的協做關係能夠以下圖表示:

from Kelin

####d、MVVM的問題
  第一點:數據綁定使得 Bug 很難被調試。你看到界面異常了,有多是你 View 的代碼有 Bug,也多是 Model 的代碼有問題。數據綁定使得一個位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那麼容易了。
  第二點:對於過大的項目,數據綁定須要花費更多的內存。
 
對於MVVM的理解,發現網絡上,你們在總體上的理解是差很少的,可是細節上有一些不同的地方,好比MVVM的業務邏輯分工不是很明確,有些人會在ViewModel寫,有的會在Moldel中寫,甚至還有一些反對派戳我,認爲MVVM違背的JAVA的分層設計思想,我認爲無論什麼架構設計模塊化,分層思想是基礎,因此這個基本的原則咱們要遵照,對一些新的架構模式,抱着一個客觀的態度去學學習老是能夠提升本身的設計水平。

Please accept mybest wishes for your happiness and success !

推薦閱讀:
Android架構設計---MVP模式第(一)篇之基本認實
Android架構設計---MVP模式第(二)篇,如何減小類爆炸

參考:

tech.meituan.com/android_mvv…

www.jianshu.com/p/2fc41a310…

zhuanlan.zhihu.com/p/23772285?…

github.com/tianzhijiex…

www.open-open.com/lib/view/op…

juejin.im/entry/57d16…

github.com/Kelin-Hong/…

相關文章
相關標籤/搜索