Android 中的MVP設(shè)計(jì)模式

MVP模式的核心思想:

MVP把Activity中的UI邏輯抽象成View接口写半,把業(yè)務(wù)邏輯抽象成功接口又碌,Model類(lèi)還是原來(lái)的Model九昧。

MVC

  • 其中View層其實(shí)就是程序的UI界面,用于向用戶(hù)展示數(shù)據(jù)以及接收用戶(hù)的輸入
  • 而Model層就是JavaBean實(shí)體類(lèi)毕匀,用于保存實(shí)例數(shù)據(jù)
  • Controller控制器用于更新UI界面和數(shù)據(jù)實(shí)例

View層接受用戶(hù)的輸入耽装,然后通過(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ì)變得方便許多缤苫。

MVP

MVP與MVC最不同的一點(diǎn)是M與V是不直接關(guān)聯(lián)的也是就Model與View不存在直接關(guān)系速兔,這兩者之間間隔著的是Presenter層

Model

Model 是用戶(hù)界面需要顯示數(shù)據(jù)的抽象,也可以理解為從業(yè)務(wù)數(shù)據(jù)(結(jié)果)那里到用戶(hù)界面的抽象(Business rule, data access, model classes)

View

視圖這一層體現(xiàn)的很輕薄活玲,負(fù)責(zé)顯示數(shù)據(jù)涣狗、提供友好界面跟用戶(hù)交互就行谍婉。MVP下Activity和Fragment體現(xiàn)在了這一層,Activity一般也就做加載UI視圖镀钓、設(shè)置監(jiān)聽(tīng)再交由Presenter處理的一些工作穗熬,所以也就需要持有相應(yīng)Presenter的引用。例如丁溅,Activity上滾動(dòng)列表時(shí)隱藏或者顯示Acionbar(Toolbar)唤蔗,這樣的UI邏輯時(shí)也應(yīng)該在這一層。另外在View上輸入的數(shù)據(jù)做一些判斷時(shí)窟赏,例如妓柜,EditText的輸入數(shù)據(jù),假如是簡(jiǎn)單的非空判斷則可以作為View層的邏輯涯穷,而當(dāng)需要對(duì)EditText的數(shù)據(jù)進(jìn)行更復(fù)雜的比較時(shí)棍掐,如從數(shù)據(jù)庫(kù)獲取本地?cái)?shù)據(jù)進(jìn)行判斷時(shí)明顯需要經(jīng)過(guò)Model層才能返回了藏姐,所以這些細(xì)節(jié)需要自己掂量氓扛。

Presenter

Presenter這一層處理著程序各種邏輯的分發(fā),收到View層UI上的反饋命令妙真、定時(shí)命令蝠嘉、系統(tǒng)命令等指令后分發(fā)處理邏輯交由業(yè)務(wù)層做具體的業(yè)務(wù)操作,然后將得到的 Model 給 View 顯示杯巨。

這就是MVP模式蚤告,現(xiàn)在這樣的話(huà),Activity的工作的簡(jiǎn)單了服爷,只用來(lái)響應(yīng)生命周期杜恰,其他工作都丟到Presenter中去完成。從上圖可以看出仍源,Presenter是Model和View之間的橋梁心褐,為了讓結(jié)構(gòu)變得更加簡(jiǎn)單,View并不能直接對(duì)Model進(jìn)行操作笼踩,這也是MVP與MVC最大的不同之處逗爹。

優(yōu)點(diǎn)

  • 分離了視圖邏輯和業(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

代碼變得更加簡(jiǎn)潔

使用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è)試

MVP中魏割,由于業(yè)務(wù)邏輯都在Presenter里譬嚣,我們完全可以寫(xiě)一個(gè)PresenterTest的實(shí)現(xiàn)類(lèi)繼承Presenter的接口,現(xiàn)在只要在Activity里把Presenter的創(chuàng)建換成PresenterTest钞它,就能進(jìn)行單元測(cè)試了拜银,測(cè)試完再換回來(lái)即可。萬(wàn)一發(fā)現(xiàn)還得進(jìn)行測(cè)試遭垛,那就再換成PresenterTest吧尼桶。

