Android 之 MVP架构

一、MVP架构分层与职责

层​ ​职责​ ​关键接口/类​
​Model​ 数据处理(网络请求、本地验证) UserRepository
​View​ UI展示(Activity/Fragment),实现View接口,被动响应Presenter指令 LoginContract.View
​Presenter​ 业务逻辑协调:接收View事件 → 调用Model → 处理结果 → 更新View LoginContract.Presenter

二、项目结构与包组织

├── contract

│ └── LoginContract.java // 定义View和Presenter接口

├── model

│ ├── UserRepository.java // 数据操作实现

│ └── OnLoginListener.java // 登录回调接口

├── presenter

│ └── LoginPresenter.java // 业务逻辑实现

└── view

└── LoginActivity.java // 实现View接口,处理UI

三、详细代码实现

1. ​​Contract接口(contract/LoginContract.java)
java 复制代码
public interface LoginContract {
    interface View {
        void showLoading();
        void hideLoading();
        void showUsernameError(String error);
        void showPasswordError(String error);
        void onLoginSuccess();
        void onLoginFailure(String error);
    }

    interface Presenter {
        void attachView(View view);
        void detachView();
        void login(String username, String password);
    }
}

作用​​:统一管理View和Presenter的接口,避免类膨胀

2. ​​Model层(model/UserRepository.java)​
java 复制代码
public class UserRepository {
    // 模拟网络请求登录
    public void login(String username, String password, OnLoginListener listener) {
        // 实际开发中替换为Retrofit/Volley请求
        if ("admin".equals(username) && "123456".equals(password)) {
            listener.onSuccess();
        } else {
            listener.onFailure("用户名或密码错误");
        }
    }

    public interface OnLoginListener {
        void onSuccess();
        void onFailure(String errorMsg);
    }
}

​职责​​:

  • 数据验证、网络请求、数据库操作
  • 通过回调接口OnLoginListener返回结果(避免直接依赖Presenter)
3. ​​Presenter层(presenter/LoginPresenter.java)
java 复制代码
public class LoginPresenter implements LoginContract.Presenter {
    private LoginContract.View view;
    private UserRepository userRepository;

    public LoginPresenter() {
        userRepository = new UserRepository();
    }

    @Override
    public void attachView(LoginContract.View view) {
        this.view = view;
    }

    @Override
    public void detachView() {
        view = null; // 防止内存泄漏
    }

    @Override
    public void login(String username, String password) {
        if (view == null) return;

        // 本地校验
        if (TextUtils.isEmpty(username)) {
            view.showUsernameError("用户名不能为空");
            return;
        }
        if (TextUtils.isEmpty(password)) {
            view.showPasswordError("密码不能为空");
            return;
        }

        view.showLoading();
        userRepository.login(username, password, new UserRepository.OnLoginListener() {
            @Override
            public void onSuccess() {
                if (view != null) {
                    view.hideLoading();
                    view.onLoginSuccess();
                }
            }

            @Override
            public void onFailure(String errorMsg) {
                if (view != null) {
                    view.hideLoading();
                    view.onLoginFailure(errorMsg);
                }
            }
        });
    }
}

关键逻辑​​:

  • 持有View的弱引用(通过attachView/detachView管理)
  • 本地验证 → 调用Model → 处理回调 → 更新View。
4. ​​View层(view/LoginActivity.java)
java 复制代码
public class LoginActivity extends AppCompatActivity implements LoginContract.View {
    private EditText etUsername, etPassword;
    private Button btnLogin;
    private ProgressBar progressBar;
    private LoginPresenter presenter;

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

        etUsername = findViewById(R.id.et_username);
        etPassword = findViewById(R.id.et_password);
        btnLogin = findViewById(R.id.btn_login);
        progressBar = findViewById(R.id.progress_bar);

        presenter = new LoginPresenter();
        presenter.attachView(this);

        btnLogin.setOnClickListener(v -> {
            String username = etUsername.getText().toString().trim();
            String password = etPassword.getText().toString().trim();
            presenter.login(username, password);
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.detachView(); // 解绑View,避免内存泄漏
    }

    // 实现View接口方法
    @Override
    public void showLoading() {
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        progressBar.setVisibility(View.GONE);
    }

    @Override
    public void showUsernameError(String error) {
        etUsername.setError(error);
    }

    @Override
    public void showPasswordError(String error) {
        etPassword.setError(error);
    }

    @Override
    public void onLoginSuccess() {
        Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
        startActivity(new Intent(this, HomeActivity.class));
        finish();
    }

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

四、总结:MVP vs MVC 优势

方面​ ​MVC​ ​MVP​​(本方案)
Activity职责 同时处理UI和逻辑,臃肿 仅处理UI,逻辑分离到Presenter
可测试性 难(依赖Android组件) 易(Presenter纯Java,可Mock测试)
耦合度 View与Model直接交互 View与Model完全解耦
内存泄漏风险 高(异步回调持有Activity引用) 低(通过detachView()主动解绑)
相关推荐
峥嵘life36 分钟前
Android 蓝牙设备连接广播详解-2026
android·python·学习
MusingByte4 小时前
别再裸用 Claude Code 了!安卓开发者必装 13 个官方推荐插件,效率翻 3 倍省 70% token
android
_李小白4 小时前
【android opencv学习笔记】Day 29: 滤波算法之Sobel 边缘检测
android·opencv·学习
Dxy12393102165 小时前
Python 操作 MySQL 事务:从入门到避坑
android·python·mysql
峥嵘life6 小时前
Android getprop 属性限制详解:User 版本属性获取问题分析
android·开发语言·python·学习
一航jason7 小时前
Speed Tools:一套低侵入的 Android 插件化 + 动态换肤 + 字体切换框架
android·插件化·组件化·换肤
李斯维8 小时前
Jetpack 可观察数据容器 LiveData 的入门与基础使用
android·android jetpack
问心无愧05138 小时前
ctf show web入门261
android·前端·笔记
alexhilton9 小时前
车载系统中的可扩展UI:从UI嵌入到系统窗口编排
android·kotlin·android jetpack
Cloud_Shy6189 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第一章 Item 4 - 6)
android·数据库·论文阅读·python