相信大家對(duì)MVC,MVP和MVVM都不陌生,作為三個(gè)最耳熟能詳?shù)腁ndroid框架,它們的應(yīng)用可以是非常廣泛的,但是對(duì)于一些新手來(lái)說(shuō),可能對(duì)于區(qū)分它們?nèi)齻€(gè)都有困難,更別說(shuō)在實(shí)際的項(xiàng)目中應(yīng)用了,有些時(shí)候想用MVP的,代碼寫(xiě)著寫(xiě)著就變成了MVC,久而久之就對(duì)它們?nèi)齻€(gè)的選擇產(chǎn)生了恐懼感,如果你也是這樣的人群,那么這篇文章可能會(huì)對(duì)你有很大的幫助,希望大家看完都會(huì)有收獲吧!
文章重點(diǎn):
(1)了解并區(qū)分MVC,MVP,MVVM。
(2)知道這三種模式在Android中如何使用。
(3)走出data binding的誤區(qū)。
(4)了解MVP+data binding的開(kāi)發(fā)模式。
本篇文章的demo我將會(huì)上傳到我的github上。
正如莊子在逍遙游中說(shuō)的,如果水不夠深,那就沒(méi)有能夠擔(dān)負(fù)大船的力量 。所以在真正開(kāi)始涉及具體的代碼之前,我們要先對(duì)MVC,MVP和MVVM做一個(gè)初步的了解。如果各位同學(xué)對(duì)此已經(jīng)有所了解了,可以選擇性跳過(guò)這一節(jié)。
MVC,Model View Controller,是軟件架構(gòu)中最常見(jiàn)的一種框架,簡(jiǎn)單來(lái)說(shuō)就是通過(guò)controller的控制去操作model層的數(shù)據(jù),并且返回給view層展示,具體見(jiàn)下圖

當(dāng)用戶(hù)出發(fā)事件的時(shí)候,view層會(huì)發(fā)送指令到controller層,接著controller去通知model層更新數(shù)據(jù),model層更新完數(shù)據(jù)以后直接顯示在view層上,這就是MVC的工作原理。
那具體到Android上是怎么樣一個(gè)情況呢?
大家都知道一個(gè)Android工程有什么對(duì)吧,有java的class文件,有res文件夾,里面是各種資源,還有類(lèi)似manifest文件等等。對(duì)于原生的Android項(xiàng)目來(lái)說(shuō),layout.xml里面的xml文件就對(duì)應(yīng)于MVC的view層,里面都是一些view的布局代碼,而各種java bean,還有一些類(lèi)似repository類(lèi)就對(duì)應(yīng)于model層,至于controller層嘛,當(dāng)然就是各種activity咯。大家可以試著套用我上面說(shuō)的MVC的工作原理是理解。比如你的界面有一個(gè)按鈕,按下這個(gè)按鈕去網(wǎng)絡(luò)上下載一個(gè)文件,這個(gè)按鈕是view層的,是使用xml來(lái)寫(xiě)的,而那些和網(wǎng)絡(luò)連接相關(guān)的代碼寫(xiě)在其他類(lèi)里,比如你可以寫(xiě)一個(gè)專(zhuān)門(mén)的networkHelper類(lèi),這個(gè)就是model層,那怎么連接這兩層呢?是通過(guò)button.setOnClickListener()這個(gè)函數(shù),這個(gè)函數(shù)就寫(xiě)在了activity中,對(duì)應(yīng)于controller層。是不是很清晰。
大家想過(guò)這樣會(huì)有什么問(wèn)題嗎?顯然是有的,不然為什么會(huì)有MVP和MVVM的誕生呢,是吧。問(wèn)題就在于xml作為view層,控制能力實(shí)在太弱了,你想去動(dòng)態(tài)的改變一個(gè)頁(yè)面的背景,或者動(dòng)態(tài)的隱藏/顯示一個(gè)按鈕,這些都沒(méi)辦法在xml中做,只能把代碼寫(xiě)在activity中,造成了activity既是controller層,又是view層的這樣一個(gè)窘境。大家回想一下自己寫(xiě)的代碼,如果是一個(gè)邏輯很復(fù)雜的頁(yè)面,activity或者fragment是不是動(dòng)輒上千行呢?這樣不僅寫(xiě)起來(lái)麻煩,維護(hù)起來(lái)更是噩夢(mèng)。(當(dāng)然看過(guò)Android源碼的同學(xué)其實(shí)會(huì)發(fā)現(xiàn)上千行的代碼不算啥,一個(gè)RecyclerView.class的代碼都快上萬(wàn)行了呢。。)
MVC還有一個(gè)重要的缺陷,大家看上面那幅圖,view層和model層是相互可知的,這意味著兩層之間存在耦合,耦合對(duì)于一個(gè)大型程序來(lái)說(shuō)是非常致命的,因?yàn)檫@表示開(kāi)發(fā),測(cè)試,維護(hù)都需要花大量的精力。
正因?yàn)镸VC有這樣那樣的缺點(diǎn),所以才演化出了MVP和MVVM這兩種框架。
MVP作為MVC的演化,解決了MVC不少的缺點(diǎn),對(duì)于Android來(lái)說(shuō),MVP的model層相對(duì)于MVC是一樣的,而activity和fragment不再是controller層,而是純粹的view層,所有關(guān)于用戶(hù)事件的轉(zhuǎn)發(fā)全部交由presenter層處理。下面還是讓我們看圖