避免內(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用戶(hù)在設(shè)計(jì)代碼的時(shí)候泵督,不用像C++用戶(hù)那樣考慮對(duì)象的回收問(wèn)題。然而庶喜,Java用戶(hù)總是喜歡隨便寫(xiě)一大堆對(duì)象小腊,然后幻想著虛擬機(jī)能幫他們處理好內(nèi)存的回收工作【每撸可是虛擬機(jī)在回收內(nèi)存的時(shí)候秩冈,只會(huì)回收那些沒(méi)有被引用的對(duì)象,被引用著的對(duì)象因?yàn)檫€可能會(huì)被調(diào)用斥扛,所以不能回收入问。

Activity是有生命周期的,用戶(hù)隨時(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。

MVP 使用

MVP的主要特點(diǎn)就是把Activity里的許多邏輯都抽離到View和Presenter接口中去蒿偎,并由具體的實(shí)現(xiàn)類(lèi)來(lái)完成朽们。

  1. 創(chuàng)建IPresenter接口,把所有業(yè)務(wù)邏輯的接口都放在這里诉位,并創(chuàng)建它的實(shí)現(xiàn)PresenterCompl(在這里可以方便地查看業(yè)務(wù)功能骑脱,由于接口可以有多種實(shí)現(xiàn)所以也方便寫(xiě)單元測(cè)試),IPresenter持有 IView,調(diào)用 IView 中的方法
  2. 創(chuàng)建IView接口苍糠,把所有視圖邏輯的接口都放在這里叁丧,其實(shí)現(xiàn)類(lèi)是當(dāng)前的Activity/Fragment
  3. 由UML圖可以看出,Activity里包含了一個(gè)IPresenter椿息,而PresenterCompl里又包含了一個(gè)IView并且依賴(lài)了Model。Activity里只保留對(duì)IPresenter的調(diào)用坷衍,其它工作全部留到PresenterCompl中實(shí)現(xiàn)
  4. Model并不是必須有的寝优,但是一定會(huì)有View和Presenter

DMEO

簡(jiǎn)單的登陸界面的例子

  • 登陸 view 接口
package io.github.xuyushi.androidmvpdemo.Login.view;

/**
* Created by xuyushi on 16/2/28.
*/
public interface ILoginView {
void clearEditText();

void showProgress();

void hideProgress();

void setUsernameError();

void setPasswordError();

String getUsername();

String getPassword();

void loginSuccess();

}

登陸Presenter接口

package io.github.xuyushi.androidmvpdemo.Login.presenter;

/**
* Created by xuyushi on 16/2/28.
*/
public interface ILoginPresenter {
void doLogin(String username, String password);

void clear();

void onDestroy();
}

實(shí)現(xiàn)Presenter接口

package io.github.xuyushi.androidmvpdemo.Login.presenter;

import android.os.Handler;

import io.github.xuyushi.androidmvpdemo.Login.model.User;
import io.github.xuyushi.androidmvpdemo.Login.view.ILoginView;

/**
* Created by xuyushi on 16/2/28.
*/
public class LoginPresenter implements ILoginPresenter {
private ILoginView mLoginView;
private User mUser;

public LoginPresenter(ILoginView loginView) {
this.mLoginView = loginView;
initUser();
}

private void initUser() {
mUser = new User(mLoginView.getUsername(), mLoginView.getPassword());
}

@Override
public void doLogin(String username, String password) {
mLoginView.showProgress();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mLoginView.hideProgress();
int code = mUser.checkUserValidity(mLoginView.getUsername(), mLoginView.getPassword());
if (code == -1) {
mLoginView.setPasswordError();
} else if (code == 0) {
mLoginView.loginSuccess();
}
}
}, 2000);
}

@Override
public void clear() {
mLoginView.clearEditText();
}

@Override
public void onDestroy() {
mLoginView = null;
}
}

定義model

package io.github.xuyushi.androidmvpdemo.Login.model;

/**
* Created by xuyushi on 16/2/28.
*/
public class User {
private String username;
private String password;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}


public User(String username, String password) {
this.username = username;
this.password = password;
}

public int checkUserValidity(String username, String password) {
if (username == null || password == null ||
username.isEmpty() ||
password.isEmpty()) {
return -1;
}
return 0;
}
}

在 Activity 中實(shí)現(xiàn) view接口

package io.github.xuyushi.androidmvpdemo.Login.view;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import butterknife.Bind;
import butterknife.ButterKnife;
import io.github.xuyushi.androidmvpdemo.Login.presenter.ILoginPresenter;
import io.github.xuyushi.androidmvpdemo.Login.presenter.LoginPresenter;
import io.github.xuyushi.androidmvpdemo.R;

