【Android】MVP架构模式
文章目录
- 【Android】MVP架构模式
-
- 🍿1.MVP架构原理
-
- [1.1 各组件职责](#1.1 各组件职责)
- [1.2 MVP的两种主要变体](#1.2 MVP的两种主要变体)
-
- [1.2.1 被动视图](#1.2.1 被动视图)
- [1.2.2 监督控制器](#1.2.2 监督控制器)
- [🍕2 实现简单登录DEMO](#🍕2 实现简单登录DEMO)
-
- [2.1 定义接口](#2.1 定义接口)
- [2.2 实现Model和Presenter](#2.2 实现Model和Presenter)
- [2.3 View层实现](#2.3 View层实现)
- [2.4 相关布局文件](#2.4 相关布局文件)
- [🌭3. 结论](#🌭3. 结论)
🍿1.MVP架构原理
MVP(Model-View-Presenter)是一种将应用程序分为三个主要组件的架构模式,这三个组件各自承担不同的职责:
Model(模型) :负责数据的存储、检索和业务逻辑处理
View(视图) :负责用户界面的展示和用户输入的接收
Presenter(表示器) :作为Model和View之间的中介,处理用户输入,操作Model并更新View
MVP架构的核心思想是实现关注点分离,将用户界面逻辑与业务逻辑明确区分,从而提高代码的可维护性和可测试性。各组件关系如下图:

1.1 各组件职责
-
Model:负责处理数据的获取、存储和管理,通常包含数据访问层(如数据库、API等)和实体模型
-
View:负责展示UI并与用户交互。View是一个"被动视图"(Passive View),它不会包含任何业务逻辑,只接受Presenter的指令来更新界面。
-
Presenter:是MVP的核心组件,负责处理业务逻辑。Presenter从Model获取数据,并将其传递给View,同时监听View的用户操作并处理相应的逻辑。
通过引入Presenter层,彻底实现了Model、View和Presenter三层的解耦,与MVC相比,MVP最大的不同点在于View和Model之间没有直接交互,而是通过Presenter相链接。Presenter通过接口与View和Model通信,保证了各组件之间的低耦合性,从而使代码更易于维护和测试。
1.2 MVP的两种主要变体
1.2.1 被动视图
这就是上面详细说明的,也是MVP中最常见的版本
- 特点:View 极其"笨拙"和被动。它除了显示和转发用户输入外,几乎什么都不做。
- 所有表现逻辑都在 Presenter 中。包括决定哪个UI元素显示/隐藏。
- 优点:View 和 Model 完全解耦,可测试性最高。
- 缺点:Presenter 可能会变得庞大,因为它要处理大量细微的UI状态。
1.2.2 监督控制器
其实,监督控制器就是Presenter的另一种形象称呼,它强调 Presenter 同时作为"监督者"和"控制器"的双重角色。
控制器:
View层只负责用户点击、滑动等操作,然后立即通知给Presenter层处理。比如"用户点击了登录按钮,用户名是XXX,密码是XXX,接下来你来处理。"
Presenter层接到指令后,开始发挥作为控制器的职责:
它决定调用哪个Model层的方法来实现具体的业务逻辑。
监督者:
- 监听结果:当Presenter将任务交给Model层后,它并不会一直等待,而是注册一个监听器,监督Model层任务的完成情况
- 处理结果:当Model层完成任务后(有可能成功也有可能失败),会通过回调(监听器)通知Presenter,这时,监督者开始工作:
- 处理Model返回的数据或处理异常
- 根据处理结果,监督者Presenter会指挥View层,给它一个明确的命令,比如"显示登陆成功界面"或者"显示错误信息"
- 特点:View 层稍稍"聪明"一些。它可以处理一些简单的、不涉及业务逻辑的UI数据绑定。
- 优点:减轻了 Presenter 的负担,Presenter 更专注于流程控制。
- 缺点:View 和 Model 之间存在一定的耦合(通过数据模型),可测试性不如被动视图。
🍕2 实现简单登录DEMO
2.1 定义接口
首先,为了保证Presenter与View和Model的解耦,我们需要为View和Model定义接口。
java
// LoginContract.java
package com.example.mvplogin.contract;
public interface LoginContract {
interface View {
void showLoading();
void hideLoading();
void showLoginSuccess(String username);
void showLoginError(String message);
String getUsername();
String getPassword();
}
interface Presenter {
void login();
void attachView(View view);
void detachView();
}
}
2.2 实现Model和Presenter
模型层(LoginModel.java)
java
// LoginModel.java
package com.example.mvplogin.model;
public class LoginModel {
// 模拟用户数据库
private static final String VALID_USERNAME = "admin";
private static final String VALID_PASSWORD = "123456";
public void login(String username, String password, OnLoginFinishedListener listener) {
// 模拟网络请求延迟
new android.os.Handler().postDelayed(() -> {
if (username.isEmpty() || password.isEmpty()) {
listener.onError("用户名或密码不能为空");
} else if (!username.equals(VALID_USERNAME)) {
listener.onError("用户名不存在");
} else if (!password.equals(VALID_PASSWORD)) {
listener.onError("密码错误");
} else {
listener.onSuccess(username);
}
}, 1500);
}
public interface OnLoginFinishedListener {
void onSuccess(String username);
void onError(String message);
}
}
Presenter层(LoginPresenter.java)
java
// LoginPresenter.java
package com.example.mvplogin.presenter;
import com.example.mvplogin.contract.LoginContract;
import com.example.mvplogin.model.LoginModel;
public class LoginPresenter implements LoginContract.Presenter, LoginModel.OnLoginFinishedListener {
private LoginContract.View view;
private final LoginModel model;
public LoginPresenter() {
this.model = new LoginModel();
}
@Override
public void attachView(LoginContract.View view) {
this.view = view;
}
@Override
public void detachView() {
this.view = null;
}
@Override
public void login() {
if (view == null) return;
String username = view.getUsername();
String password = view.getPassword();
view.showLoading();
model.login(username, password, this);
}
@Override
public void onSuccess(String username) {
if (view != null) {
view.hideLoading();
view.showLoginSuccess(username);
}
}
@Override
public void onError(String message) {
if (view != null) {
view.hideLoading();
view.showLoginError(message);
}
}
}
2.3 View层实现
最后,我们在View层(如Activity或Fragment)中实现LoginContract接口,并将操作委托给Presenter。
视图层 (LoginActivity.java):
java
// LoginActivity.java
package com.example.mvplogin.view;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.mvplogin.R;
import com.example.mvplogin.contract.LoginContract;
import com.example.mvplogin.presenter.LoginPresenter;
public class LoginActivity extends AppCompatActivity implements LoginContract.View {
private EditText etUsername, etPassword;
private Button btnLogin;
private ProgressBar progressBar;
private LoginContract.Presenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 初始化Presenter
presenter = new LoginPresenter();
presenter.attachView(this);
// 初始化UI组件
etUsername = findViewById(R.id.etUsername);
etPassword = findViewById(R.id.etPassword);
btnLogin = findViewById(R.id.btnLogin);
progressBar = findViewById(R.id.progressBar);
// 设置登录按钮点击事件
btnLogin.setOnClickListener(v -> presenter.login());
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.detachView();
}
@Override
public void showLoading() {
progressBar.setVisibility(View.VISIBLE);
btnLogin.setEnabled(false);
}
@Override
public void hideLoading() {
progressBar.setVisibility(View.GONE);
btnLogin.setEnabled(true);
}
@Override
public void showLoginSuccess(String username) {
// 跳转到欢迎页面
Intent intent = new Intent(this, WelcomeActivity.class);
intent.putExtra("username", username);
startActivity(intent);
finish();
}
@Override
public void showLoginError(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public String getUsername() {
return etUsername.getText().toString().trim();
}
@Override
public String getPassword() {
return etPassword.getText().toString().trim();
}
}
欢迎页面 (WelcomeActivity.java):
java
// WelcomeActivity.java
package com.example.mvplogin.view;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.mvplogin.R;
public class WelcomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_welcome);
TextView tvWelcome = findViewById(R.id.tvWelcome);
String username = getIntent().getStringExtra("username");
tvWelcome.setText("欢迎," + username + "!");
}
}
2.4 相关布局文件
布局文件 (activity_login.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center"
tools:context=".view.LoginActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="32dp"/>
<EditText
android:id="@+id/etUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="用户名"
android:inputType="text"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:inputType="textPassword"
android:layout_marginBottom="24dp"/>
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:layout_marginBottom="16dp"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center"/>
</LinearLayout>
欢迎页面布局 (activity_welcome.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="32dp"
tools:context=".view.WelcomeActivity">
<TextView
android:id="@+id/tvWelcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="欢迎"
android:textSize="24sp"
android:textStyle="bold"/>
</LinearLayout>
字符串资源 (strings.xml):
xml
<resources>
<string name="app_name">MVP登录示例</string>
</resources>
布局说明:
- 用户名输入框 (EditText):用户输入登录用户名的地方,id为etUsername。
- 密码输入框 (EditText):用户输入登录密码的地方,id为etPassword,并设置了输入类型为密码格式。
- 登录按钮 (Button):用于触发登录操作的按钮,id为btnLogin。
- 加载进度条 (ProgressBar):在处理登录请求时显示的加载指示器,默认隐藏(visibility="gone"),只有在请求处理中显示。
🌭3. 结论
MVP架构通过将View、Model、Presenter三层彻底解耦,使得业务逻辑、UI展示、数据处理分别在不同的层中负责。这样不仅提高了代码的可维护性和可测试性,也使得项目的扩展性更强。在实际开发中,MVP非常适用于复杂度较高的应用程序,尤其是在需要严格分离UI逻辑和业务逻辑的场景下