mthoutai

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

【Android】MVP架构模式

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定义接口。

// 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)

// 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)

// 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):

// 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):

// 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 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 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):

<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逻辑和业务逻辑的场景下

posted on 2025-11-08 21:26  mthoutai  阅读(1)  评论(0)    收藏  举报