一、概述
MVP相比较于MVC ,能够解决MVC的内存泄漏问题。并且业务模块分的更加明确。
M:model层 一般指javabean对象 。当然也可以处理业务逻辑
V:activity 发起请求,交给presenter处理
P:Presenter层 负责将activity的请求交给model层处理,也可以自己处理。并将处理结果返回给view层
比如我们真实的业务中,登陆请求。下面看看该如何封装一个mvp框架·。
二、使用
1、创建M层,V层,P层基类
(1)Model层基类BaseModel,持有Presenter层的引用,并且提供契约接口,供子类实现。代码如下:
/**
* 需要拿到契约CONTRACT
* 使用抽象类 子类必须重写抽象类的抽象方法
* @param <P>
*/
public abstract class BaseModel<P extends BasePresenter,CONTRACT> {
protected P p;
public BaseModel(P p) {
this.p = p;
}
public abstract CONTRACT getContract();
}
(2)View层基类BaseView,持有Presenter层引用,并且提供契约接口,供子类实现。代码如下:
package com.example.mvpdemo.base;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
/**
* view层基类
* 需要交给P层任务 需要将契约传给P层,P层就可以操作view层
* @param <P>
*/
public abstract class BaseView<P extends BasePresenter,CONTRACT> extends AppCompatActivity {
protected P p;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//获取子类的Presenter
p = getPresenter();
//p层绑定view层
p.bindView(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
p.uunBindView();
}
public abstract CONTRACT getContract();
public abstract P getPresenter();
public abstract int error();
}
(2)Presenter层基类BasePresenter,持有View层和Model层引用,并且提供契约接口,供子类实现。代码如下:
package com.example.mvpdemo.base;
import java.lang.ref.WeakReference;
/**
* 需要将view任务交给model层
* 需要将结果通知view层更新
*/
public abstract class BasePresenter<V extends BaseView, M extends BaseModel, CONTRACT> {
protected M m;
//当前view弱引用,防止view层内存泄漏
private WeakReference<V> vWeakReference;
public BasePresenter() {
m = getModel();
}
/**
* 绑定view
*/
public void bindView(V v) {
vWeakReference = new WeakReference<>(v);
}
public V getView() {
if (vWeakReference != null) {
return vWeakReference.get();
}
return null;
}
/**
* 、
* 解绑view
*/
public void uunBindView() {
if (vWeakReference != null) {
vWeakReference.clear();
vWeakReference = null;
System.gc();
}
}
public abstract CONTRACT getContract();
//子类实现中拿到model实例
public abstract M getModel();
}
该类中使用弱引用的形式持有view层的引用。并且提供添加弱引用和解除弱引用的方法。这样就能够够解决内存泄露的问题。
(4)创建网络请求返回结果实体基类BaseBean,主要记录网络请求的状态,错误码及错误信息。代码如下:
package com.example.mvpdemo.base;
public class BaseBean {
private String msg;
private boolean isSuccess;
private int errorCode;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
}
2、创建登陆模块各层实现类
(1)创建契约接口类LoginContract,主要提供了供子类去实现的契约接口,代码如下:
package com.example.mvpdemo.login;
import com.example.mvpdemo.base.BaseBean;
/**
* 用于协商view层 model层 presenter层业务
*/
public interface LoginContract {
/**
* 处理登陆请求
*/
interface Model<T extends BaseBean>{
void excuteLogin(String userName,String pwd) throws Exception;
}
/**
* 接受处理结果更新界面
* 做约束需要集成baseBean
*/
interface View<T extends BaseBean>{
void handlerResult(T t);
}
/**
* 接受view的请求交给model层处理
* 接受model层处理结果返回给view层
*/
interface Presenter<T>{
void requestLogin(String userName,String pwd);
void responseResult(T t);
}
}
(2)创建LoginActivity。继承BaseView,并重写getContract()方法和getPresenter()方法。代码如下:
package com.example.mvpdemo.login;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.example.mvpdemo.base.BaseView;
import com.example.mvpdemo.bean.LoginBean;
public class LoginActivity extends BaseView<LoginPresenter,LoginContract.View> {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
p.getContract().requestLogin("qq","1111");
}
@Override
public LoginContract.View getContract() {
return new LoginContract.View<LoginBean>() {
@Override
public void handlerResult(LoginBean loginBean) {
}
};
}
@Override
public LoginPresenter getPresenter() {
return new LoginPresenter();
}
@Override
public int error() {
return 0;
}
}
注意这里的getContract()方法,当我们的请求结果返回成功后,由于P层可以通过getView方法获取当前操作的·view。那么可以调用view.getContract()创建接口实例。接着就可以将结果进行返回。显然是面向接口编程。
(3)创建LoginModel,继承BaseView,并重写getContract()方法。代码如下:
package com.example.mvpdemo.login;
import com.example.mvpdemo.base.BaseModel;
import com.example.mvpdemo.bean.LoginBean;
public class LoginModel extends BaseModel<LoginPresenter,LoginContract.Model> {
public LoginModel(LoginPresenter loginPresenter) {
super(loginPresenter);
}
@Override
public LoginContract.Model getContract() {
return new LoginContract.Model<LoginBean>() {
@Override
public void excuteLogin(String userName, String pwd) throws Exception{
LoginBean loginBean = new LoginBean();
loginBean.setPwd("1111");
loginBean.setUserName("qb");
if(userName.equalsIgnoreCase("qb")){
//交给P层
p.getContract().responseResult(loginBean);
}else{
p.getContract().responseResult(null);
}
}
};
}
}
同样,由于P层持有M层的引用。那么可以调用model.getContract()创建接口实例。并将请求交给Model层处理。
(4)创建LoginPresenter,继承BasePresenter。并重写getContract()和getModel()方法。代码如下:
package com.example.mvpdemo.login;
import com.example.mvpdemo.base.BasePresenter;
import com.example.mvpdemo.bean.LoginBean;
public class LoginPresenter extends BasePresenter<LoginActivity, LoginModel, LoginContract.Presenter> {
@Override
public LoginContract.Presenter getContract() {
return new LoginContract.Presenter<LoginBean>() {
@Override
public void requestLogin(String userName, String pwd) {
try {
//1、交给model层
m.getContract().excuteLogin(userName, pwd);
//2、返回结果自己处理
//responseResult();
//3、写一个处理类返回结果 比如HttpEngine
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void responseResult(LoginBean loginBean) {
//拿到view层契约,是一个接口,在activity中已经实现。面向接口编程
getView().getContract().handlerResult(loginBean);
}
};
}
@Override
public LoginModel getModel() {
return new LoginModel(this);
}
}
这里m.getContract().excuteLogin(userName, pwd),把处理逻辑交给M层实现;getView().getContract().handlerResult(loginBean),将结果返回给V层。
三、内存泄漏验证
1、首先我们先把baseView的这一段代码注释掉:
@Override
protected void onDestroy() {
super.onDestroy();
//p.unBindView();
}
2、运行程序,点击按钮,日志如下,我这里返回的结果是我自己模拟的数据
3、点击内存分析工具profiler。进入界面视图。此时将activity关闭,进行以下步骤:
4、第一步、第二步完成后进入以下界面,选中packet模式,找到com包名,可以发现,存在我们的LoginActivity。说明发生了内存泄漏
5、将之前注释的代码恢复,再次执行以上步骤,发现没有发现我们的包名,这样内存泄漏就得到了解决