MVP基类抽取,面向接口
通常项目中使用MVP架构并不会像MVP基础结构Demo中这样直接把类写死,而是要进行基类抽取,面向接口。
创建三个基类:BaseModel
、BaseView
、BasePresenter
MVP整体流程是这样的:
- 用户操作View
- View把任务传递给Presenter
- Presenter把任务传递给Model
- Model执行具体的任务,任务完成后把结果返回给Presenter
- Presenter把结果返回给View
根据这个流程,按View -> Presenter -> Model 这个顺序去梳理基类,思路更清晰。
首先是BaseView
,这里有两点需要完成:
- 根据上述流程第二步描述,View里肯定是需要持有Presenter,这个Presenter具体是哪个类只有到具体业务才知道,所以这个Presenter的赋值需要由具体的子类实现。
- 根据上述流程第五步描述,Presenter需要持有View,并且Presenter和View之间需要有个协议,Presenter才能把结果返回给View,也就是说View必须提供出一个方法给Presenter调用。这里的具体协议也需要具体到业务才能确定,所以也需要由子类实现.
综上两点,BaseView
代码如下:
//这里继承Activity,当然也可以是其他Fragment或者其他View
public abstract class BaseView<P extends BasePresenter, CONTRACT> extends Activity {
protected P p;
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
p = getPresenter();
p.v = this;//这里会产生内存泄漏,不影响现在理解,稍后解决
}
// 这个Presenter具体是哪个类只有到具体业务才知道,所以这个Presenter的赋值需要由具体的子类实现
public abstract P getPresenter();
// 获取具体协议
// 协议规定了Presenter怎么和View沟通
public abstract CONTRACT getContract();
}
然后需要完成BasePresenter
,上述MVP流程,Presenter需要分别持有View和Model,Presenter是View和Model沟通的桥梁,把操作从View从递给Model,把结果从Model返回给View,而Presenter与他们之间的沟通,也是要遵循某种协议。
Presenter的View在BaseView
里通过p.v = this
已经赋值,这里指需要给Model赋值,而具体的Model也需要由具体业务的子类去完成,不在累赘:
public abstract class BasePresenter<M extends BaseModel, V extends BaseView, CONTRACT> {
protected M m;
protected V v;
public BasePresenter() {
m = getModel();
}
public abstract M getModel();
public abstract CONTRACT getContract();
}
最后是完成BaseModel
,Model里需要持有Presenter,并且和Presenter之间也有协议,具体的赋值和协议和上述思想一样,都交给具体子类:
public abstract class BaseModel<P extends BasePresenter, CONTRACT> {
protected P p;
// 子类的初始化方法调用super.BaseModel(xxxPresenter),就会实现具体的Presenter和Model的绑定
public BaseModel(P p) {
this.p = p;
}
public abstract CONTRACT getContract();
}
三个基类完成,模拟一个登录业务爽一下:
一般情况下网络请求都会有个BaseBean,这里定义为BaseResponseBean
:
public class BaseResponseBean {
private int code;
private String error;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
然后定义一个UserInfo
用来接收网络请求返回的数据,继承自BaseRequrieResult
:
public class UserInfo extends BaseResponseBean {
private String nickName;
public UserInfo(String nickName) {
this.nickName = nickName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
之后就要定义一直提到的协议,上面流程也分析过,Model和Presenter需要有执行操作的函数,Presenter和View需要有接收结果的函数:
// 数字流程顺序,方便理解
public interface LoginContract {
interface View<T extends BaseResponseBean> {
void responseLogin(T t);//-------------------------------------4
}
interface Presenter<T extends BaseResponseBean> {
void requireLogin(String userName, String password);//---------1
void responseLogin(T t);//-------------------------------------3
}
interface Model {
void requireLogin(String userName, String password);//---------2
}
}
协议完成,现在可以去写具体业务了:LoginModel
、LoginActivity
、LoginPresenter
分别继承自:BaseModel
、BaseView
、BasePresenter
View层这里就是Activity,因为BaseView
技能了Activity,这里LoginActivity就直接继承BaseView
,布局很简单不贴了,直接上LoginActivity
代码:
public class LoginActivity extends BaseView<LoginPresenter, LoginContract.View> {
private EditText et_username;
private EditText et_pwd;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
initOnclickListener();
}
private void initView() {
et_username = findViewById(R.id.et_username);
et_pwd = findViewById(R.id.et_pwd);
button = findViewById(R.id.btn_login);
}
private void initOnclickListener() {
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// p已通过getPresenter赋值
p.getContract().requireLogin(et_username.getText().toString(), et_pwd.getText().toString());
}
});
}
@Override
public LoginPresenter getPresenter() {
return new LoginPresenter();
}
@Override
public LoginContract.View getContract() {
return new LoginContract.View<UserInfo>() {
@Override
public void responseLogin(UserInfo userInfo) {
Toast.makeText(LoginActivity.this, userInfo == null ? "登录失败" : "登录成功:" + userInfo.getNickName(), Toast.LENGTH_LONG).show();
}
};
}
}
然后是Presenter,这里有三种写法:
- 在Presenter里处理的具体的操作(Google的Demo里是这样做的)
- 用其他的业务类去做具体操作
- 在Model层去做具体操作
这里采用的第三种,把任务给了Model层。这个看团队选择和个人习惯,没什么好坏。
LoginPresenter
里上面都没做,只是分发:
public class LoginPresenter extends BasePresenter<LoginModel, LoginActivity, LoginContract.Presenter> {
@Override
public LoginModel getModel() {
return new LoginModel(this);
}
@Override
public LoginContract.Presenter getContract() {
return new LoginContract.Presenter<UserInfo>() {
@Override
public void requireLogin(String userName, String password) {
// 任务发送给Model
m.getContract().requireLogin(userName, password);
}
@Override
public void responseLogin(UserInfo userInfo) {
// 结果返回给Presenter
v.getContract().responseLogin(userInfo);
}
};
}
}
然后是LoginModel
,在这做具体的登录操作,简单模拟了一下,并没有做真正的网络请求:
public class LoginModel extends BaseModel<LoginPresenter, LoginContract.Model> {
public LoginModel(LoginPresenter loginPresenter) {
// 此时通过父类的构造方法把具体的loginPresenter赋值给了BasePresenter中的p
super(loginPresenter);
}
@Override
public LoginContract.Model getContract() {
return new LoginContract.Model() {
@Override
public void requireLogin(String userName, String password) {
// 结果返回给Presenter
if ("123".equals(userName) && "123".equals(password)){
p.getContract().responseLogin(new UserInfo("yu"));
}else {
p.getContract().responseLogin(null);
}
}
};
}
}
以上就完成了View->Presenter->Model->Presenter->View这么一个完整的流程。
上面大码用了很多泛型,一定要写好泛型之后再用提示自动生成代码,不亲手撸一把体会不到有多爽,完全不需要强转什么的,谁爽谁知道!!!
代码到这里并没有完事,真是项目中的登录操作要做网络请求,这里如果处理耗时操作,就会出现内存泄漏,在LoginModel
的requireLogin
方法里模拟一个耗时操作:
@Override
public void requireLogin(String userName, String password) {
new Thread(){
@Override
public void run() {
super.run();
SystemClock.sleep(50000);
}
}.start();
}
run一把,点击登录,然后退出Activity,打开Profiler看MEMORY:
是的,
LoginActivity
泄漏了,原因就是Model 中持有Presenter的引用,Presenter中持有LoginActivity
的引用,而Model中有耗时操作没有销毁。解决方式就是把Presenter对View的引用变成弱引用,修改
BasePresenter
,如下:
public abstract class BasePresenter<M extends BaseModel, V extends BaseView, CONTRACT> {
......
// 不能才用强引用,会发送内存泄漏
// protected V v;
// 弱引用
private WeakReference<V> vWeakReference;
// 弱引用绑定
public void bindView(V v) {
vWeakReference = new WeakReference<V>(v);
}
// 弱引用解绑,回收
public void unBindView() {
if (vWeakReference != null) {
vWeakReference.clear();
vWeakReference = null;
System.gc();
}
}
// 提供弱引用获取方法
public V getView(){
if (vWeakReference != null) {
return vWeakReference.get();
}
return null;
}
......
}
在BaseView
里也不能直接用p.v = this;
这样赋值了,而是采用绑定的方式,并且在onDestroy
里解绑:
public abstract class BaseView<P extends BasePresenter, CONTRACT> extends Activity {
protected P p;
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
p = getPresenter();
// 强引用会导致内存泄漏
// p.v = this;
p.bindView(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 和Presenter解绑
p.unBindView();
}
......
}
Presenter里不再持有View强引用,LoginPresenter
里也不能通过v.getContract().responseLogin(userInfo);
去调用View层,而是要通过getView()
去获取v:
public class LoginPresenter extends BasePresenter<LoginModel, LoginActivity, LoginContract.Presenter> {
@Override
public LoginModel getModel() {
return new LoginModel(this);
}
@Override
public LoginContract.Presenter getContract() {
return new LoginContract.Presenter<UserInfo>() {
......
public void responseLogin(UserInfo userInfo) {
// 结果返回给Presenter
// Presenter不在持有v,不能通过v.getContract().responseLogin(userInfo)去调用,通过getView()获取对View的弱引用;
getView().getContract().responseLogin(userInfo);
}
};
}
}
哦了,再看看memory:
LoginActivity
不见了哦。如果是MVC,Activity就是Controller,耗时操作都直接在Activity里执行,就会发生内存泄漏。