android MVP模式代码示例

以下是一个 Android MVP 模式的完整示例,以「用户登录功能」为核心场景,包含 Model、View、Presenter 三层架构,代码简洁且符合 Android 开发规范。

一、MVP 架构分层说明

  • Model:负责数据处理(网络请求、本地存储等)
  • View:负责界面展示和用户交互(Activity/Fragment)
  • Presenter:作为 View 和 Model 的中间层,处理业务逻辑,解耦视图和数据

二、完整代码实现

1. 基础接口定义
(1) 通用回调接口(处理异步请求结果)

java

运行

复制代码
// BaseCallback.java
public interface BaseCallback<T> {
    // 请求成功
    void onSuccess(T data);
    // 请求失败
    void onFailure(String errorMsg);
}
(2) View 层接口(定义登录界面的行为)

这个interface LoginView 很关键,可以省很多Ui层的代码,在P层处理数据和UI各种状态的关联

连接p层和view层的桥梁

复制代码
// LoginView.java
public interface LoginView {
    // 显示加载中
    void showLoading();
    // 隐藏加载中
    void hideLoading();
    // 登录成功回调
    void onLoginSuccess(String msg);
    // 登录失败回调
    void onLoginFailure(String errorMsg);
    // 获取输入的用户名
    String getUsername();
    // 获取输入的密码
    String getPassword();
}
(3) Presenter 层接口(定义登录业务逻辑)

java

运行

复制代码
// LoginPresenter.java
public interface LoginPresenter {
    // 执行登录逻辑
    void login();
    // 销毁资源(防止内存泄漏)
    void detachView();
}
2. Model 层实现(模拟登录数据请求)

java

运行

复制代码
// LoginModel.java
public class LoginModel {
    // 模拟网络请求登录
    public void login(String username, String password, BaseCallback<String> callback) {
        // 模拟异步请求(子线程)
        new Thread(() -> {
            try {
                // 模拟网络延迟
                Thread.sleep(1000);
                
                // 模拟登录校验逻辑
                if ("admin".equals(username) && "123456".equals(password)) {
                    callback.onSuccess("登录成功!");
                } else {
                    callback.onFailure("用户名或密码错误");
                }
            } catch (InterruptedException e) {
                callback.onFailure("请求异常:" + e.getMessage());
            }
        }).start();
    }
}
3. Presenter 层实现(核心业务逻辑处理)

java

运行

复制代码
// LoginPresenterImpl.java
public class LoginPresenterImpl implements LoginPresenter {
    // View 层引用(使用弱引用防止内存泄漏)
    private WeakReference<LoginView> mLoginViewRef;
    private LoginModel mLoginModel;

    public LoginPresenterImpl(LoginView loginView) {
        this.mLoginViewRef = new WeakReference<>(loginView);
        this.mLoginModel = new LoginModel();
    }

    @Override
    public void login() {
        LoginView loginView = mLoginViewRef.get();
        if (loginView == null) return;

        // 显示加载中
        loginView.showLoading();

        // 获取View层输入的参数
        String username = loginView.getUsername();
        String password = loginView.getPassword();

        // 简单参数校验
        if (TextUtils.isEmpty(username)) {
            loginView.onLoginFailure("用户名不能为空");
            loginView.hideLoading();
            return;
        }
        if (TextUtils.isEmpty(password)) {
            loginView.onLoginFailure("密码不能为空");
            loginView.hideLoading();
            return;
        }

        // 调用Model层处理登录
        mLoginModel.login(username, password, new BaseCallback<String>() {
            @Override
            public void onSuccess(String data) {
                // 切回主线程更新UI
                new Handler(Looper.getMainLooper()).post(() -> {
                    if (loginView != null) {
                        loginView.hideLoading();
                        loginView.onLoginSuccess(data);
                    }
                });
            }

            @Override
            public void onFailure(String errorMsg) {
                // 切回主线程更新UI
                new Handler(Looper.getMainLooper()).post(() -> {
                    if (loginView != null) {
                        loginView.hideLoading();
                        loginView.onLoginFailure(errorMsg);
                    }
                });
            }
        });
    }

