版權(quán)聲明:本文為LooperJing原創(chuàng)文章,轉(zhuǎn)載請注明出處!
一、MVP模式優(yōu)缺點(diǎn)
在說MVVM之前,簡單回顧一下MVP分層,MVP總共分成三層:
- a 、View: 視圖層,對應(yīng)xml文件與Activity/Fragment;
- b 、Presenter: 邏輯控制層,同時(shí)持有View和Model對象;
- c 、Model: 實(shí)體層,負(fù)責(zé)獲取實(shí)體數(shù)據(jù)。

MVP模式有其很大的優(yōu)點(diǎn)
- 1.解耦合,業(yè)務(wù)邏輯和視圖分離;
- 2.項(xiàng)目代碼結(jié)構(gòu)(文件夾)清晰,一看就知道什么類干什么事情;
- 3.便于單元測試(其實(shí)還是第一點(diǎn));
- 4.協(xié)同工作(例如在設(shè)計(jì)師沒出圖之前可以先寫一些業(yè)務(wù)邏輯代碼或者其他人接手代碼改起來比較容易);
但是也有美中不足的部分,MVP模式的缺點(diǎn)如下:
- 1.Presente層與View層是通過接口進(jìn)行交互的,接口粒度不好控制。粒度太小,就會存在大量接口的情況,使代碼太過碎版化;粒度太大,解耦效果不好。因?yàn)閂iew定義的方法并不一定全部要用到,可能只是后面要用到先定義出來(后面要不要刪也未知),而且如果后面有些方法要刪改,Presenter和Activity都要刪改,比較麻煩;
2.V層與P層還是有一定的耦合度。一旦V層某個(gè)UI元素更改,那么對應(yīng)的接口就必須得改,數(shù)據(jù)如何映射到UI上、事件監(jiān)聽接口這些都需要轉(zhuǎn)變,牽一發(fā)而動全身。如果這一層也能解耦就更好了。
3.復(fù)雜的業(yè)務(wù)同時(shí)也可能會導(dǎo)致P層太大,代碼臃腫的問題依然不能解決,這已經(jīng)不是接口粒度把控的問題了,一旦業(yè)務(wù)邏輯越來越多,View定義的方法越來越多,會造成Activity和Fragment實(shí)現(xiàn)的方法越來越多,依然臃腫。
二、MVVM模式
2.1、數(shù)據(jù)的雙向綁定
OK,現(xiàn)在開始介紹MVVM,MVVM模式不是四層,同MVP一樣也是三層,但是我不同意MVVM是MVP的升級版,二者有相同的地方,但是MVP的一些優(yōu)點(diǎn),MVVM也無法取代,MVVM的三層模型如下:
Model :負(fù)責(zé)數(shù)據(jù)實(shí)現(xiàn)和邏輯處理,類似MVP。
View : 對應(yīng)于Activity和XML,負(fù)責(zé)View的繪制以及與用戶交互,類似MVP。
ViewModel : 創(chuàng)建關(guān)聯(lián),將model和view綁定起來。如此之后,我們model的更改,通過viewmodel反饋給view。(view的xml布局文件,經(jīng)過特定的編寫,編譯工具處理后,生成的代碼會接收viewmodel的數(shù)據(jù)通知消息,自動刷新界面)。
可以看到,MVVM模式的最大亮點(diǎn)是雙向綁定

單向綁定上,數(shù)據(jù)的流向是單方面的,只能從代碼流向UI;雙向綁定的數(shù)據(jù)流向是雙向的,當(dāng)業(yè)務(wù)代碼中的數(shù)據(jù)改變時(shí),UI上的數(shù)據(jù)能夠得到刷新;當(dāng)用戶通過UI交互編輯了數(shù)據(jù)時(shí),數(shù)據(jù)的變化也能自動的更新到業(yè)務(wù)代碼中的數(shù)據(jù)上。對于雙向綁定,剛好可以使用DataBinding,DataBinding是一個(gè)實(shí)現(xiàn)數(shù)據(jù)和UI綁定的框架,是構(gòu)建MVVM模式的一個(gè)關(guān)鍵的工具。所以Android中實(shí)現(xiàn)MVVM就方便多了,IOS中還要使用block回調(diào),或者使用reactiveCocoa庫。
2.2、DataBinding基本用法
- Gradle配置

只要在Gradle中的android域里面,將dataBinding打開就OK了。
- 創(chuàng)建實(shí)體類
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;
}
}
實(shí)現(xiàn)綁定的話,布局編寫和傳統(tǒng)的xml有區(qū)別
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后,布局都是以<layout>標(biāo)簽作為根節(jié)點(diǎn),這個(gè)布局 最終會生成一個(gè)Binding類,命名規(guī)則是:單詞首字母大寫,移除下劃線,并在最后添加上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進(jìn)行綁定。運(yùn)行效果如下。

