轉(zhuǎn) 認(rèn)清Android框架 MVC录别,MVP和MVVM

相信大家對MVC,MVP和MVVM都不陌生邻吞,作為三個(gè)最耳熟能詳?shù)腁ndroid框架组题,它們的應(yīng)用可以是非常廣泛的,但是對于一些新手來說抱冷,可能對于區(qū)分它們?nèi)齻€(gè)都有困難崔列,更別說在實(shí)際的項(xiàng)目中應(yīng)用了,有些時(shí)候想用MVP的旺遮,代碼寫著寫著就變成了MVC赵讯,久而久之就對它們?nèi)齻€(gè)的選擇產(chǎn)生了恐懼感,如果你也是這樣的人群耿眉,那么這篇文章可能會(huì)對你有很大的幫助边翼,希望大家看完都會(huì)有收獲吧!

文章重點(diǎn):

(1)了解并區(qū)分MVC鸣剪,MVP组底,MVVM。

(2)知道這三種模式在Android中如何使用筐骇。

(3)走出data binding的誤區(qū)债鸡。

(4)了解MVP+data binding的開發(fā)模式。

本篇文章的demo我將會(huì)上傳到我的github上拥褂。

水之積也不厚,則其負(fù)大舟也無力

正如莊子在逍遙游中說的聚假,如果水不夠深,那就沒有能夠擔(dān)負(fù)大船的力量 袜啃。所以在真正開始涉及具體的代碼之前条霜,我們要先對MVC,MVP和MVVM做一個(gè)初步的了解悔详。如果各位同學(xué)對此已經(jīng)有所了解了镊屎,可以選擇性跳過這一節(jié)。

MVC

MVC茄螃,Model View Controller缝驳,是軟件架構(gòu)中最常見的一種框架,簡單來說就是通過controller的控制去操作model層的數(shù)據(jù),并且返回給view層展示用狱,具體見下圖

當(dāng)用戶出發(fā)事件的時(shí)候运怖,view層會(huì)發(fā)送指令到controller層,接著controller去通知model層更新數(shù)據(jù)夏伊,model層更新完數(shù)據(jù)以后直接顯示在view層上摇展,這就是MVC的工作原理。

那具體到Android上是怎么樣一個(gè)情況呢溺忧?

大家都知道一個(gè)Android工程有什么對吧咏连,有java的class文件,有res文件夾鲁森,里面是各種資源祟滴,還有類似manifest文件等等。對于原生的Android項(xiàng)目來說歌溉,layout.xml里面的xml文件就對應(yīng)于MVC的view層垄懂,里面都是一些view的布局代碼,而各種java bean痛垛,還有一些類似repository類就對應(yīng)于model層埠偿,至于controller層嘛,當(dāng)然就是各種activity咯榜晦。大家可以試著套用我上面說的MVC的工作原理是理解冠蒋。比如你的界面有一個(gè)按鈕,按下這個(gè)按鈕去網(wǎng)絡(luò)上下載一個(gè)文件乾胶,這個(gè)按鈕是view層的抖剿,是使用xml來寫的,而那些和網(wǎng)絡(luò)連接相關(guān)的代碼寫在其他類里识窿,比如你可以寫一個(gè)專門的networkHelper類斩郎,這個(gè)就是model層,那怎么連接這兩層呢喻频?是通過button.setOnClickListener()這個(gè)函數(shù)缩宜,這個(gè)函數(shù)就寫在了activity中,對應(yīng)于controller層甥温。是不是很清晰锻煌。

大家想過這樣會(huì)有什么問題嗎?顯然是有的姻蚓,不然為什么會(huì)有MVP和MVVM的誕生呢宋梧,是吧。問題就在于xml作為view層狰挡,控制能力實(shí)在太弱了捂龄,你想去動(dòng)態(tài)的改變一個(gè)頁面的背景释涛,或者動(dòng)態(tài)的隱藏/顯示一個(gè)按鈕,這些都沒辦法在xml中做倦沧,只能把代碼寫在activity中唇撬,造成了activity既是controller層,又是view層的這樣一個(gè)窘境展融。大家回想一下自己寫的代碼局荚,如果是一個(gè)邏輯很復(fù)雜的頁面,activity或者fragment是不是動(dòng)輒上千行呢愈污?這樣不僅寫起來麻煩,維護(hù)起來更是噩夢轮傍。(當(dāng)然看過Android源碼的同學(xué)其實(shí)會(huì)發(fā)現(xiàn)上千行的代碼不算啥暂雹,一個(gè)RecyclerView.class的代碼都快上萬行了呢。创夜。)