    @Override
    public void detachView() {
        // 释放View引用,防止内存泄漏
        if (mLoginViewRef != null) {
            mLoginViewRef.clear();
            mLoginViewRef = null;
        }
    }
}
4. View 层实现(Activity 作为视图载体)

java

运行

复制代码
// LoginActivity.java
public class LoginActivity extends AppCompatActivity implements LoginView {
    private EditText etUsername, etPassword;
    private Button btnLogin;
    private ProgressDialog progressDialog;
    private LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        // 初始化控件
        initView();

        // 创建Presenter实例
        mLoginPresenter = new LoginPresenterImpl(this);

        // 登录按钮点击事件
        btnLogin.setOnClickListener(v -> mLoginPresenter.login());
    }

    private void initView() {
        etUsername = findViewById(R.id.et_username);
        etPassword = findViewById(R.id.et_password);
        btnLogin = findViewById(R.id.btn_login);

        // 初始化加载对话框
        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("登录中...");
        progressDialog.setCancelable(false);
    }

    @Override
    public void showLoading() {
        if (!progressDialog.isShowing()) {
            progressDialog.show();
        }
    }

    @Override
    public void hideLoading() {
        if (progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

    @Override
    public void onLoginSuccess(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        // 登录成功跳转到主页面
        startActivity(new Intent(this, MainActivity.class));
        finish();
    }

    @Override
    public void onLoginFailure(String errorMsg) {
        Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public String getUsername() {
        return etUsername.getText().toString().trim();
    }

    @Override
    public String getPassword() {
        return etPassword.getText().toString().trim();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 销毁Presenter,释放引用
        mLoginPresenter.detachView();
    }
}
5. 布局文件(activity_login.xml)

xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="20dp">

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"
        android:inputType="text"
        android:maxLines="1"/>

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入密码"
        android:inputType="textPassword"
        android:maxLines="1"
        android:layout_marginTop="15dp"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"
        android:layout_marginTop="30dp"/>

</LinearLayout>

三、核心亮点说明

  1. 解耦性:View 层(Activity)只负责界面展示,不处理业务逻辑;Model 层只负责数据处理;Presenter 层作为中间层协调两者,符合单一职责原则。
  2. 内存泄漏防护 :Presenter 中使用 WeakReference 持有 View 引用,且在 Activity 销毁时调用 detachView() 释放引用。
  3. 线程切换 :Model 层模拟子线程网络请求,通过 Handler 切回主线程更新 UI。
  4. 可测试性:Presenter 层不依赖具体的 View 实现,可通过 Mock View 进行单元测试。

该示例是 MVP 模式的基础实现,实际项目中可结合 RxJava / 协程优化线程处理、使用 Dagger 依赖注入、封装 BasePresenter/BaseActivity 等进一步提升代码复用性。

相关推荐
TAEHENGV18 小时前
创建目标模块 Cordova 与 OpenHarmony 混合开发实战
android·java·开发语言
zjw_swun18 小时前
Compose原理简易实现
android·composer
青莲84318 小时前
Kotlin Flow 深度探索与实践指南——中部:实战与应用篇
android·前端
建群新人小猿19 小时前
陀螺匠企业助手-我的日程
android·大数据·运维·开发语言·容器
_李小白19 小时前
【Android FrameWork】第三十九天:DeviceStorageManagerService
android
不急不躁12320 小时前
Android16 给应用默认获取权限
android·java
用户416596736935520 小时前
拒绝 Race Condition:深入理解 StateFlow 的取值与更新
android
青莲84321 小时前
Kotlin Flow 深度探索与实践指南——上部:基础与核心篇
android·前端
恋猫de小郭21 小时前
2025 年终醒悟,AI 让我误以为自己很强,未来程序员的转型之路
android·前端·flutter
2501_9159184121 小时前
iOS 开发中证书创建与管理中的常见问题
android·ios·小程序·https·uni-app·iphone·webview