前言
通过一个小的登录功能模块案例,帮助大家了解MVP。最终实现一个结合Rxjava2,Retrofit 的MVP通用框架。代码放到github上。(如有错误之处,请在评论区指出,谢谢。如果感觉写的不错,请点赞,关注,谢谢。)
目录:
Android MVP-编程思想1(什么是MVC-MVP-MVVM模式?)
Android MVP-编程思想2(代码实现初级版)
Android MVP-编程思想3(内存泄露问题处理,基类封装,有没有必要再使用软引用?)
Android MVP-编程思想4(AOP思想-动态代理运用,反射创建M层实例对象)
Android MVP-编程思想5(如何处理多个P层的问题?)
Android MVP-编程思想6(依赖注入多个P层优化—注解,反射)
Android MVP-编程思想7(为什么使用代理类抽取通用方法而不是工具类?,基类BaseMvpFragment)
未完待续--------
Android MVP-编程思想8(集成Rxjava2,Retrofit)
MVP 实战
那么我们下面就要将这个类中的代码改写为 MVP 的写法,回顾上面提及的 MVP 架构的思想,它是将 View 层与 Model 层彻底隔离,意味着 View 和 Model 都不再持对方的引用,它们通过一个第三者 Presenter 来代理事物的传递,所以 Presenter 层会持有 Model 与 View 层的引用,这是第一步。
第二步,是将它们之间的联系抽象出来,以接口的方式相互调用,所以 Model 、View、Presenter 各自拥有自己的接口和抽象方法,所以这就会无形的多出了三个接口类,这也就是 MVP 的缺点之一。所以,为了较少的创建接口类,我们就给这三层接口定义了一个契约接口,把它们更加紧密的结合在一起,方法查看,例如代码这样写:参考谷歌官方mvp案例todo-mvp
下面我们以登录功能为例行了解 MVP 架构的代码写法。
第一步
首先我们参考谷歌todo-mvp 写一个契约类,定义交互行为。契约类的目的是把MVP的行为定义放在一起,方便阅读和维护
public class LoginContract {
/**
* V 定义View的行为,调用者是P
*/
interface IView {
//登录成功跳转到主界面
void goToMainActivity();
//请求网络显示等待框
void showProgress(boolean isShow);
//显示提示框-登录失败,或其他提示信息
void showToast(String string);
}
/**
* P(中转站和数据处理者) 定义View与Model的交互行为,调用者是V。P做中转
*/
interface IPresenter {
void onClickLogin(String username, String pwd);
}
/**
* M 数据提供者
*/
interface IModel {
void getUserInfo(String username, String pwd, ICallBack callBack);
}
}
第二步,定义MVP行为接口的实现类
辅助类ICallBack,用来回调http请求的数据
public interface ICallBack<T> {
void success(T data);
void error(String error);
}
M层的实现类LoginModel,提供数据,网络数据,本地数据
public class LoginModel implements LoginContract.IModel {
@Override
public void getUserInfo(String username, String pwd, final ICallBack callBack) {
//todo 模拟 http请求
Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
callBack.success("成功");
}
};
handler.postDelayed(runnable, 2000);
}
}
V层的实现类LoginActivity,控制视图显示,要调用P层接口,所以要持有P对象的引用
public class LoginActivity extends AppCompatActivity implements LoginContract.IView {
LoginContract.IPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPresenter = new LoginPresenter(this);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPresenter.onClickLogin("xxx", "xxx");
}
});
}
@Override
public void goToMainActivity() {
runOnUiThread(new Runnable() {
@Override
public void run() {
//todo 登录成功跳转到主界面
Log.i("mvp_", "登录成功跳转到主界面");
}
});
}
@Override
public void showProgress(boolean isShow) {
if (isShow) {
Log.i("mvp_", "显示等待框");
} else {
Log.i("mvp_", "隐藏等待框");
}
}
@Override
public void showToast(String str) {
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
}
}
P层实现类LoginPresenter,是M和V交互的协调者,所以要持有M,V对象的引用
注意点:网络请求是耗时操作,我们 Presenter 层持有了 View 层的引用,也就是 Activity 的引用,在网络请求过程中,Activity被用户或者系统关闭,这时 View 相当于被摧毁了,如果不进行判断 View 引用是否为空,当数据返回时,就会造成空指针异常,所以每次都要进行判空才合理。
public class LoginPresenter implements LoginContract.IPresenter {
private LoginContract.IModel mModel;
private LoginContract.IView mView;
public LoginPresenter(LoginContract.IView view) {
this.mModel = new LoginModel();
this.mView = view;
}
@Override
public void onClickLogin(String username, String pwd) {
mView.showProgress(true);
mModel.getUserInfo(username, pwd, new ICallBack<String>() {
@Override
public void success(String data) {
if (mView != null) {
mView.showToast("登录成功");
mView.showProgress(false);
mView.goToMainActivity();
}
}
@Override
public void error(String error) {
if (mView != null) {
mView.showProgress(false);
mView.showToast("登录失败");
}
}
});
}
}
上面使用最简单的方式,演示了MVP的设计思想。目的是让大家理解,MVP模式是如何把业务逻辑,数据与V层进行分离的。再回想对比MVC是不是觉得MVP模式,职责分离的更清楚。
运行,点击登录按钮,打印日志如下:---------------------
2019-12-11 14:34:06.990 6376-6376/chongchong.wei.rx_retrofit_mvp I/mvp_: 显示等待框
2019-12-11 14:34:09.002 6376-6376/chongchong.wei.rx_retrofit_mvp I/mvp_: 隐藏等待框
2019-12-11 14:34:09.002 6376-6376/chongchong.wei.rx_retrofit_mvp I/mvp_: 登录成功跳转到主界面
当然上面的代码只是帮助大家理解MVP模式的思想,一些细节问题没有处理。下一节我们对代码进行优化,抽离,封装。