MVC還有一個(gè)重要的缺陷杭跪,大家看上面那幅圖,view層和model層是相互可知的驰吓,這意味著兩層之間存在耦合涧尿,耦合對于一個(gè)大型程序來說是非常致命的,因?yàn)檫@表示開發(fā)檬贰,測試姑廉,維護(hù)都需要花大量的精力。

正因?yàn)镸VC有這樣那樣的缺點(diǎn)翁涤,所以才演化出了MVP和MVVM這兩種框架桥言。

MVP

MVP作為MVC的演化,解決了MVC不少的缺點(diǎn)葵礼,對于Android來說号阿,MVP的model層相對于MVC是一樣的,而activity和fragment不再是controller層鸳粉,而是純粹的view層扔涧,所有關(guān)于用戶事件的轉(zhuǎn)發(fā)全部交由presenter層處理。下面還是讓我們看圖

從圖中就可以看出届谈,最明顯的差別就是view層和model層不再相互可知枯夜,完全的解耦,取而代之的presenter層充當(dāng)了橋梁的作用艰山,用于操作view層發(fā)出的事件傳遞到presenter層中卤档,presenter層去操作model層,并且將數(shù)據(jù)返回給view層程剥,整個(gè)過程中view層和model層完全沒有聯(lián)系劝枣√捞ぃ看到這里大家可能會(huì)問,雖然view層和model層解耦了舔腾,但是view層和presenter層不是耦合在一起了嗎溪胶?其實(shí)不是的,對于view層和presenter層的通信稳诚,我們是可以通過接口實(shí)現(xiàn)的哗脖,具體的意思就是說我們的activity,fragment可以去實(shí)現(xiàn)實(shí)現(xiàn)定義好的接口扳还,而在對應(yīng)的presenter中通過接口調(diào)用方法才避。不僅如此,我們還可以編寫測試用的View氨距,模擬用戶的各種操作桑逝,從而實(shí)現(xiàn)對Presenter的測試。這就解決了MVC模式中測試俏让,維護(hù)難的問題楞遏。

當(dāng)然,其實(shí)最好的方式是使用fragment作為view層首昔,而activity則是用于創(chuàng)建view層(fragment)和presenter層(presenter)的一個(gè)控制器寡喝。

MVVM

MVVM最早是由微軟提出的

這里要感謝泡在網(wǎng)上的日子,因?yàn)榍懊婵吹降娜龔垐D我都是從它的博客中摘取的勒奇,如果有人知道不允許這樣做的話請告訴我预鬓,我會(huì)從我的博客中刪除的,謝謝赊颠。

從圖中看出珊皿,它和MVP的區(qū)別貌似不大,只不過是presenter層換成了viewmodel層巨税,還有一點(diǎn)就是view層和viewmodel層是相互綁定的關(guān)系蟋定,這意味著當(dāng)你更新viewmodel層的數(shù)據(jù)的時(shí)候,view層會(huì)相應(yīng)的變動(dòng)ui草添。

我們很難去說MVP和MVVM這兩個(gè)MVC的變種孰優(yōu)孰劣驶兜,還是要具體情況具體分析。

紙上得來終覺淺远寸,絕知此事要躬行

對于程序員來說抄淑,空談是最沒效率的一種方式,相信大家看了我上面對于三種模式的分析驰后,或多或少都會(huì)有點(diǎn)云里霧里肆资,下面讓我們結(jié)合代碼來看看。

讓我們試想一下下面這個(gè)情景灶芝,用戶點(diǎn)擊一個(gè)按鈕A郑原,獲取github上對應(yīng)公司對應(yīng)倉庫中貢獻(xiàn)排行第一的任的名字唉韭,然后我們還會(huì)有一個(gè)按鈕B,用戶點(diǎn)擊按鈕B犯犁,界面上排行第一的那個(gè)人的名字就會(huì)換成自己的属愤。

MVC

MVC實(shí)現(xiàn)是最簡單的。

