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