一、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()主动解绑) |