首先看對應(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"/>

很簡單酸役,兩個(gè)Button一個(gè)TextView

接著看對應(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)該都明白這是啥意思住诸,如果對這兩個(gè)開源庫不熟悉也沒事,可以參考給 Android 開發(fā)者的 RxJava 詳解用 Retrofit 2 簡化 HTTP 請求這兩篇文章涣澡。

對于這里大家只要知道這段代碼的意思就是去獲取github上owner公司中的repo倉庫里貢獻(xiàn)排名第一的那個(gè)人贱呐。貢獻(xiàn)者是通過Contributor這個(gè)java bean存儲(chǔ)的。

[js]view plaincopy

publicclassContributor?{

publicString?login;

publicintcontributions;

@Override

publicString?toString()?{

returnlogin?+",?"+?contributions;

}

}

很簡單入桂,login表示貢獻(xiàn)者的名字奄薇,contributor表示貢獻(xiàn)的次數(shù)。

然后通過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ù)了搞隐。

好了驹愚,我們來回顧一遍整個(gè)流程。

首先在xml中寫好布局代碼劣纲。

其次逢捺,activity作為一個(gè)controller,里面的邏輯是監(jiān)聽用戶點(diǎn)擊按鈕并作出相應(yīng)的操作癞季。比如針對get按鈕劫瞳,做的工作就是調(diào)用GithubApi的方法去獲取數(shù)據(jù)。

GithubApi绷柒,Contributor等類則表示MVC中的model層志于,里面是數(shù)據(jù)和一些具體的邏輯操作。

說完了流程再來看看問題废睦,還記得我們前面說的嗎伺绽,MVC在Android上的應(yīng)用,一個(gè)具體的問題就是activity的責(zé)任過重嗜湃,既是controller又是view奈应。這里是怎么體現(xiàn)的呢?看了代碼大家發(fā)現(xiàn)其中有一個(gè)progressDialog购披,在加載數(shù)據(jù)的時(shí)候顯示杖挣,加載完了以后取消,邏輯其實(shí)是view層的邏輯刚陡,但是這個(gè)我們沒辦法寫到xml里面啊惩妇,包括TextView.setTextView()株汉,這個(gè)也一樣。我們只能把這些邏輯寫到activity中屿附,這就造成了activity的臃腫郎逃,這個(gè)例子可能還好,如果是一個(gè)復(fù)雜的頁面呢挺份?大家自己想象一下褒翰。

MVP

通過具體的代碼大家知道了MVC在Android上是如何工作的,也知道了它的缺點(diǎn)匀泊,那MVP是如何修正的呢优训?

這里先向大家推薦github上的一個(gè)第三方庫,通過這個(gè)庫大家可以很輕松的實(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è)接口起什么作用呢搞监?還記得我之前說的嗎?MVP模式中镰矿,view層和presenter層靠的就是接口進(jìn)行連接琐驴,而具體的就是上面的這個(gè)了,里面定義的三個(gè)方法秤标,第一個(gè)是開始獲取數(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í)行的潘拨,可以看到,里面完完全全沒有任何和model層邏輯相關(guān)的東西饶号,只是簡單的委托給了presenter铁追,那我們再看看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);

}

publicvoidchange(){

ContributorView?view?=?getView();

if(view?!=null){

view.onChangeContributorName("zjutkz");

}

}

}

其實(shí)就是把剛才MVC中activity的那部分和model層相關(guān)的邏輯抽取了出來,并且在相應(yīng)的時(shí)機(jī)調(diào)用ContributorView接口對應(yīng)的方法茫船,而我們的activity是實(shí)現(xiàn)了這個(gè)接口的琅束,自然會(huì)走到對應(yīng)的方法中扭屁。

好了,我們來捋一捋涩禀。

首先料滥,和MVC最大的不同,MVP把a(bǔ)ctivity作為了view層艾船,通過代碼也可以看到葵腹,整個(gè)activity沒有任何和model層相關(guān)的邏輯代碼,取而代之的是把代碼放到了presenter層中屿岂,presenter獲取了model層的數(shù)據(jù)之后践宴,通過接口的形式將view層需要的數(shù)據(jù)返回給它就OK了。