public class LoginActivity extends AppCompatActivity
implements ILoginView, View.OnClickListener {

private ILoginPresenter mLoginPresenter;

@Bind(R.id.et_username)
EditText etUsername;
@Bind(R.id.et_passwrod)
EditText etPasswrod;
@Bind(R.id.bt_enter)
Button btEnter;
@Bind(R.id.bt_clear)
Button btClear;
@Bind(R.id.progress)
ProgressBar progress;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mLoginPresenter = new LoginPresenter(this);
btEnter.setOnClickListener(this);
btClear.setOnClickListener(this);
}

@Override
public void clearEditText() {
etPasswrod.setText("");
etUsername.setText("");
}

@Override
public void showProgress() {
progress.setVisibility(View.VISIBLE);
}

@Override
public void hideProgress() {
progress.setVisibility(View.GONE);
}

@Override
public void setUsernameError() {
etUsername.setError("username error");
}

@Override
public void setPasswordError() {
etPasswrod.setError("password error");

}

@Override
public String getUsername() {
return etUsername.getText().toString();
}

@Override
public String getPassword() {
return etPasswrod.getText().toString();
}

@Override
public void loginSuccess() {
//start act Main
Toast.makeText(this, "login success", Toast.LENGTH_SHORT);
finish();
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_clear:
mLoginPresenter.clear();
break;
case R.id.bt_enter:
mLoginPresenter.doLogin(etUsername.getText().toString(),
etPasswrod.getText().toString());
break;
}
}

@Override
protected void onDestroy() {
mLoginPresenter.onDestroy();
super.onDestroy();
}
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市枫耳,隨后出現(xiàn)的幾起案子乏矾,更是在濱河造成了極大的恐慌,老刑警劉巖迁杨,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钻心,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡铅协,警方通過(guò)查閱死者的電腦和手機(jī)捷沸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狐史,“玉大人痒给,你說(shuō)我怎么就攤上這事说墨。” “怎么了苍柏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵尼斧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我试吁,道長(zhǎng)棺棵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任熄捍,我火速辦了婚禮烛恤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘治唤。我一直安慰自己棒动,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布宾添。 她就那樣靜靜地躺著船惨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缕陕。 梳的紋絲不亂的頭發(fā)上粱锐,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音扛邑,去河邊找鬼怜浅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蔬崩,可吹牛的內(nèi)容都是我干的恶座。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沥阳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跨琳!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起桐罕,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤脉让,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后功炮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體溅潜,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年薪伏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滚澜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嫁怀,死狀恐怖博秫,靈堂內(nèi)的尸體忽然破棺而出潦牛,到底是詐尸還是另有隱情,我是刑警寧澤挡育,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布巴碗,位于F島的核電站,受9級(jí)特大地震影響即寒,放射性物質(zhì)發(fā)生泄漏橡淆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一母赵、第九天 我趴在偏房一處隱蔽的房頂上張望逸爵。 院中可真熱鬧,春花似錦凹嘲、人聲如沸师倔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)趋艘。三九已至,卻和暖如春凶朗,著一層夾襖步出監(jiān)牢的瞬間瓷胧,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工棚愤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搓萧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓宛畦,卻偏偏與公主長(zhǎng)得像瘸洛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子次和,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 最近稍微了解了下MVP架構(gòu)模式斯够,這篇文章寫(xiě)得不錯(cuò)囚玫,轉(zhuǎn)過(guò)來(lái)mark下:原博客原地址:http://www.jians...
    Stan_Z閱讀 1,171評(píng)論 0 8
  • 原文地址:MODEL VIEW PRESENTER (MVP) IN ANDROID, PART 1 本系列文章的...
    Ted熊閱讀 1,991評(píng)論 0 8
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理喧锦,服務(wù)發(fā)現(xiàn)读规,斷路器,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • 前言 MVC燃少、MVP束亏、MVVM一直以來(lái)都是Android應(yīng)用常見(jiàn)的架構(gòu)模式,都是為了抽離出UI邏輯和業(yè)務(wù)邏輯阵具。但是...
    希格斯子閱讀 1,173評(píng)論 0 1
  • 商女不知亡國(guó)恨碍遍,一天到晚碼代碼定铜。 煙籠寒水月籠沙,為碼代碼不回家怕敬。 舉頭望明月揣炕,低頭碼代碼。 少壯不努力东跪,老大碼代...
    panrusheng閱讀 558評(píng)論 1 0