但是這還不能實(shí)現(xiàn)雙向綁定,要實(shí)現(xiàn)雙向綁定的話。需要修改我們的實(shí)體類。
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<String> name = new ObservableField<>()這種方式來創(chuàng)建屬性,ObservableField的作用是,當(dāng)我們實(shí)體類中的值發(fā)生改變時(shí)會自動通知View刷新。用 name.get()獲取屬性值,用name.set()設(shè)置屬性值。若想改變一個(gè)字段,需要該字段的get方法添加上@Bindable注解,然后給該字段的set方法加上 notifyPropertyChanged(mvvm.wangjing.com.mvvm.BR.name),上面的代碼就演示了點(diǎn)擊View的時(shí)候,修改name的值。關(guān)于dadabinding更高級的用法見:[戳我](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2992.html
http://blog.csdn.net/listen2code/article/details/53401461?ref=myread)。
對于ObservableField這些字段是可以稍微做一下分類和包裹的。比如說可能一些字段是綁定到控件的一些Style屬性上(如長度、顏色、大小),對于這類針對View Style的的字段可以聲明一個(gè)ViewStyle類包裹起來,這樣整個(gè)代碼邏輯會更清晰一些,不然閱讀性較差。而對于其他一些字段,比如說title、imageUrl、name這些屬于數(shù)據(jù)源類型的字段,這些字段也叫數(shù)據(jù)字段,是和業(yè)務(wù)數(shù)據(jù)和邏輯息息相關(guān)的,這些字段可以放在一塊。
上面演示了DataBinding是如何雙向綁定的,這個(gè)是實(shí)現(xiàn)MVVM模式的中ViewModel的關(guān)鍵部分。
2.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>
請注意 ,這次 <variable>中導(dǎo)入的是UserViewModel,這也就是MVVM的VM層,當(dāng)Model業(yè)務(wù)數(shù)據(jù)發(fā)生變化時(shí)候,通知UI更新,UI更新的時(shí)候,通知Model發(fā)生變化。
<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);
}
}
可以發(fā)現(xiàn),View層做的就是和UI相關(guān)的工作,我們只在XML、Activity和Fragment寫View層的代碼,View層不做和業(yè)務(wù)相關(guān)的事,也就是我們在Activity不寫業(yè)務(wù)邏輯和業(yè)務(wù)數(shù)據(jù)相關(guān)的代碼,更新UI通過數(shù)據(jù)綁定實(shí)現(xiàn),盡量在ViewModel里面做。
b、ViewModel層
public class UserViewModel {
//注意,這里都需要定義成public,否則這個(gè)字段讀取不到
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僅僅專注于業(yè)務(wù)的邏輯處理,只做和業(yè)務(wù)邏輯和業(yè)務(wù)數(shù)據(jù)相關(guān)的事,UI相關(guān)的事情不要寫在這里面,ViewModel 層不會持有任何控件的引用,更不會在ViewModel中通過UI控件的引用去做更新UI的事情。但是ViewModel可能會改變數(shù)據(jù),由于數(shù)據(jù)和UI已經(jīng)綁定到一起了,所以相應(yīng)的控件上會自動去更新UI。
c、Model層
Model層就是職責(zé)數(shù)據(jù)獲取的,網(wǎng)絡(luò)請求的邏輯在這里面寫,類似于MVP。所以我覺得ViewModel層可以持有一個(gè)Model的引用,通知Model獲取數(shù)據(jù),同時(shí)Model在獲取到數(shù)據(jù)之后,回調(diào)通知ViewModel進(jìn)行數(shù)據(jù)更改,進(jìn)而使UI得到更新。
總結(jié)一下:View層的Activity通過DataBinding生成Binding實(shí)例,把這個(gè)實(shí)例傳遞給ViewModel,ViewModel層通過把自身與Binding實(shí)例綁定,從而實(shí)現(xiàn)View中l(wèi)ayout與ViewModel的雙向綁定。如果不引入ViewModel這一層,還會有一個(gè)缺點(diǎn):一個(gè)xml中可能會涉及到多個(gè)數(shù)據(jù)對象,那么我們只有把這個(gè)多個(gè)數(shù)據(jù)對象都引入進(jìn)來,xml布局的清晰程度胡下降,通過這種方法,我們的layout文件中data標(biāo)簽中只需要引入ViewModel就可以了,其它的數(shù)據(jù)對象統(tǒng)一在ViewModel中一并處理。關(guān)于三者的協(xié)作關(guān)系可以如下圖表示:

d、MVVM的問題
第一點(diǎn):數(shù)據(jù)綁定使得 Bug 很難被調(diào)試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問題。數(shù)據(jù)綁定使得一個(gè)位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那么容易了。
第二點(diǎn):對于過大的項(xiàng)目,數(shù)據(jù)綁定需要花費(fèi)更多的內(nèi)存。
對于MVVM的理解,發(fā)現(xiàn)網(wǎng)絡(luò)上,大家在整體上的理解是差不多的,但是細(xì)節(jié)上有一些不一樣的地方,比如MVVM的業(yè)務(wù)邏輯分工不是很明確,有些人會在ViewModel寫,有的會在Moldel中寫,甚至還有一些反對派戳我,認(rèn)為MVVM違背的JAVA的分層設(shè)計(jì)思想,我認(rèn)為不管什么架構(gòu)設(shè)計(jì),模塊化,框架化,服務(wù)化等是基本思想,所以這個(gè)基本的原則我們要遵守,對一些新的架構(gòu)模式,抱著一個(gè)客觀的態(tài)度去學(xué)學(xué)習(xí)總是可以提高自己的設(shè)計(jì)水平。
Please accept mybest wishes for your happiness and success !
參考:
http://tech.meituan.com/android_mvvm.html
http://www.itdecent.cn/p/2fc41a310f79
https://zhuanlan.zhihu.com/p/23772285?from=groupmessage
https://github.com/tianzhijiexian/DBinding
http://www.open-open.com/lib/view/open1450008180500.html