這樣的好處是什么呢爷怀?首先阻肩,activity的代碼邏輯減少了,其次运授,view層和model層完全解耦烤惊,具體來說,如果你需要測試一個(gè)http請求是否順利吁朦,你不需要寫一個(gè)activity柒室,只需要寫一個(gè)java類,實(shí)現(xiàn)對應(yīng)的接口喇完,presenter獲取了數(shù)據(jù)自然會(huì)調(diào)用相應(yīng)的方法伦泥,相應(yīng)的剥啤,你也可以自己在presenter中mock數(shù)據(jù)锦溪,分發(fā)給view層,用來測試布局是否正確府怯。

MVVM

首先在看這段內(nèi)容之前刻诊,你需要保證你對data binding框架有基礎(chǔ)的了解。不了解的同學(xué)可以去看下這篇文章牺丙。在接下去讓我們開始探索MVVM则涯,MVVM最近在Android上可謂十分之火,最主要的原因就是谷歌推出了data binding這個(gè)框架冲簿,可以輕松的實(shí)現(xiàn)MVVM粟判。但是,我在網(wǎng)上查閱關(guān)于Android的data binding資料的時(shí)候峦剔,發(fā)現(xiàn)國內(nèi)有很多人都誤解了档礁,首先,我們從一篇錯(cuò)誤的文章開始吝沫。當(dāng)然我在這里引用這篇文章也是對事不對人呻澜,如果對文章的作者產(chǎn)生了不好的影響我這里說一聲抱歉递礼。

上面那篇文章是一個(gè)關(guān)于data binding的使用,看起來很美好羹幸,但是脊髓,其中有一個(gè)錯(cuò)誤可以說是非常,非常栅受,非常嚴(yán)重的将硝。

它竟然說data binding的viewmodel層是binding類,其實(shí)不止是這篇文章窘疮,其他有一些開發(fā)者寫的關(guān)于data binding的文章里都犯了一樣的錯(cuò)誤袋哼。大家如果也有這樣的概念,請務(wù)必糾正過來U⑸馈涛贯!

說完了錯(cuò)誤的概念,那data binding中真正的viewmodel是什么呢蔚出?我們還是以之前MVC弟翘,MVP的那個(gè)例子做引導(dǎo)。

首先是view層骄酗,這沒啥好說的稀余,和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);

}

}

如果你對data binding框架是有了解的睛琳,上面的代碼你能輕松的看懂。

那model層又是什么呢踏烙?當(dāng)然就是那些和數(shù)據(jù)相關(guān)的類师骗,GithubApi等等。

重點(diǎn)來了讨惩,viewmodel層呢辟癌?好吧,viewmodel層就是是Contributor類荐捻!大家不要驚訝黍少,我慢慢的來說。

[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)魂角。

我為什么說Contributor是一個(gè)viewmodel呢昵济。大家還記得viewmodel的概念嗎?view和viewmodel相互綁定在一起,viewmodel的改變會(huì)同步到view層砸紊,從而view層作出響應(yīng)传于。這不就是Contributor和xml中那些組件元素的關(guān)系嗎?所以醉顽,大家不要被binding類迷惑了沼溜,data binding框架中的viewmodel是自己定義的那些看似是model類的東西!比如這里的Contributor游添!

話說到這里系草,那binding類又是什么呢?其實(shí)具體對應(yīng)到之前MVVM的那張圖就很好理解了唆涝,我們想一下找都,binding類的工作是什么?

[js]view plaincopy

binding?=?DataBindingUtil.setContentView(this,?R.layout.mvvm_activity_main);

binding.setContributor(contributor);

首先廊酣,binding要通過DataBindingUtil.setContentView()方法將xml能耻,也就是view層設(shè)定。

接著亡驰,通過setXXX()方法將viewmodel層注入進(jìn)去晓猛。

由于這兩個(gè)工作,view層(xml的各個(gè)組件)和viewmodel層(contributor)綁定在了一起凡辱。

好了戒职,大家知道了嗎,binding類透乾,其實(shí)就是上圖中view和viewmodel中間的那根線昂樵铩!乳乌!

真理在荒謬被證實(shí)以前捧韵,都只是暗室里的裝飾

前面討論了MVC,MVP和MVVM具體的實(shí)現(xiàn)方案钦扭,大家肯定都了解了它們?nèi)叩年P(guān)系和使用方式纫版。但是床绪,這里我想說客情,不要把一個(gè)框架看作萬能的,其實(shí)MVP和MVVM都是有自己的缺陷的癞己!下面我一一來說膀斋。

