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 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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接口

1
2
3
4
5
6
7
8
9
10
11
12
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接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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(); }}

源碼地址
https://github.com/xuyushi/AndroidMVPDemo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM