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()主动解绑)
相关推荐
介一安全8 小时前
【Frida Android】基础篇15(完):Frida-Trace 基础应用——JNI 函数 Hook
android·网络安全·ida·逆向·frida
吞掉星星的鲸鱼9 小时前
android studio创建使用开发打包教程
android·ide·android studio
陈老师还在写代码9 小时前
android studio 签名打包教程
android·ide·android studio
csj509 小时前
android studio设置
android
hifhf9 小时前
Android Studio gradle下载失败报错
android·ide·android studio
陈老师还在写代码9 小时前
android studio,java 语言。新建了项目,在哪儿设置 app 的名字和 logo。
android·java·android studio
2501_9160074711 小时前
Fastlane 结合 开心上架(Appuploader)命令行实现跨平台上传发布 iOS App 的完整方案
android·ios·小程序·https·uni-app·iphone·webview
listhi52013 小时前
Vue.js 3的组合式API
android·vue.js·flutter
用户693717500138413 小时前
🚀 Jetpack MVI 实战全解析:一次彻底搞懂 MVI 架构,让状态管理像点奶茶一样丝滑!
android·android jetpack
2501_9159184114 小时前
iOS 上架应用市场全流程指南,App Store 审核机制、证书管理与跨平台免 Mac 上传发布方案(含开心上架实战)
android·macos·ios·小程序·uni-app·cocoa·iphone