MVP

MVP的問題在于,由于我們使用了接口的方式去連接view層和presenter層痹雅,這樣就導(dǎo)致了一個(gè)問題仰担,如果你有一個(gè)邏輯很復(fù)雜的頁面,你的接口會(huì)有很多绩社,十幾二十個(gè)都不足為奇摔蓝。想象一個(gè)app中有很多個(gè)這樣復(fù)雜的頁面赂苗,維護(hù)接口的成本就會(huì)非常的大。

這個(gè)問題的解決方案就是你得根據(jù)自己的業(yè)務(wù)邏輯去斟酌著寫接口贮尉。你可以定義一些基類接口拌滋,把一些公共的邏輯,比如網(wǎng)絡(luò)請求成功失敗猜谚,toast等等放在里面败砂,之后你再定義新的接口的時(shí)候可以繼承自那些基類,這樣會(huì)好不少魏铅。

MVVM

MVVM的問題呢昌犹,其實(shí)和MVC有一點(diǎn)像。data binding框架解決了數(shù)據(jù)綁定的問題览芳,但是view層還是會(huì)過重斜姥,大家可以看我上面那個(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);

}

}

大家有沒有發(fā)現(xiàn),activity在MVVM中應(yīng)該是view層的沧竟,但是里面卻和MVC一樣寫了對model的處理疾渴。有人會(huì)說你可以把對model的處理放到viewmodel層中,這樣不是更符合MVVM的設(shè)計(jì)理念嗎屯仗?這樣確實(shí)可以搞坝,但是progressDialog的show和dismiss呢?你怎么在viewmodel層中控制魁袜?這是view層的東西啊桩撮,而且在xml中也沒有,我相信會(huì)有解決的方案峰弹,但是我們有沒有一種更加便捷的方式呢店量?

路漫漫其修遠(yuǎn)兮,吾將上下而求索

其實(shí)鞠呈,真正的最佳實(shí)踐都是人想出來的融师,我們?yōu)楹尾唤Y(jié)合一下MVP和MVVM的特點(diǎn)呢?其實(shí)谷歌已經(jīng)做了這樣的事蚁吝,大家可以看下這個(gè)旱爆。沒錯(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é)省了類似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)镣隶。

說到這里极谊,我還是想重復(fù)剛才的那句話,最佳實(shí)踐都是人想出來的安岂,用這些框架根本的原因也是為了盡量低的耦合性和盡量高的可復(fù)用性轻猖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市域那,隨后出現(xiàn)的幾起案子咙边,更是在濱河造成了極大的恐慌,老刑警劉巖次员,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件败许,死亡現(xiàn)場離奇詭異,居然都是意外死亡淑蔚,警方通過查閱死者的電腦和手機(jī)市殷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刹衫,“玉大人醋寝,你說我怎么就攤上這事〈伲” “怎么了音羞?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長仓犬。 經(jīng)常有香客問我嗅绰,道長,這世上最難降的妖魔是什么搀继? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任窘面,我火速辦了婚禮,結(jié)果婚禮上律歼,老公的妹妹穿的比我還像新娘民镜。我一直安慰自己啡专,他們只是感情好险毁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般畔况。 火紅的嫁衣襯著肌膚如雪鲸鹦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天跷跪,我揣著相機(jī)與錄音馋嗜,去河邊找鬼。 笑死吵瞻,一個(gè)胖子當(dāng)著我的面吹牛葛菇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播橡羞,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼眯停,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卿泽?” 一聲冷哼從身側(cè)響起莺债,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎签夭,沒想到半個(gè)月后齐邦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡第租,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年措拇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慎宾。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儡羔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出璧诵,到底是詐尸還是另有隱情汰蜘,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布之宿,位于F島的核電站族操,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏比被。R本人自食惡果不足惜色难,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望等缀。 院中可真熱鬧枷莉,春花似錦、人聲如沸尺迂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹲盘,卻和暖如春股毫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背召衔。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工铃诬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苍凛。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓趣席,卻偏偏與公主長得像,于是被迫代替她去往敵國和親醇蝴。 傳聞我的和親對象是個(gè)殘疾皇子吩坝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容