【Android】MVP架构模式
【Android】MVP架构模式
文章目录
- 【Android】MVP架构模式
- 🍿1.MVP架构原理
- 1.1 各组件职责
- 1.2 MVP的两种主要变体
- 1.2.1 被动视图
- 1.2.2 监督控制器
- 🍕2 实现简单登录DEMO
- 2.1 定义接口
- 2.2 实现Model和Presenter
- 2.3 View层实现
- 2.4 相关布局文件
- 布局说明:
- 🌭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定义接口。
// 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();}@Overridepublic void attachView(LoginContract.View view) {this.view = view;}@Overridepublic void detachView() {this.view = null;}@Overridepublic void login() {if (view == null) return;String username = view.getUsername();String password = view.getPassword();view.showLoading();model.login(username, password, this);}@Overridepublic void onSuccess(String username) {if (view != null) {view.hideLoading();view.showLoginSuccess(username);}}@Overridepublic 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);// 初始化Presenterpresenter = 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());}@Overrideprotected void onDestroy() {super.onDestroy();presenter.detachView();}@Overridepublic void showLoading() {progressBar.setVisibility(View.VISIBLE);btnLogin.setEnabled(false);}@Overridepublic void hideLoading() {progressBar.setVisibility(View.GONE);btnLogin.setEnabled(true);}@Overridepublic void showLoginSuccess(String username) {// 跳转到欢迎页面Intent intent = new Intent(this, WelcomeActivity.class);intent.putExtra("username", username);startActivity(intent);finish();}@Overridepublic void showLoginError(String message) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show();}@Overridepublic String getUsername() {return etUsername.getText().toString().trim();}@Overridepublic String getPassword() {return etPassword.getText().toString().trim();}
}
欢迎页面 (WelcomeActivity.java):
// WelcomeActivity.javapackage 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 {@Overrideprotected 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"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="登录"android:textSize="24sp"android:textStyle="bold"android:layout_marginBottom="32dp"/><EditTextandroid:id="@+id/etUsername"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="用户名"android:inputType="text"android:layout_marginBottom="16dp"/><EditTextandroid:id="@+id/etPassword"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="密码"android:inputType="textPassword"android:layout_marginBottom="24dp"/><Buttonandroid:id="@+id/btnLogin"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="登录"android:layout_marginBottom="16dp"/><ProgressBarandroid: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"><TextViewandroid: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逻辑和业务逻辑的场景下