從圖中就可以看出,最明顯的差別就是view層和model層不再相互可知,完全的解耦,取而代之的presenter層充當(dāng)了橋梁的作用,用于操作view層發(fā)出的事件傳遞到presenter層中,presenter層去操作model層,并且將數(shù)據(jù)返回給view層,整個(gè)過(guò)程中view層和model層完全沒(méi)有聯(lián)系。看到這里大家可能會(huì)問(wèn),雖然view層和model層解耦了,但是view層和presenter層不是耦合在一起了嗎?其實(shí)不是的,對(duì)于view層和presenter層的通信,我們是可以通過(guò)接口實(shí)現(xiàn)的,具體的意思就是說(shuō)我們的activity,fragment可以去實(shí)現(xiàn)實(shí)現(xiàn)定義好的接口,而在對(duì)應(yīng)的presenter中通過(guò)接口調(diào)用方法。不僅如此,我們還可以編寫(xiě)測(cè)試用的View,模擬用戶(hù)的各種操作,從而實(shí)現(xiàn)對(duì)Presenter的測(cè)試。這就解決了MVC模式中測(cè)試,維護(hù)難的問(wèn)題。
當(dāng)然,其實(shí)最好的方式是使用fragment作為view層,而activity則是用于創(chuàng)建view層(fragment)和presenter層(presenter)的一個(gè)控制器。
MVVM最早是由微軟提出的

