MVP是在MVC架构的基础上演化来的一种架构,在MVC中Activity属于C,因此业务逻辑也都是在Activity中实现,造成Activity十分臃肿,而且使用不当容易造成内存泄漏,比如在Activity中有线程在执行耗时操作,然后点back键,销毁Activity,此时因为有线程存在,线程依然持有Activity的引用,造成Activity无法正常销毁,从而内存泄漏,MVP的主要优点是将业务层与Activity层解耦。
这里以网络请求登录模块做一个简单的Demo,用户点击登录按钮,这一步是在View层做的,也就是Activity,然后我们去调用Presenter的登录业务接口,Presenter不需要自己去完成网络请求,而是继续调用Model层去具体实现网络请求,Model从网络拿到数据后封装成JavaBean,然后将请求结果(Javabean或者请求异常消息)告诉给Presenter,Presenter拿到结果后再次回调View层去更新UI,这就是一个简单的MVP流程,这里也很明显,Model层和View层是不直接沟通的,而是通过Presenter这个桥梁。
model层就是真正业务逻辑实现的地方,当业务逻辑实现完之后需要通知Presenter层,因此BaseModel需要拿到Presenter的引用,所以在BaseModel构造方法中需要将Presenter传进来,然后还需要契约方法,让子类去实现具体的业务逻辑。
//接受P层给他的需求(基类)
public abstract class BaseModel {
protected P p;
//业务结束通过,通过presenter调用契约、合同 void responseResult(T t);
public BaseModel(P p) {
this.p = p;
}
protected abstract CONTRACT getContract();
}
Presenter层首先在其构造方法中创建Model,业务逻辑不同是不同的Model,因此我们生命抽象方法getModel交给子类去创建具体的Model,另外我们需要持有View层(Activity)的引用,但是直接在构造方法中把Activity传进来这种强引用很容易因为代码不当而内存泄漏,因此采用弱引用是比较好的一种方式。
public abstract class BasePresenter {
protected M m;
//绑定View层的弱引用
private WeakReference vWeakReference;
public BasePresenter() {
m = getModel();
}
public void bindView(BaseView v) {
vWeakReference = (WeakReference) new WeakReference<>(v);
}
public void unbindView() {
if (vWeakReference != null) {
vWeakReference.clear();
vWeakReference = null;
System.gc();
}
}
//获取View ,P->V
public V getView() {
if (vWeakReference != null) {
return vWeakReference.get();
}
return null;
}
public abstract M getModel();
//获取子类具体契约
public abstract CONTRACT getContract();
}
View层就是Activity,View层需要和Presenter层进行通讯,我们在onCreate方法中创建Presenter,因为但是具体的Presenter要根据业务逻辑不同而不同,因此我们让子类去实现,我们定义抽象方法getPresenter,并在onCreate中调用,这样View层就持有了Presenter的一个对象,同时我们调用presenter层的bindView将自身传给Presenter层,Presenter通过弱引用就持有了View层的引用,最后别忘了在onDestroy中释放Presenter层,解绑为了避免内存泄漏。
public abstract class BaseView extends AppCompatActivity {
protected P p;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
p = getPresenter();
p.bindView(this);
}
//P层做什么需求
public abstract CONTRACT getContract();
//从子类中获取具体的契约
public abstract P getPresenter();
//如果Presenter层出现异常,需要告知View层
public void error(Exception e) {
}
@Override
protected void onDestroy() {
p.unbindView();
super.onDestroy();
}
}
契约类就是将M、V、P层协商的共同业务封装成接口,让代码逻辑更清晰。Model层就是一个请求登录的最终实现。View层(Activity)则是处理登录的具体结果,刷新UI。Presenter层既要去调用Model层的具体登录,又要讲回调结果通知给View层。
public interface LoginContract {
interface Model {
//Model层子类完成方法的具体实现
void executeLogin(String name, String psw) throws Exception;
}
interface View {
//项目中往往是以Javabean
void handleResult(T t);
}
interface Presenter {
//登录请求(接受View层的指令,可以自己去做,也可以让model层去做)
void requestLogin(String name, String psw);
//结果响应(接受Model层处理的结果,通知View层去刷新)
void responseResult(T t);
}
}
Model类需要实现具体具体登录的契约。
public class LoginModel extends BaseModel {
public LoginModel(LoginPresenter loginPresenter) {
super(loginPresenter);
}
@Override
protected LoginContract.Model getContract() {
return new LoginContract.Model() {
@Override
public void executeLogin(String name, String psw) throws Exception {
if ("123".equals(name) && "123".equals(psw)) {
p.getContract().responseResult(new UserInfo("123", "123"));
} else {
p.getContract().responseResult(null);
}
}
};
}
}
首先实现基类的getModel方法,创建LoginModel,并将自己传给LoginModel。然后实现自己的契约,在请求登录的实现中调用Model层的登录方法,并在拿到Model层的结果后调用View层的处理方法。
public class LoginPresenter extends BasePresenter {
@Override
public LoginModel getModel() {
return new LoginModel(this);
}
@Override
public LoginContract.Presenter getContract() {
return new LoginContract.Presenter() {
@Override
public void requestLogin(String name, String psw) {
try {
//3种风格,P层很极端,要不不做事制作转发,要不拼命一个人干活,Google的MVPDemo中在P端做了所有的业务逻辑
m.getContract().executeLogin(name, psw);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void responseResult(UserInfo userInfo) {
getView().getContract().handleResult(userInfo);
}
};
}
}
LoginActivity层首先实现父类的抽象方法getPresenter创建LoginPresenter,然后在按钮点击后调用P层的登录接口,并在请求结果返回的回调中刷新UI。
public class LoginActivity extends BaseView {
private EditText etCount;
private EditText etPwd;
private Button btnLongin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
initListener();
}
private void initView() {
etCount = findViewById(R.id.et_count);
etPwd = findViewById(R.id.et_password);
btnLongin = findViewById(R.id.btn_login);
}
private void initListener() {
btnLongin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = etCount.getText().toString();
String psw = etPwd.getText().toString();
p.getContract().requestLogin(name, psw);
}
});
}
@Override
public LoginContract.View getContract() {
return new LoginContract.View() {
@Override
public void handleResult(UserInfo userInfo) {
if (userInfo != null) {
Toast.makeText(LoginActivity.this, userInfo.toString(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
}
}
};
}
@Override
public LoginPresenter getPresenter() {
return new LoginPresenter();
}
}
最后补充上两个JavaBean的代码,比较简单
public class BaseEntity {
private int code;
private boolean success;
private String error;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
public class UserInfo extends BaseEntity {
private String name;
private String company;
public UserInfo() {
}
public UserInfo(String name, String company) {
this.name = name;
this.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", company='" + company + '\'' +
'}';
}
}
一个简易版的MVP Demo就完成了。
Demo地址:https://github.com/987570437/MVPDemo