Android之MVP 模式:简单易懂的介绍方式
轉(zhuǎn)載:https://segmentfault.com/a/1190000003927200
Android MVP Pattern
Android?MVP 模式1?也不是什么新鮮的東西了,我在自己的項(xiàng)目里也普遍地使用了這個(gè)設(shè)計(jì)模式。當(dāng)項(xiàng)目越來(lái)越龐大、復(fù)雜,參與的研發(fā)人員越來(lái)越多的時(shí)候,MVP 模式的優(yōu)勢(shì)就充分顯示出來(lái)了。
導(dǎo)讀:MVP模式是MVC模式在Android上的一種變體,要介紹MVP就得先介紹MVC。在MVC模式中,Activity應(yīng)該是屬于View這一層。而實(shí)質(zhì)上,它既承擔(dān)了View,同時(shí)也包含一些Controller的東西在里面。這對(duì)于開(kāi)發(fā)與維護(hù)來(lái)說(shuō)不太友好,耦合度大高了。把Activity的View和Controller抽離出來(lái)就變成了View和Presenter,這就是MVP模式。
基本信息
-
作者:Kaede
-
項(xiàng)目:Android-MVP-Pattern
-
出處:Android MVP模式 簡(jiǎn)單易懂的介紹方式
MVP模式(Model-View-Presenter)可以說(shuō)是MVC模式(Model-View-Controller)在Android開(kāi)發(fā)上的一種變種、進(jìn)化模式。后者大家可能比較熟悉,就算不熟悉也可能或多或少地在自己的項(xiàng)目中用到過(guò)。要介紹MVP模式,就不得不先說(shuō)說(shuō)MVC模式。
MVC模式
MVC模式的結(jié)構(gòu)分為三部分,實(shí)體層的Model,視圖層的View,以及控制層的Controller。
-
其中View層其實(shí)就是程序的UI界面,用于向用戶展示數(shù)據(jù)以及接收用戶的輸入
-
而Model層就是JavaBean實(shí)體類,用于保存實(shí)例數(shù)據(jù)
-
Controller控制器用于更新UI界面和數(shù)據(jù)實(shí)例
例如,View層接受用戶的輸入,然后通過(guò)Controller修改對(duì)應(yīng)的Model實(shí)例;同時(shí),當(dāng)Model實(shí)例的數(shù)據(jù)發(fā)生變化的時(shí)候,需要修改UI界面,可以通過(guò)Controller更新界面。(View層也可以直接更新Model實(shí)例的數(shù)據(jù),而不用每次都通過(guò)Controller,這樣對(duì)于一些簡(jiǎn)單的數(shù)據(jù)更新工作會(huì)變得方便許多。)
舉個(gè)簡(jiǎn)單的例子,現(xiàn)在要實(shí)現(xiàn)一個(gè)飄雪的動(dòng)態(tài)壁紙,可以給雪花定義一個(gè)實(shí)體類Snow,里面存放XY軸坐標(biāo)數(shù)據(jù),View層當(dāng)然就是SurfaceView(或者其他視圖),為了實(shí)現(xiàn)雪花飄的效果,可以啟動(dòng)一個(gè)后臺(tái)線程,在線程里不斷更新Snow實(shí)例里的坐標(biāo)值,這部分就是Controller的工作了,Controller里還要定時(shí)更新SurfaceView上面的雪花。進(jìn)一步的話,可以在SurfaceView上監(jiān)聽(tīng)用戶的點(diǎn)擊,如果用戶點(diǎn)擊,只通過(guò)Controller對(duì)觸摸點(diǎn)周圍的Snow的坐標(biāo)值進(jìn)行調(diào)整,從而實(shí)現(xiàn)雪花在用戶點(diǎn)擊后出現(xiàn)彈開(kāi)等效果。具體的MVC模式請(qǐng)自行Google。
MVP模式
在Android項(xiàng)目中,Activity和Fragment占據(jù)了大部分的開(kāi)發(fā)工作。如果有一種設(shè)計(jì)模式(或者說(shuō)代碼結(jié)構(gòu))專門是為優(yōu)化Activity和Fragment的代碼而產(chǎn)生的,你說(shuō)這種模式重要不?這就是MVP設(shè)計(jì)模式。
按照MVC的分層,Activity和Fragment(后面只說(shuō)Activity)應(yīng)該屬于View層,用于展示UI界面,以及接收用戶的輸入,此外還要承擔(dān)一些生命周期的工作。Activity是在Android開(kāi)發(fā)中充當(dāng)非常重要的角色,特別是TA的生命周期的功能,所以開(kāi)發(fā)的時(shí)候我們經(jīng)常把一些業(yè)務(wù)邏輯直接寫在Activity里面,這非常直觀方便,代價(jià)就是Activity會(huì)越來(lái)越臃腫,超過(guò)1000行代碼是常有的事,而且如果是一些可以通用的業(yè)務(wù)邏輯(比如用戶登錄),寫在具體的Activity里就意味著這個(gè)邏輯不能復(fù)用了。如果有進(jìn)行代碼重構(gòu)經(jīng)驗(yàn)的人,看到1000+行的類肯定會(huì)有所顧慮。因此,Activity不僅承擔(dān)了View的角色,還承擔(dān)了一部分的Controller角色,這樣一來(lái)V和C就耦合在一起了,雖然這樣寫方便,但是如果業(yè)務(wù)調(diào)整的話,要維護(hù)起來(lái)就難了,而且在一個(gè)臃腫的Activity類查找業(yè)務(wù)邏輯的代碼也會(huì)非常蛋疼,所以看起來(lái)有必要在Activity中,把View和Controller抽離開(kāi)來(lái),而這就是MVP模式的工作了。
MVP模式的核心思想:
MVP把Activity中的UI邏輯抽象成View接口,把業(yè)務(wù)邏輯抽象成Presenter接口,Model類還是原來(lái)的Model。
這就是MVP模式,現(xiàn)在這樣的話,Activity的工作的簡(jiǎn)單了,只用來(lái)響應(yīng)生命周期,其他工作都丟到Presenter中去完成。從上圖可以看出,Presenter是Model和View之間的橋梁,為了讓結(jié)構(gòu)變得更加簡(jiǎn)單,View并不能直接對(duì)Model進(jìn)行操作,這也是MVP與MVC最大的不同之處。
MVP模式的作用
MVP的好處都有啥,誰(shuí)說(shuō)對(duì)了就給他 KIRA!!(<ゝω·)☆
-
分離了視圖邏輯和業(yè)務(wù)邏輯,降低了耦合
-
Activity只處理生命周期的任務(wù),代碼變得更加簡(jiǎn)潔
-
視圖邏輯和業(yè)務(wù)邏輯分別抽象到了View和Presenter的接口中去,提高代碼的可閱讀性
-
Presenter被抽象成接口,可以有多種具體的實(shí)現(xiàn),所以方便進(jìn)行單元測(cè)試
-
把業(yè)務(wù)邏輯抽到Presenter中去,避免后臺(tái)線程引用著Activity導(dǎo)致Activity的資源無(wú)法被系統(tǒng)回收從而引起內(nèi)存泄露和OOM
其中最重要的有三點(diǎn):
Activity 代碼變得更加簡(jiǎn)潔
相信很多人閱讀代碼的時(shí)候,都是從Activity開(kāi)始的,對(duì)著一個(gè)1000+行代碼的Activity,看了都覺(jué)得難受。
使用MVP之后,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其他的就是對(duì)Presenter的調(diào)用,還有對(duì)View接口的實(shí)現(xiàn)。這種情形下閱讀代碼就容易多了,而且你只要看Presenter的接口,就能明白這個(gè)模塊都有哪些業(yè)務(wù),很快就能定位到具體代碼。Activity變得容易看懂,容易維護(hù),以后要調(diào)整業(yè)務(wù)、刪減功能也就變得簡(jiǎn)單許多。
方便進(jìn)行單元測(cè)試
一般單元測(cè)試都是用來(lái)測(cè)試某些新加的業(yè)務(wù)邏輯有沒(méi)有問(wèn)題,如果采用傳統(tǒng)的代碼風(fēng)格(習(xí)慣性上叫做MV模式,少了P),我們可能要先在Activity里寫一段測(cè)試代碼,測(cè)試完了再把測(cè)試代碼刪掉換成正式代碼,這時(shí)如果發(fā)現(xiàn)業(yè)務(wù)有問(wèn)題又得換回測(cè)試代碼,咦,測(cè)試代碼已經(jīng)刪掉了!好吧重新寫吧……
MVP中,由于業(yè)務(wù)邏輯都在Presenter里,我們完全可以寫一個(gè)PresenterTest的實(shí)現(xiàn)類繼承Presenter的接口,現(xiàn)在只要在Activity里把Presenter的創(chuàng)建換成PresenterTest,就能進(jìn)行單元測(cè)試了,測(cè)試完再換回來(lái)即可。萬(wàn)一發(fā)現(xiàn)還得進(jìn)行測(cè)試,那就再換成PresenterTest吧。
避免 Activity 的內(nèi)存泄露
Android APP 發(fā)生OOM的最大原因就是出現(xiàn)內(nèi)存泄露造成APP的內(nèi)存不夠用,而造成內(nèi)存泄露的兩大原因之一就是Activity泄露(Activity Leak)(另一個(gè)原因是Bitmap泄露(Bitmap Leak))。
Java一個(gè)強(qiáng)大的功能就是其虛擬機(jī)的內(nèi)存回收機(jī)制,這個(gè)功能使得Java用戶在設(shè)計(jì)代碼的時(shí)候,不用像C++用戶那樣考慮對(duì)象的回收問(wèn)題。然而,Java用戶總是喜歡隨便寫一大堆對(duì)象,然后幻想著虛擬機(jī)能幫他們處理好內(nèi)存的回收工作。可是虛擬機(jī)在回收內(nèi)存的時(shí)候,只會(huì)回收那些沒(méi)有被引用的對(duì)象,被引用著的對(duì)象因?yàn)檫€可能會(huì)被調(diào)用,所以不能回收。
Activity是有生命周期的,用戶隨時(shí)可能切換Activity,當(dāng)APP的內(nèi)存不夠用的時(shí)候,系統(tǒng)會(huì)回收處于后臺(tái)的Activity的資源以避免OOM。
采用傳統(tǒng)的MV模式,一大堆異步任務(wù)和對(duì)UI的操作都放在Activity里面,比如你可能從網(wǎng)絡(luò)下載一張圖片,在下載成功的回調(diào)里把圖片加載到 Activity 的 ImageView 里面,所以異步任務(wù)保留著對(duì)Activity的引用。這樣一來(lái),即使Activity已經(jīng)被切換到后臺(tái)(onDestroy已經(jīng)執(zhí)行),這些異步任務(wù)仍然保留著對(duì)Activity實(shí)例的引用,所以系統(tǒng)就無(wú)法回收這個(gè)Activity實(shí)例了,結(jié)果就是Activity Leak。Android的組件中,Activity對(duì)象往往是在堆(Java Heap)里占最多內(nèi)存的,所以系統(tǒng)會(huì)優(yōu)先回收Activity對(duì)象,如果有Activity Leak,APP很容易因?yàn)閮?nèi)存不夠而OOM。
采用MVP模式,只要在當(dāng)前的Activity的onDestroy里,分離異步任務(wù)對(duì)Activity的引用,就能避免 Activity Leak。
說(shuō)了這么多,沒(méi)看懂?好吧,我自己都沒(méi)看懂自己寫的,我們還是直接看代碼吧。
MVP模式的使用
上面一張簡(jiǎn)單的MVP模式的UML圖,從圖中可以看出,使用MVP,至少需要經(jīng)歷以下步驟:
創(chuàng)建IPresenter接口,把所有業(yè)務(wù)邏輯的接口都放在這里,并創(chuàng)建它的實(shí)現(xiàn)PresenterCompl(在這里可以方便地查看業(yè)務(wù)功能,由于接口可以有多種實(shí)現(xiàn)所以也方便寫單元測(cè)試)
創(chuàng)建IView接口,把所有視圖邏輯的接口都放在這里,其實(shí)現(xiàn)類是當(dāng)前的Activity/Fragment
由UML圖可以看出,Activity里包含了一個(gè)IPresenter,而PresenterCompl里又包含了一個(gè)IView并且依賴了Model。Activity里只保留對(duì)IPresenter的調(diào)用,其它工作全部留到PresenterCompl中實(shí)現(xiàn)
Model并不是必須有的,但是一定會(huì)有View和Presenter
通過(guò)上面的介紹,MVP的主要特點(diǎn)就是把Activity里的許多邏輯都抽離到View和Presenter接口中去,并由具體的實(shí)現(xiàn)類來(lái)完成。這種寫法多了許多IView和IPresenter的接口,在某種程度上加大了開(kāi)發(fā)的工作量,剛開(kāi)始使用MVP的小伙伴可能會(huì)覺(jué)得這種寫法比較別扭,而且難以記住。其實(shí)一開(kāi)始想太多也沒(méi)有什么卵用,只要在具體項(xiàng)目中多寫幾次,就能熟悉MVP模式的寫法,理解TA的意圖,以及享♂受其帶來(lái)的好處。
扯了這么多,但是好像并沒(méi)有什么卵用,畢竟
Talk is cheap, let me show you the code!
所以還是來(lái)寫一下實(shí)際的項(xiàng)目吧。
MVP模式簡(jiǎn)單實(shí)例
一個(gè)簡(jiǎn)單的登錄界面(實(shí)在想不到別的了╮( ̄▽ ̄")╭),點(diǎn)擊LOGIN則進(jìn)行賬號(hào)密碼驗(yàn)證,點(diǎn)擊CLEAR則重置輸入。
項(xiàng)目結(jié)構(gòu)看起來(lái)像是這個(gè)樣子的,MVP的分層還是很清晰的。我的習(xí)慣是先按模塊分Package,在模塊下面再去創(chuàng)建model、view、presenter的子Package,當(dāng)然也可以用model、view、presenter作為頂級(jí)的Package,然后把所有的模塊的model、view、presenter類都到這三個(gè)頂級(jí)Package中,就好像有人喜歡把項(xiàng)目里所有的Activity、Fragment、Adapter都放在一起一樣。
首先來(lái)看看LoginActivity
public class LoginActivity extends ActionBarActivity implements ILoginView, View.OnClickListener {private EditText editUser;private EditText editPass;private Button btnLogin;private Button btnClear;ILoginPresenter loginPresenter;private ProgressBar progressBar;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//find vieweditUser = (EditText) this.findViewById(R.id.et_login_username);editPass = (EditText) this.findViewById(R.id.et_login_password);btnLogin = (Button) this.findViewById(R.id.btn_login_login);btnClear = (Button) this.findViewById(R.id.btn_login_clear);progressBar = (ProgressBar) this.findViewById(R.id.progress_login);//set listenerbtnLogin.setOnClickListener(this);btnClear.setOnClickListener(this);//initloginPresenter = new LoginPresenterCompl(this);loginPresenter.setProgressBarVisiblity(View.INVISIBLE);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.btn_login_clear:loginPresenter.clear();break;case R.id.btn_login_login:loginPresenter.setProgressBarVisiblity(View.VISIBLE);btnLogin.setEnabled(false);btnClear.setEnabled(false);loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());break;}}@Overridepublic void onClearText() {editUser.setText("");editPass.setText("");}@Overridepublic void onLoginResult(Boolean result, int code) {loginPresenter.setProgressBarVisiblity(View.INVISIBLE);btnLogin.setEnabled(true);btnClear.setEnabled(true);if (result){Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();startActivity(new Intent(this, HomeActivity.class));}elseToast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();}@Overridepublic void onSetProgressBarVisibility(int visibility) {progressBar.setVisibility(visibility);} }
從代碼可以看出LoginActivity只做了findView以及setListener的工作,而且包含了一個(gè)ILoginPresenter,所有業(yè)務(wù)邏輯都是通過(guò)調(diào)用ILoginPresenter的具體接口來(lái)完成。所以LoginActivity的代碼看起來(lái)很舒爽,甚至有點(diǎn)愉♂悅呢 (/ω\*)。視力不錯(cuò)的你可能還看到了ILoginView接口的實(shí)現(xiàn),如果不懂為什么要這樣寫的話,可以先往下看,這里只要記住LoginActivity實(shí)現(xiàn)了ILoginView接口。
再來(lái)看看ILoginPresenter
public interface ILoginPresenter {void clear();void doLogin(String name, String passwd);void setProgressBarVisiblity(int visiblity); } public class LoginPresenterCompl implements ILoginPresenter {ILoginView iLoginView;IUser user;Handler handler;public LoginPresenterCompl(ILoginView iLoginView) {this.iLoginView = iLoginView;initUser();handler = new Handler(Looper.getMainLooper());}@Overridepublic void clear() {iLoginView.onClearText();}@Overridepublic void doLogin(String name, String passwd) {Boolean isLoginSuccess = true;final int code = user.checkUserValidity(name,passwd);if (code!=0) isLoginSuccess = false;final Boolean result = isLoginSuccess;handler.postDelayed(new Runnable() {@Overridepublic void run() {iLoginView.onLoginResult(result, code);}}, 3000);}@Overridepublic void setProgressBarVisiblity(int visiblity){iLoginView.onSetProgressBarVisibility(visiblity);}private void initUser(){user = new UserModel("mvp","mvp");} } 從代碼可以看出,LoginPresenterCompl保留了ILoginView的引用,因此在LoginPresenterCompl里就可以直接進(jìn)行UI操作了,而不用在Activity里完成。這里使用了ILoginView引用,而不是直接使用Activity,這樣一來(lái),如果在別的Activity里也需要用到相同的業(yè)務(wù)邏輯,就可以直接復(fù)用LoginPresenterCompl類了(一個(gè)Activity可以包含一個(gè)以上的Presenter,總之,需要什么業(yè)務(wù)就new什么樣的Presenter,是不是很靈活(@ ̄︶ ̄@)),這也是MVP的核心思想通過(guò)IVIew和IPresenter,把Activity的UI Logic和Business Logic分離開(kāi)來(lái),Activity just does its basic job! 至于Model嘛,還是原來(lái)MVC里的Model。
再來(lái)看看ILoginView,至于ILoginView的實(shí)現(xiàn)類呢,翻到上面看看LoginActivity吧
public interface ILoginView {public void onClearText();public void onLoginResult(Boolean result, int code);public void onSetProgressBarVisibility(int visibility); } 代碼這種東西放在日志里講好像除了把整個(gè)版面拉長(zhǎng)沒(méi)什么卵用,我把幾種自己常用的MVP的寫法寫成一個(gè)Demo項(xiàng)目,歡迎圍觀和PullRequest: Android-MVP-Pattern 。
后記
以上就是我的MVP模式的一點(diǎn)理解,在MVVM模式還沒(méi)有成熟的現(xiàn)在,我覺(jué)得沒(méi)有比MVP模式更好的替代品了。當(dāng)然今天寫的只是MVP的基礎(chǔ)使用,介紹的實(shí)例項(xiàng)目也非常簡(jiǎn)單,看不出MVP的優(yōu)勢(shì),后面還會(huì)針對(duì)MVP模式寫一些日志,就目前能想到的至少包括
-
目前我們寫ListView的Adapter都喜歡把它寫成一個(gè)內(nèi)部類,如果有兩個(gè)Activity里要用同一個(gè)Adapter就比較難了,通過(guò)MVP模式,能輕松地復(fù)用Adapter(你說(shuō)已經(jīng)不用ListView了,這不是重點(diǎn)不是么( ??? ))
-
MVP模式需要多寫許多新的接口,這也是其缺點(diǎn)所在,經(jīng)過(guò)一段時(shí)間的實(shí)戰(zhàn),我自己已有一種優(yōu)化的MVP模式,我會(huì)試著總結(jié)一下,把她拿出來(lái)說(shuō)說(shuō)
總結(jié)
以上是生活随笔為你收集整理的Android之MVP 模式:简单易懂的介绍方式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android之如果解决Android
- 下一篇: Android之基于xmpp openf