MVPAndroid学习笔记 - 架构

MVP基类抽取,面向接口

2019-07-25  本文已影响0人  CoderYuZ

通常项目中使用MVP架构并不会像MVP基础结构Demo中这样直接把类写死,而是要进行基类抽取,面向接口。

创建三个基类:BaseModelBaseViewBasePresenter

MVP整体流程是这样的:

  1. 用户操作View
  2. View把任务传递给Presenter
  3. Presenter把任务传递给Model
  4. Model执行具体的任务,任务完成后把结果返回给Presenter
  5. Presenter把结果返回给View

根据这个流程,按View -> Presenter -> Model 这个顺序去梳理基类,思路更清晰。

首先是BaseView,这里有两点需要完成:

  1. 根据上述流程第二步描述,View里肯定是需要持有Presenter,这个Presenter具体是哪个类只有到具体业务才知道,所以这个Presenter的赋值需要由具体的子类实现。
  2. 根据上述流程第五步描述,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
    }

}

协议完成,现在可以去写具体业务了:LoginModelLoginActivityLoginPresenter分别继承自:BaseModelBaseViewBasePresenter
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,这里有三种写法:

  1. 在Presenter里处理的具体的操作(Google的Demo里是这样做的)
  2. 用其他的业务类去做具体操作
  3. 在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这么一个完整的流程。
上面大码用了很多泛型,一定要写好泛型之后再用提示自动生成代码,不亲手撸一把体会不到有多爽,完全不需要强转什么的,谁爽谁知道!!!

代码到这里并没有完事,真是项目中的登录操作要做网络请求,这里如果处理耗时操作,就会出现内存泄漏,在LoginModelrequireLogin方法里模拟一个耗时操作:

            @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泄漏了我的哥
是的,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里执行,就会发生内存泄漏。

MVP基础结构Demo
项目地址

上一篇下一篇

猜你喜欢

热点阅读