MVP基类抽取,面向接口

作者: CoderYuZ | 来源:发表于2019-07-25 16:53 被阅读0次

    通常项目中使用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
    项目地址

    相关文章

      网友评论

        本文标题:MVP基类抽取,面向接口

        本文链接:https://www.haomeiwen.com/subject/ztdcrctx.html