Android 中的MVP設計模式

MVP模式的核心思想:

MVP把Activity中的UI邏輯抽象成View接口,把業務邏輯抽象成功接口,Model類還是原來的Model。

MVC

  • 其中View層其實就是程序的UI界面,用于向用戶展示數據以及接收用戶的輸入
  • 而Model層就是JavaBean實體類,用于保存實例數據
  • Controller控制器用于更新UI界面和數據實例

View層接受用戶的輸入,然后通過Controller修改對應的Model實例;同時,當Model實例的數據發生變化的時候,需要修改UI界面,可以通過Controller更新界面。View層也可以直接更新Model實例的數據,而不用每次都通過Controller,這樣對于一些簡單的數據更新工作會變得方便許多。

MVP

MVP與MVC最不同的一點是M與V是不直接關聯的也是就Model與View不存在直接關系,這兩者之間間隔著的是Presenter層

Model

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

View

視圖這一層體現的很輕薄,負責顯示數據、提供友好界面跟用戶交互就行。MVP下Activity和Fragment體現在了這一層,Activity一般也就做加載UI視圖、設置監聽再交由Presenter處理的一些工作,所以也就需要持有相應Presenter的引用。例如,Activity上滾動列表時隱藏或者顯示Acionbar(Toolbar),這樣的UI邏輯時也應該在這一層。另外在View上輸入的數據做一些判斷時,例如,EditText的輸入數據,假如是簡單的非空判斷則可以作為View層的邏輯,而當需要對EditText的數據進行更復雜的比較時,如從數據庫獲取本地數據進行判斷時明顯需要經過Model層才能返回了,所以這些細節需要自己掂量。

Presenter

Presenter這一層處理著程序各種邏輯的分發,收到View層UI上的反饋命令、定時命令、系統命令等指令后分發處理邏輯交由業務層做具體的業務操作,然后將得到的 Model 給 View 顯示。

這就是MVP模式,現在這樣的話,Activity的工作的簡單了,只用來響應生命周期,其他工作都丟到Presenter中去完成。從上圖可以看出,Presenter是Model和View之間的橋梁,為了讓結構變得更加簡單,View并不能直接對Model進行操作,這也是MVP與MVC最大的不同之處

優點

  • 分離了視圖邏輯和業務邏輯,降低了耦合
  • Activity只處理生命周期的任務,代碼變得更加簡潔
  • 視圖邏輯和業務邏輯分別抽象到了View和Presenter的接口中去,提高代碼的可閱讀性
  • Presenter被抽象成接口,可以有多種具體的實現,所以方便進行單元測試
  • 把業務邏輯抽到Presenter中去,避免后臺線程引用著Activity導致Activity的資源無法被系統回收從而引起內存泄露和OOM

代碼變得更加簡潔

使用MVP之后,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其他的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,而且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,以后要調整業務、刪減功能也就變得簡單許多。

方便進行單元測試

MVP中,由于業務邏輯都在Presenter里,我們完全可以寫一個PresenterTest的實現類繼承Presenter的接口,現在只要在Activity里把Presenter的創建換成PresenterTest,就能進行單元測試了,測試完再換回來即可。萬一發現還得進行測試,那就再換成PresenterTest吧。

避免內存泄露

Android APP 發生OOM的最大原因就是出現內存泄露造成APP的內存不夠用,而造成內存泄露的兩大原因之一就是Activity泄露(Activity Leak)(另一個原因是Bitmap泄露(Bitmap Leak))

Java一個強大的功能就是其虛擬機的內存回收機制,這個功能使得Java用戶在設計代碼的時候,不用像C++用戶那樣考慮對象的回收問題。然而,Java用戶總是喜歡隨便寫一大堆對象,然后幻想著虛擬機能幫他們處理好內存的回收工作。可是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用著的對象因為還可能會被調用,所以不能回收。

Activity是有生命周期的,用戶隨時可能切換Activity,當APP的內存不夠用的時候,系統會回收處于后臺的Activity的資源以避免OOM。

采用傳統的MV模式,一大堆異步任務和對UI的操作都放在Activity里面,比如你可能從網絡下載一張圖片,在下載成功的回調里把圖片加載到 Activity 的 ImageView 里面,所以異步任務保留著對Activity的引用。這樣一來,即使Activity已經被切換到后臺(onDestroy已經執行),這些異步任務仍然保留著對Activity實例的引用,所以系統就無法回收這個Activity實例了,結果就是Activity Leak。Android的組件中,Activity對象往往是在堆(Java Heap)里占最多內存的,所以系統會優先回收Activity對象,如果有Activity Leak,APP很容易因為內存不夠而OOM。

采用MVP模式,只要在當前的Activity的onDestroy里,分離異步任務對Activity的引用,就能避免 Activity Leak。

MVP 使用

MVP的主要特點就是把Activity里的許多邏輯都抽離到View和Presenter接口中去,并由具體的實現類來完成。

  1. 創建IPresenter接口,把所有業務邏輯的接口都放在這里,并創建它的實現PresenterCompl(在這里可以方便地查看業務功能,由于接口可以有多種實現所以也方便寫單元測試),IPresenter持有 IView,調用 IView 中的方法
  2. 創建IView接口,把所有視圖邏輯的接口都放在這里,其實現類是當前的Activity/Fragment
  3. 由UML圖可以看出,Activity里包含了一個IPresenter,而PresenterCompl里又包含了一個IView并且依賴了Model。Activity里只保留對IPresenter的調用,其它工作全部留到PresenterCompl中實現
  4. Model并不是必須有的,但是一定會有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();
}

實現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 中實現 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();
}
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 最近稍微了解了下MVP架構模式,這篇文章寫得不錯,轉過來mark下:原博客原地址:http://www.jians...
    Stan_Z閱讀 1,206評論 0 8
  • 原文地址:MODEL VIEW PRESENTER (MVP) IN ANDROID, PART 1 本系列文章的...
    Ted熊閱讀 2,000評論 0 8
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 前言 MVC、MVP、MVVM一直以來都是Android應用常見的架構模式,都是為了抽離出UI邏輯和業務邏輯。但是...
    希格斯子閱讀 1,179評論 0 1
  • 商女不知亡國恨,一天到晚碼代碼。 煙籠寒水月籠沙,為碼代碼不回家。 舉頭望明月,低頭碼代碼。 少壯不努力,老大碼代...
    panrusheng閱讀 582評論 1 0