這里要感謝泡在網(wǎng)上的日子,因?yàn)榍懊婵吹降娜龔垐D我都是從它的博客中摘取的,如果有人知道不允許這樣做的話請(qǐng)告訴我,我會(huì)從我的博客中刪除的,謝謝。
從圖中看出,它和MVP的區(qū)別貌似不大,只不過(guò)是presenter層換成了viewmodel層,還有一點(diǎn)就是view層和viewmodel層是相互綁定的關(guān)系,這意味著當(dāng)你更新viewmodel層的數(shù)據(jù)的時(shí)候,view層會(huì)相應(yīng)的變動(dòng)ui。
我們很難去說(shuō)MVP和MVVM這兩個(gè)MVC的變種孰優(yōu)孰劣,還是要具體情況具體分析。
對(duì)于程序員來(lái)說(shuō),空談是最沒(méi)效率的一種方式,相信大家看了我上面對(duì)于三種模式的分析,或多或少都會(huì)有點(diǎn)云里霧里,下面讓我們結(jié)合代碼來(lái)看看。
讓我們?cè)囅胍幌孪旅孢@個(gè)情景,用戶(hù)點(diǎn)擊一個(gè)按鈕A,獲取github上對(duì)應(yīng)公司對(duì)應(yīng)倉(cāng)庫(kù)中貢獻(xiàn)排行第一的任的名字,然后我們還會(huì)有一個(gè)按鈕B,用戶(hù)點(diǎn)擊按鈕B,界面上排行第一的那個(gè)人的名字就會(huì)換成自己的。
MVC實(shí)現(xiàn)是最簡(jiǎn)單的。
首先看對(duì)應(yīng)view層的xml文件
[js]view plaincopy
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
tools:context=".ui.view.MainActivity"
android:fitsSystemWindows="true">
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>
android:text="change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30sp"/>
很簡(jiǎn)單,兩個(gè)Button一個(gè)TextView
接著看對(duì)應(yīng)controller層的activity
[js]view plaincopy
publicclassMainActivityextendsAppCompatActivity?{
privateProcessDialog?dialog;
privateContributor?contributor?=newContributor();
privateTextView?topContributor;
privateSubscriber?contributorSub?=newSubscriber()?{
@Override
publicvoidonStart()?{
showProgress();
}
@Override
publicvoidonCompleted()?{
}
@Override
publicvoidonError(Throwable?e)?{
}
@Override
publicvoidonNext(Contributor?contributor)?{
MainActivity.this.contributor?=?contributor;
topContributor.setText(contributor.login);
dismissProgress();
}
};
@Override
protectedvoidonCreate(Bundle?savedInstanceState)?{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
topContributor?=?(TextView)findViewById(R.id.top_contributor);
}
publicvoidget(View?view){
getTopContributor("square","retrofit");
}
publicvoidchange(View?view){
contributor.login?="zjutkz";
topContributor.setText(contributor.login);
}
publicvoidgetTopContributor(String?owner,String?repo){
GitHubApi.getContributors(owner,?repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(newFunc1,?Contributor>()?{
@Override
publicContributor?call(List?contributors)?{
returncontributors.get(0);
}
})
.subscribe(contributorSub);
}
publicvoidshowProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.showMessage("正在加載...");
}
publicvoiddismissProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.dismiss();
}
}
我們看一下get()方法中調(diào)用的getTopContributor方法
[js]view plaincopy
publicvoidgetTopContributor(String?owner,String?repo){
GitHubApi.getContributors(owner,?repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(newFunc1,?Contributor>()?{
@Override
publicContributor?call(List?contributors)?{
returncontributors.get(0);
}
})
.subscribe(contributorSub);
}
熟悉rxjava和retrofit的同學(xué)應(yīng)該都明白這是啥意思,如果對(duì)這兩個(gè)開(kāi)源庫(kù)不熟悉也沒(méi)事,可以參考給 Android 開(kāi)發(fā)者的 RxJava 詳解和用 Retrofit 2 簡(jiǎn)化 HTTP 請(qǐng)求這兩篇文章。
對(duì)于這里大家只要知道這段代碼的意思就是去獲取github上owner公司中的repo倉(cāng)庫(kù)里貢獻(xiàn)排名第一的那個(gè)人。貢獻(xiàn)者是通過(guò)Contributor這個(gè)java bean存儲(chǔ)的。
[js]view plaincopy
publicclassContributor?{
publicString?login;
publicintcontributions;
@Override
publicString?toString()?{
returnlogin?+",?"+?contributions;
}
}
很簡(jiǎn)單,login表示貢獻(xiàn)者的名字,contributor表示貢獻(xiàn)的次數(shù)。
然后通過(guò)rxjava的subscriber中的onNext()函數(shù)得到這個(gè)數(shù)據(jù)。
[js]view plaincopy
privateSubscriber?contributorSub?=newSubscriber()?{
@Override
publicvoidonStart()?{
showProgress();
}
@Override
publicvoidonCompleted()?{
}
@Override
publicvoidonError(Throwable?e)?{
}
@Override
publicvoidonNext(Contributor?contributor)?{
MainActivity.this.contributor?=?contributor;
topContributor.setText(contributor.login);
dismissProgress();
}
};
至于另外那個(gè)change按鈕的工作大家應(yīng)該都看得懂,這里不重復(fù)了。
好了,我們來(lái)回顧一遍整個(gè)流程。
首先在xml中寫(xiě)好布局代碼。
其次,activity作為一個(gè)controller,里面的邏輯是監(jiān)聽(tīng)用戶(hù)點(diǎn)擊按鈕并作出相應(yīng)的操作。比如針對(duì)get按鈕,做的工作就是調(diào)用GithubApi的方法去獲取數(shù)據(jù)。
GithubApi,Contributor等類(lèi)則表示MVC中的model層,里面是數(shù)據(jù)和一些具體的邏輯操作。
說(shuō)完了流程再來(lái)看看問(wèn)題,還記得我們前面說(shuō)的嗎,MVC在Android上的應(yīng)用,一個(gè)具體的問(wèn)題就是activity的責(zé)任過(guò)重,既是controller又是view。這里是怎么體現(xiàn)的呢?看了代碼大家發(fā)現(xiàn)其中有一個(gè)progressDialog,在加載數(shù)據(jù)的時(shí)候顯示,加載完了以后取消,邏輯其實(shí)是view層的邏輯,但是這個(gè)我們沒(méi)辦法寫(xiě)到xml里面啊,包括TextView.setTextView(),這個(gè)也一樣。我們只能把這些邏輯寫(xiě)到activity中,這就造成了activity的臃腫,這個(gè)例子可能還好,如果是一個(gè)復(fù)雜的頁(yè)面呢?大家自己想象一下。
通過(guò)具體的代碼大家知道了MVC在Android上是如何工作的,也知道了它的缺點(diǎn),那MVP是如何修正的呢?
這里先向大家推薦github上的一個(gè)第三方庫(kù),通過(guò)這個(gè)庫(kù)大家可以很輕松的實(shí)現(xiàn)MVP。好了,還是看代碼吧。
首先還是xml
[js]view plaincopy
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
tools:context=".ui.view.MainActivity"
android:fitsSystemWindows="true">
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>
android:text="change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30sp"/>
這個(gè)和MVC是一樣的,畢竟界面的形式是一樣的嘛。
接下去,我們看一個(gè)接口。
[js]view plaincopy
publicinterfaceContributorViewextendsMvpView?{
voidonLoadContributorStart();
voidonLoadContributorComplete(Contributor?topContributor);
voidonChangeContributorName(String?name);
}
這個(gè)接口起什么作用呢?還記得我之前說(shuō)的嗎?MVP模式中,view層和presenter層靠的就是接口進(jìn)行連接,而具體的就是上面的這個(gè)了,里面定義的三個(gè)方法,第一個(gè)是開(kāi)始獲取數(shù)據(jù),第二個(gè)是獲取數(shù)據(jù)成功,第三個(gè)是改名。我們的view層(activity)只要實(shí)現(xiàn)這個(gè)接口就可以了。
下面看activity的代碼
[js]view plaincopy
publicclassMainActivityextendsMvpActivityimplementsContributorView?{
privateProcessDialog?dialog;
privateTextView?topContributor;
@Override
protectedvoidonCreate(Bundle?savedInstanceState)?{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
topContributor?=?(TextView)findViewById(R.id.top_contributor);
}
@NonNull
@Override
publicContributorPresenter?createPresenter()?{
returnnewContributorPresenter();
}
publicvoidget(View?view){
getPresenter().get("square","retrofit");
}
publicvoidchange(View?view){
getPresenter().change();
}
@Override
publicvoidonLoadContributorStart()?{
showProgress();
}
@Override
publicvoidonLoadContributorComplete(Contributor?contributor)?{
topContributor.setText(contributor.toString());
dismissProgress();
}
@Override
publicvoidonChangeContributorName(String?name)?{
topContributor.setText(name);
}
publicvoidshowProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.showMessage("正在加載...");
}
publicvoiddismissProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.dismiss();
}
}
它繼承自MvpActivity,實(shí)現(xiàn)了剛才的ContributorView接口。繼承的那個(gè)MvpActivity大家這里不用太關(guān)心主要是做了一些初始化和生命周期的封裝。我們只要關(guān)心這個(gè)activity作為view層,到底是怎么工作的。
[js]view plaincopy
publicvoidget(View?view){
getPresenter().get("square","retrofit");
}
publicvoidchange(View?view){
getPresenter().change();
}
get()和change()這兩個(gè)方法是我們點(diǎn)擊按鈕以后執(zhí)行的,可以看到,里面完完全全沒(méi)有任何和model層邏輯相關(guān)的東西,只是簡(jiǎn)單的委托給了presenter,那我們?cè)倏纯磒resenter層做了什么
[js]view plaincopy
publicclassContributorPresenterextendsMvpBasePresenter?{
privateSubscriber?contributorSub?=newSubscriber()?{
@Override
publicvoidonStart()?{
ContributorView?view?=?getView();
if(view?!=null){
view.onLoadContributorStart();
}
}
@Override
publicvoidonCompleted()?{
}
@Override
publicvoidonError(Throwable?e)?{
}
@Override
publicvoidonNext(Contributor?topContributor)?{
ContributorView?view?=?getView();
if(view?!=null){
view.onLoadContributorComplete(topContributor);
}
}
};
publicvoidget(String?owner,String?repo){
GitHubApi.getContributors(owner,?repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(newFunc1,?Contributor>()?{
@Override
publicContributor?call(List?contributors)?{
returncontributors.get(0);
}
})
.subscribe(contributorSub);
}
publicvoidchange(){
ContributorView?view?=?getView();
if(view?!=null){
view.onChangeContributorName("zjutkz");
}
}
}
其實(shí)就是把剛才MVC中activity的那部分和model層相關(guān)的邏輯抽取了出來(lái),并且在相應(yīng)的時(shí)機(jī)調(diào)用ContributorView接口對(duì)應(yīng)的方法,而我們的activity是實(shí)現(xiàn)了這個(gè)接口的,自然會(huì)走到對(duì)應(yīng)的方法中。
好了,我們來(lái)捋一捋。
首先,和MVC最大的不同,MVP把a(bǔ)ctivity作為了view層,通過(guò)代碼也可以看到,整個(gè)activity沒(méi)有任何和model層相關(guān)的邏輯代碼,取而代之的是把代碼放到了presenter層中,presenter獲取了model層的數(shù)據(jù)之后,通過(guò)接口的形式將view層需要的數(shù)據(jù)返回給它就OK了。
這樣的好處是什么呢?首先,activity的代碼邏輯減少了,其次,view層和model層完全解耦,具體來(lái)說(shuō),如果你需要測(cè)試一個(gè)http請(qǐng)求是否順利,你不需要寫(xiě)一個(gè)activity,只需要寫(xiě)一個(gè)java類(lèi),實(shí)現(xiàn)對(duì)應(yīng)的接口,presenter獲取了數(shù)據(jù)自然會(huì)調(diào)用相應(yīng)的方法,相應(yīng)的,你也可以自己在presenter中mock數(shù)據(jù),分發(fā)給view層,用來(lái)測(cè)試布局是否正確。
首先在看這段內(nèi)容之前,你需要保證你對(duì)data binding框架有基礎(chǔ)的了解。不了解的同學(xué)可以去看下這篇文章。在接下去讓我們開(kāi)始探索MVVM,MVVM最近在Android上可謂十分之火,最主要的原因就是谷歌推出了data binding這個(gè)框架,可以輕松的實(shí)現(xiàn)MVVM。但是,我在網(wǎng)上查閱關(guān)于Android的data binding資料的時(shí)候,發(fā)現(xiàn)國(guó)內(nèi)有很多人都誤解了,首先,我們從一篇錯(cuò)誤的文章開(kāi)始。當(dāng)然我在這里引用這篇文章也是對(duì)事不對(duì)人,如果對(duì)文章的作者產(chǎn)生了不好的影響我這里說(shuō)一聲抱歉。
上面那篇文章是一個(gè)關(guān)于data binding的使用,看起來(lái)很美好,但是,其中有一個(gè)錯(cuò)誤可以說(shuō)是非常,非常,非常嚴(yán)重的。

它竟然說(shuō)data binding的viewmodel層是binding類(lèi),其實(shí)不止是這篇文章,其他有一些開(kāi)發(fā)者寫(xiě)的關(guān)于data binding的文章里都犯了一樣的錯(cuò)誤。大家如果也有這樣的概念,請(qǐng)務(wù)必糾正過(guò)來(lái)?。?/p>
說(shuō)完了錯(cuò)誤的概念,那data binding中真正的viewmodel是什么呢?我們還是以之前MVC,MVP的那個(gè)例子做引導(dǎo)。
首先是view層,這沒(méi)啥好說(shuō)的,和MVP一樣,view層就是xml和activity。
[js]view plaincopy
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
tools:context=".ui.view.MainActivity"
android:fitsSystemWindows="true">
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>
android:text="change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30sp"/>
[js]view plaincopy
publicclassMainActivityextendsAppCompatActivity?{
privateSubscriber?contributorSub?=newSubscriber()?{
@Override
publicvoidonStart()?{
showProgress();
}
@Override
publicvoidonCompleted()?{
}
@Override
publicvoidonError(Throwable?e)?{
}
@Override
publicvoidonNext(Contributor?contributor)?{
binding.setContributor(contributor);
dismissProgress();
}
};
privateProcessDialog?dialog;
privateMvvmActivityMainBinding?binding;
@Override
protectedvoidonCreate(Bundle?savedInstanceState)?{
super.onCreate(savedInstanceState);
binding?=?DataBindingUtil.setContentView(this,?R.layout.mvvm_activity_main);
}
publicvoidget(View?view){
getContributors("square","retrofit");
}
publicvoidchange(View?view){
if(binding.getContributor()?!=null){
binding.getContributor().setLogin("zjutkz");
}
}
publicvoidshowProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.showMessage("正在加載...");
}
publicvoiddismissProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.dismiss();
}
publicvoidgetContributors(String?owner,String?repo){
GitHubApi.getContributors(owner,?repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(newFunc1,?Contributor>()?{
@Override
publicContributor?call(List?contributors)?{
returncontributors.get(0);
}
})
.subscribe(contributorSub);
}
}
如果你對(duì)data binding框架是有了解的,上面的代碼你能輕松的看懂。
那model層又是什么呢?當(dāng)然就是那些和數(shù)據(jù)相關(guān)的類(lèi),GithubApi等等。
重點(diǎn)來(lái)了,viewmodel層呢?好吧,viewmodel層就是是Contributor類(lèi)!大家不要驚訝,我慢慢的來(lái)說(shuō)。
[js]view plaincopy
publicclassContributorextendsBaseObservable{
privateString?login;
privateintcontributions;
@Bindable
publicString?getLogin(){
returnlogin;
}
@Bindable
publicintgetContributions(){
returncontributions;
}
publicvoidsetLogin(String?login){
this.login?=?login;
notifyPropertyChanged(BR.login);
}
publicvoidsetContributions(intcontributions){
this.contributions?=?contributions;
notifyPropertyChanged(BR.contributions);
}
@Override
publicString?toString()?{
returnlogin?+",?"+?contributions;
}
}
我們可以看到,Contributor和MVP相比,繼承自了BaseObservable,有基礎(chǔ)的同學(xué)都知道這是為了當(dāng)Contributor內(nèi)部的variable改變的時(shí)候ui可以同步的作出響應(yīng)。
我為什么說(shuō)Contributor是一個(gè)viewmodel呢。大家還記得viewmodel的概念嗎?view和viewmodel相互綁定在一起,viewmodel的改變會(huì)同步到view層,從而view層作出響應(yīng)。這不就是Contributor和xml中那些組件元素的關(guān)系嗎?所以,大家不要被binding類(lèi)迷惑了,data binding框架中的viewmodel是自己定義的那些看似是model類(lèi)的東西!比如這里的Contributor!
話說(shuō)到這里,那binding類(lèi)又是什么呢?其實(shí)具體對(duì)應(yīng)到之前MVVM的那張圖就很好理解了,我們想一下,binding類(lèi)的工作是什么?
[js]view plaincopy
binding?=?DataBindingUtil.setContentView(this,?R.layout.mvvm_activity_main);
binding.setContributor(contributor);
首先,binding要通過(guò)DataBindingUtil.setContentView()方法將xml,也就是view層設(shè)定。
接著,通過(guò)setXXX()方法將viewmodel層注入進(jìn)去。
由于這兩個(gè)工作,view層(xml的各個(gè)組件)和viewmodel層(contributor)綁定在了一起。
好了,大家知道了嗎,binding類(lèi),其實(shí)就是上圖中view和viewmodel中間的那根線?。?!
前面討論了MVC,MVP和MVVM具體的實(shí)現(xiàn)方案,大家肯定都了解了它們?nèi)叩年P(guān)系和使用方式。但是,這里我想說(shuō),不要把一個(gè)框架看作萬(wàn)能的,其實(shí)MVP和MVVM都是有自己的缺陷的!下面我一一來(lái)說(shuō)。
MVP的問(wèn)題在于,由于我們使用了接口的方式去連接view層和presenter層,這樣就導(dǎo)致了一個(gè)問(wèn)題,如果你有一個(gè)邏輯很復(fù)雜的頁(yè)面,你的接口會(huì)有很多,十幾二十個(gè)都不足為奇。想象一個(gè)app中有很多個(gè)這樣復(fù)雜的頁(yè)面,維護(hù)接口的成本就會(huì)非常的大。
這個(gè)問(wèn)題的解決方案就是你得根據(jù)自己的業(yè)務(wù)邏輯去斟酌著寫(xiě)接口。你可以定義一些基類(lèi)接口,把一些公共的邏輯,比如網(wǎng)絡(luò)請(qǐng)求成功失敗,toast等等放在里面,之后你再定義新的接口的時(shí)候可以繼承自那些基類(lèi),這樣會(huì)好不少。
MVVM的問(wèn)題呢,其實(shí)和MVC有一點(diǎn)像。data binding框架解決了數(shù)據(jù)綁定的問(wèn)題,但是view層還是會(huì)過(guò)重,大家可以看我上面那個(gè)MVVM模式下的activity
[js]view plaincopy
publicclassMainActivityextendsAppCompatActivity?{
privateSubscriber?contributorSub?=newSubscriber()?{
@Override
publicvoidonStart()?{
showProgress();
}
@Override
publicvoidonCompleted()?{
}
@Override
publicvoidonError(Throwable?e)?{
}
@Override
publicvoidonNext(Contributor?contributor)?{
binding.setContributor(contributor);
dismissProgress();
}
};
privateProcessDialog?dialog;
privateMvvmActivityMainBinding?binding;
@Override
protectedvoidonCreate(Bundle?savedInstanceState)?{
super.onCreate(savedInstanceState);
binding?=?DataBindingUtil.setContentView(this,?R.layout.mvvm_activity_main);
}
publicvoidget(View?view){
getContributors("square","retrofit");
}
publicvoidchange(View?view){
if(binding.getContributor()?!=null){
binding.getContributor().setLogin("zjutkz");
}
}
publicvoidshowProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.showMessage("正在加載...");
}
publicvoiddismissProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.dismiss();
}
publicvoidgetContributors(String?owner,String?repo){
GitHubApi.getContributors(owner,?repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(newFunc1,?Contributor>()?{
@Override
publicContributor?call(List?contributors)?{
returncontributors.get(0);
}
})
.subscribe(contributorSub);
}
}
大家有沒(méi)有發(fā)現(xiàn),activity在MVVM中應(yīng)該是view層的,但是里面卻和MVC一樣寫(xiě)了對(duì)model的處理。有人會(huì)說(shuō)你可以把對(duì)model的處理放到viewmodel層中,這樣不是更符合MVVM的設(shè)計(jì)理念嗎?這樣確實(shí)可以,但是progressDialog的show和dismiss呢?你怎么在viewmodel層中控制?這是view層的東西啊,而且在xml中也沒(méi)有,我相信會(huì)有解決的方案,但是我們有沒(méi)有一種更加便捷的方式呢?
其實(shí),真正的最佳實(shí)踐都是人想出來(lái)的,我們?yōu)楹尾唤Y(jié)合一下MVP和MVVM的特點(diǎn)呢?其實(shí)谷歌已經(jīng)做了這樣的事,大家可以看下這個(gè)。沒(méi)錯(cuò),就是MVP+data binding,我們可以使用presenter去做和model層的通信,并且使用data binding去輕松的bind data。還是讓我們看代碼吧。
首先還是view層。
[js]view plaincopy
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
android:fitsSystemWindows="true">
android:id="@+id/get"
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>
android:id="@+id/change"
android:text="change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30sp"
android:text="@{contributor.login}"/>
[js]view plaincopy
publicclassMainActivityextendsMvpActivityimplementsContributorView?{
privateProcessDialog?dialog;
privateActivityMainBinding?binding;
@Override
protectedvoidonCreate(Bundle?savedInstanceState)?{
super.onCreate(savedInstanceState);
binding?=?DataBindingUtil.setContentView(this,?R.layout.activity_main);
}
@NonNull
@Override
publicContributorPresenter?createPresenter()?{
returnnewContributorPresenter();
}
publicvoidget(View?view){
getPresenter().get("square","retrofit");
}
publicvoidchange(View?view){
if(binding.getContributor()?!=null){
binding.getContributor().setLogin("zjutkz");
}
}
@Override
publicvoidonLoadContributorStart()?{
showProgress();
}
@Override
publicvoidonLoadContributorComplete(Contributor?contributor)?{
binding.setContributor(contributor);
dismissProgress();
}
publicvoidshowProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.showMessage("正在加載...");
}
publicvoiddismissProgress(){
if(dialog?==null){
dialog?=newProcessDialog(this);
}
dialog.dismiss();
}
}
然后是presenter層
[js]view plaincopy
publicclassContributorPresenterextendsMvpBasePresenter?{
privateSubscriber?contributorSub?=newSubscriber()?{
@Override
publicvoidonStart()?{
ContributorView?view?=?getView();
if(view?!=null){
view.onLoadContributorStart();
}
}
@Override
publicvoidonCompleted()?{
}
@Override
publicvoidonError(Throwable?e)?{
}
@Override
publicvoidonNext(Contributor?topContributor)?{
ContributorView?view?=?getView();
if(view?!=null){
view.onLoadContributorComplete(topContributor);
}
}
};
publicvoidget(String?owner,String?repo){
GitHubApi.getContributors(owner,?repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(newFunc1,?Contributor>()?{
@Override
publicContributor?call(List?contributors)?{
returncontributors.get(0);
}
})
.subscribe(contributorSub);
}
}
model層就是GithubApi等等。
我們使用了data binding框架去節(jié)省了類(lèi)似findViewById和數(shù)據(jù)綁定的時(shí)間,又使用了presenter去將業(yè)務(wù)邏輯和view層分離。
當(dāng)然這也不是固定的,你大可以在viewmodel中實(shí)現(xiàn)相應(yīng)的接口,presenter層的數(shù)據(jù)直接發(fā)送到viewmodel中,在viewmodel里更新,因?yàn)関iew和viewmodel是綁定的,這樣view也會(huì)相應(yīng)的作出反應(yīng)。
說(shuō)到這里,我還是想重復(fù)剛才的那句話,最佳實(shí)踐都是人想出來(lái)的,用這些框架根本的原因也是為了盡量低的耦合性和盡量高的可復(fù)用性。