认识Mvp

作者: 玖玖君 | 来源:发表于2019-07-01 17:12 被阅读0次

    从MVP开发模式至今,其实已经过了好久;很多开发者也已经轻车熟路的运用到了项目中,本来犹豫要不要写这篇文章,后来发现还是有人在问MVP怎么用,于是有了这篇文章。

    MVP模式本身其实很简单,一些开发者难以理解,或许是因为要么直接一个Demo下来了,要么一些资料写的思路不是那么清晰,那么本篇文章以几个问题作为引导,先帮助不理解的开发者们了解一下MVP的理念是什么,关于架构理念的理解,也可以参考之前的
    移动架构这么多,如何一次搞定所有

    1. 为什么使用MVP模式?

    答:这个问题其实问的是MVP的使用场景。每个项目的规模不同,业务不同,适用于不同的开发模式与架构,不要为了使用架构而去引入架构,要先问一下开发者自己,当前项目需要架构么?当前项目适合什么样的架构?架构千千万,但不是所有的架构都具有普适性。这个问题的目的其实是问MVP模式能解决什么问题?那么我们来分析一下。

    为什么引入架构呢?如果一个项目,每个类3-500行代码就解决了,引入架构也就是玩玩而已。这时候重度引入架构反而影响了运行效率,得不偿失。 引入架构的项目,必是到了一定的规模,也就是出现了一定程度的耦合与冗余,也一定意义上违反了面向对象的单一职责原则。

    那么MVP解决的问题就很明显了, 那就是冗余、混乱、耦合重。此时抛开MVP不讲,如果要我们自己想办法去解决,如何来解决呢?

    分而治之, 我们可能会想到,根据单一职责原则,Activity或Fragment或其他组件冗余了,那么必然要根据不同的功能模块,来划分出来不同的职责模块,这样也就遵循了单一职责的原则。站在前人的智慧上,或许很多人就想到了M(Model)V(View)C(Controller)。我们可以借鉴这一开发模式,来达到我们的目的,暂时将一个页面划分为

    UI模块,也即View层
    Model模块,也即数据请求模块
    Logic模块, 司逻辑处理

    这样划分首先职责分工就明确了,解决了混乱,冗余的问题。其实,一个项目从分包,到分类,最后拆分方法实现,都是遵从单一职责;一个职责划分越具有原子性, 它的重用性就越好,当然这也要根据实际业务而定。比如以下代码

     public class LoginActivity extends AppCompatActivity {
    
        EditText inputUserName;
        EditText inputPassword;
        Button btnLogin;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            btnLogin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    final String userName = inputUserName.getText().toString();
                    final String password = inputPassword.getText().toString();
    
                    boolean isEmptyUserName = userName == null || userName.length() == 0;
                    boolean isEmptyPassword = userName == null || userName.length() == 0;
    
                    boolean isUserNameValid =Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(userName   ).matches();
                    boolean isPasswordValid = Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(password ).matches();
    
                    if (isEmptyPassword || isEmptyPassword) {
                        Toast.makeText(LoginActivity.this, "请输入帐号密码", Toast.LENGTH_SHORT).show();
                    } else {
                        if (isUserNameValid && isPasswordValid) {
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    // ...登录请求
                                    boolean loginResult = false;
    
                                    if (loginResult) {
                                        Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
                                    } else {
                                        Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
                                    }
                                }
                            }).start();
                        } else {
                            Toast.makeText(LoginActivity.this, "帐号密码格式错误", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            });
        }
    }
    

    一个简单的登录, 包括点击事件, 取登录信息, 判断是否空, 校验是否正确, 请求登录, 返回处理。这样的代码结构混乱, 可读性差; 代码冗余,可重用性差; 不同功能的代码糅合在一起, 耦合性高。这只是很简单的一个小功能。
    上面说到, 面向对象的单一职责原则, 一个模块划分越具有原子性,也即划分越细,那么重用性就越高。如果我改成这样

    public class LoginActivity extends AppCompatActivity {
    
        EditText inputUserName;
        EditText inputPassword;
        Button btnLogin;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            btnLogin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    final String userName = getEditorText(inputUserName);
                    final String password = getEditorText(inputPassword);
    
                    if (isEmpty(userName) || isEmpty(password)) {
                        showTips("请输入帐号密码");
                    } else {
                        if (isValid(userName) && isValid(password)) {
                            // 登录
                            doLogin(userName, password);
                        } else {
                            showTips("帐号密码格式错误");
                        }
                    }
                }
            });
        }
    
        private boolean isValid(String s) {
            return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
        }
    
        private boolean isEmpty(String s) {
            return s == null || s.length() == 0;
        }
    
        private String getEditorText(EditText et) {
            return et.getText().toString();
        }
    
        private void showTips(String tips) {
            Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
        }
    
        private void doLogin(String username, String password) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // ...登录请求
                    boolean loginResult = false;
                    // 更新UI
                    notifyLoginResult(loginResult);
                }
            }).start();
        }
    
        private void notifyLoginResult(boolean loginResult) {
            if (loginResult) {
                showTips("登录成功");
            } else {
                showTips("登录失败");
            }
        }
    }
    

    将源码方法进行拆分后, isEmpty, isValid, showTips..等,产生的结果有亮点:
    1) 方法拆分后,可重用性提高了
    2) 相比而言,浏览一遍,我能基本清楚onClick里做了什么,也就是架构清晰了
    这就是单一职责原则的作用,提高可重用性, 减少代码冗余,开始露出清晰的思维脉络。
    有人就问了,这只是很简单的封装啊,有什么意义呢? 那我要反问了,什么是构架?构架的目的是什么?架构无非是从宏观到细微处得代码设计与调节,无论大的方向,还是小的细节,都需要慎重设计。构架的目的自然是为了更好的重用,扩展,解耦,以达到更好的代码健壮性, 扩展性, 提高开发效率。
    以上说明了单一职责的意义,以及带来的附加的益处。那么代码经过初步重构以后, 虽然更清晰了,消除了冗余,但是耦合的问题依旧。那怎么解决耦合问题呢?我们来看下半场

    一步步让你精通MVP

    Step01:MVP实现第一步, 将页面拆分为M/V/P三个模块

    MVP的概念太简单, 就是将一个页面划分为三部分: M(Model-数据请求/查询), V(View-UI更新), P(Presenter)。
    以上问题从方法的层面解决了单一职责(方法的原子性拆分), 那么整个页面还是不同的功能糅合在一起,怎么解决呢?就是上面说的划分为MVP三个部分,每一部分只负责单一的功能,比如
    View 只负责UI
    Model 只负责数据查询
    Presenter 只负责逻辑处理

    MVP最难的难点之一: 如何正确划分各模块

    Model很简单, 数据加载的界限很明确,很简单就划分出来了, 比如数据库操作, 比如文件查询, 比如网络请求, 可以连带着异步操作一起拿出来,划分为单独的Model层。

    View层与Presenter层交互性很频繁,很多人不清楚这一块代码算是View,还是Presenter呢?
    首先, 单纯的逻辑实现必然是Presenter处理的;单纯的View初始化也必然是View处理的,如findView这些。
    像登录模块,View与逻辑交错在一起,怎么区分呢 ? 我来给你分

    首先Login功能大抵分为以下子功能:

    取值, EditText帐号与密码(明确的View层,不涉及逻辑操作)
    判空与校验 (Presenter但涉及View, 因为使用帐号与密码,通过传参的形式)
    登录请求 (名副其实的Model, 处理明显在Presenter层)
    更新UI (View层)

    其实以上划分界限相对比较清晰,项目中难免遇到一些不好界限的,教你一招

    难以划分的必然包含View也包含逻辑处理。那么第一步,原子性拆分,将View与逻辑处理单独拆分成不同的方法。View 的部分在View层, 处理的部分在Presenter层有一些Toast, Dialog等的划分,根据Context作区分。 可以使用Application Context实现的,可以作为Presenter层; 必须使用Activity Context的,作为View层

    那么明确了M V P的拆分,看一下拆分结果

    1.View 部分

    public class LoginActivity extends AppCompatActivity {
    
        EditText inputUserName;
        EditText inputPassword;
        Button btnLogin;
    
        LoginPresenter presenter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            presenter = new LoginPresenter(this);
    
            btnLogin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    presenter.execureLogin(getEditorText(inputUserName), getEditorText(inputPassword));
                }
            });
        }
    
        private String getEditorText(EditText et) {
            return et.getText().toString();
        }
    
        public void showTips(String tips) {
            Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
        }
    
        public void notifyLoginResult(boolean loginResult) {
            if (loginResult) {
                showTips("登录成功");
            } else {
                showTips("登录失败");
            }
        }
    }
    

    2.Model部分

    public class LoginModel {
        private Handler handler;
    
        public LoginModel() {
            handler = new Handler();
        }
    
        public interface OnLoginCallback {
            void onResponse(boolean success);
        }
    
        public void login(String username, String password, final OnLoginCallback callback) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // ...请求接口
                    boolean result = true; // 假设这是接口返回的结果
                    callback.onResponse(result);
                }
            }).start();
        }
    }
    

    3.Presenter部分

    public class LoginPresenter {
    
        private LoginModel model;
        private LoginActivity activity;
        private String verifyMsg;
    
        public LoginPresenter(LoginActivity activity) {
            this.activity = activity;
            model = new LoginModel();
        }
    
        public void execureLogin(String username, String password) {
            boolean verifyBefore = verifyBeforeLogin(username, password);
            if (verifyBefore) {
                // 校验通过,请求登录
                model.login(username, password, new LoginModel.OnLoginCallback() {
                    @Override
                    public void onResponse(boolean success) {
                        // 登录结果
                        activity.notifyLoginResult(success);
                    }
                });
            } else {
                // 校验失败,提示
                activity.showTips(verifyMsg);
            }
        }
    
        private boolean verifyBeforeLogin(String username, String password) {
            boolean isEmpty = isEmpty(username) || isEmpty(password);
            boolean isValid = isValid(username) && isValid(password);
            if (isEmpty) {
                verifyMsg = "请输入帐号或密码";
                return false;
            }
            if (isValid) {
                return true;
            }
            verifyMsg = "帐号或密码错误";
            return false;
        }
    
        private boolean isValid(String s) {
            return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
        }
    
        private boolean isEmpty(String s) {
            return s == null || s.length() == 0;
        }
    }
    

    通过以上代码可以看出,Toast提示, 更新登录状态等, 都拆分在View层; 校验与登录则拆分在Presenter层;网络请求则拆分到了Model层。这样每一层都只处理本层的业务,从大的方向上进行了单一职责拆分,从而整体符合单一职责原则。

    根据MVP将页面拆分为了3层,单一职责的原则我们已经完全符合了。但是仔细看,忽然发现相互之间还存在依赖,解耦效果并不是那么理想。那我们要思考了,是什么原因导致耦合尚在? 那就是对象持有,看看我们的项目

    Presenter持有View(Activity)对象,同时持有Model对象
    View持有Presenter对象

    所以要在持有对象上下功夫了, MVP是怎么解决对象持有问题的?

    面向接口编程
    Step02: MVP实现第2步, 使用接口通信,进一步解耦

    对于面向对象设计来讲, 利用接口达到解耦目的已经是人尽皆知的了。 这次改动很小,把对象持有改为接口持有即可。

    View持有Presenter对象改为持有View接口
    Presenter持有View对象改为持有View接口

    既然持有接口,肯定要在View与Presenter分别实现供外部调用的接口。View供Presenter调用的方法有notifyLoginResult和showTips; Presenter供View调用的方法有executeLogin。 那么先来实现接口如何?看代码

    Presenter接口

    public interface IPresenter {
        /**
         * 执行登录
         *
         * @param username
         * @param password
         */
        void executeLogin(String username, String password);
    }
    

    View接口

    public interface IView {
       /**
        * 更新登录结果
        *
        * @param loginResult
        */
       void notifyLoginResult(boolean loginResult);
    
       /**
        * Toast提示
        *
        * @param tips
        */
       void showTips(String tips);
    }
    

    接口的作用是对外部提供一种供外部调用的规范。因此这里我们把外部需要调用的方法抽象出来,加入到接口中。接口有了,且接口代表的是View或Presenter的实现,所以分别实现它们。看代码

    Presenter实现接口

    public class LoginPresenter implements IPresenter {
    
        private LoginModel model;
        private LoginActivity activity;
        private String verifyMsg;
    
        public LoginPresenter(LoginActivity activity) {
            this.activity = activity;
            model = new LoginModel();
        }
    
        @Override
        public void executeLogin(String username, String password) {
            boolean verifyBefore = verifyBeforeLogin(username, password);
            if (verifyBefore) {
                // 校验通过,请求登录
                model.login(username, password, new LoginModel.OnLoginCallback() {
                    @Override
                    public void onResponse(boolean success) {
                        // 登录结果
                        activity.notifyLoginResult(success);
                    }
                });
            } else {
                // 校验失败,提示
                activity.showTips(verifyMsg);
            }
        }
    
        private boolean verifyBeforeLogin(String username, String password) {
            boolean isEmpty = isEmpty(username) || isEmpty(password);
            boolean isValid = isValid(username) && isValid(password);
            if (isEmpty) {
                verifyMsg = "请输入帐号或密码";
                return false;
            }
            if (isValid) {
                return true;
            }
            verifyMsg = "帐号或密码错误";
            return false;
        }
    
        private boolean isValid(String s) {
            return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
        }
    
        private boolean isEmpty(String s) {
            return s == null || s.length() == 0;
        }
    }
    

    View实现接口

    public class LoginActivity extends AppCompatActivity implements IView{
    
        EditText inputUserName;
        EditText inputPassword;
        Button btnLogin;
    
        LoginPresenter presenter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            presenter = new LoginPresenter(this);
    
            btnLogin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    presenter.executeLogin(getEditorText(inputUserName), getEditorText(inputPassword));
                }
            });
        }
    
        private String getEditorText(EditText et) {
            return et.getText().toString();
        }
    
        @Override
        public void showTips(String tips) {
            Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void notifyLoginResult(boolean loginResult) {
            if (loginResult) {
                showTips("登录成功");
            } else {
                showTips("登录失败");
            }
        }
    
    }
    

    这一步很简单,在接口中提供对外部调用的方法,然后分别在View和Presenter中实现它们。接口与实现都有了,还记得我们的目的是什么吗?是把持有的对象替换为接口,撸起来,看代码

    // 这是View持有的接口,在onCreate中初始化的对象由原来的LoginPresenter改为了IPresenter。
    public class LoginActivity extends AppCompatActivity implements IView{
    
        EditText inputUserName;
        EditText inputPassword;
        Button btnLogin;
    
        IPresenter presenter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            presenter = new LoginPresenter(this);
            ...
    }
    
    // 这是Presenter持有的接口,在构造中由原来的LoginActivity改为了IView
    
    public class LoginPresenter implements IPresenter {
    
        private LoginModel model;
        private String verifyMsg;
    
        private IView activity;
    
        public LoginPresenter(IView activity) {
            this.activity = activity;
            model = new LoginModel();
        }
    
        ...
    

    通过以上重构,我们就实现了传入接口达到解耦的目的。怎么样,如果一步步的来,其实一点都不难吧。那么我们来总结一下MVP模式吧

    MVP遵从的面向对象原则

    1) 单一职责

    每个模块只负责该模块的本职工作,不越界。 如View负责UI初始化与更新, Model负责数据查询与异步, 至于逻辑判断,业务实现,放心的扔给Presenter中就好了。

    2) 面向接口通信

    对象的持有是造成耦合的本质原因之一,因此要达到解耦的目的,替换为接口持有最是合适不过。

    MVP模式的难点

    .代码块归属模块的划分

    当一块代码,你不知道如何划分模块,99%的原因是不具备原子性。整篇文字其实我都在强调原子性,也在强调原子性带来的好处。包括子系统的原子性(插件化), 子模块的原子性(组件化), 层的原子性(MVX)以及方法的原子性(最小域方法拆分)。
    因此要正确的找到分离点, 划分每一个CodeBlock从属于什么层, 那么先进行原子性拆分吧。

    MVP流程总结

    1) 以层为关注点进行分别设计

    说白了就是先定义3个层次:View Model Presenter, 然后把正确的代码分别规划到对应的层中。

    2) 设计通信接口

    核心是明白接口的意义,对外部层提供统一调用的规范。目标是外部层,如Presenter要调用View层,那么这就是View层的接口要考虑的,反之亦然。注意,不是所有View中的method都加到IView的接口中

    3) BaseInterface 与 Contract的概念

    MVP引入了BaseInterface 与Contract的概念。如果单纯的mvp,可能很多人都理解,但是加上这两个概念,加深了理解难度。
    base-interface 就是我们常用的base的概念,目的就是规范统一的操作。比如显示一个Toast, 判断网络是否连接,跳转动画等,我们都放在BaseActivity中,因为所有的Activity都需要这些。接口的继承也是这个目的。如
    登录功能

    1) 我们需要一个Presenter,于是有了LoginPresenter
    2) 我们需要一个LoginPresenter的接口,为View层提供调用,于是有了ILoginPresenter
    3) 无论登录,还是注册,还有其他功能,所有的Presenter都需要一个功能start, 于是有了IPresenter

    IPresenter提供了一个所有Presenter接口共有的操作,就是start,也即初始化的加载

    Contract的概念

    这个概念的引入只是为了统一管理一个页面的View和Presenter接口。每个页面对应一个View(Activity或Fragment), 一个IView(View接口), 一个Presenter, 一个IPresenter(Presenter接口),一个Contract(一个包含View接口和Presenter接口的接口)。如

    public interface LoginContract {
    
        interface View {
            void notifyLoginResult();
        }
    
        interface Presenter {
            void login(String username, String password);
        }
    
    }
    

    说白了就是一个仓库,又放水果,又放蔬菜;而不是没有仓库时,蔬菜和水果扔的满地都是,这样既不好管理,也不好看。类似你会创建chat(聊天), circle(朋友圈),mine(我的),contacts(通讯录)等包,而不是直接在com.tecent.wx下面放所有的类一样。

    下面带你从头到尾封装一个完整的MVP框架。

    1. 首先来思考,我们最先定义的应该是什么? 当然是公共接口。

    View的公共接口(MVP-Samples中的IView)没有公共的操作,我们定义一个空的接口,用于统一规范。

    public interface IView {
    }
    

    Presenter的公共接口(MVP-Samples中的IPresenter)也没有公共的操作,在mvp提供的samples中是带了一个start的,但是这里不需要。为什么呢?因为我们还要来一个BasePresenter。所以我们还是定义一个空的接口,用于统一规范。

    public interface IPresenter {
    }
    

    以上两个接口,是用于给View与Presenter的接口继承的,注意,不是View或Presenter本身继承。因为它定义的是接口的规范, 而接口才是定义的类的规范。

    1. 有了接口规范,我们就需要用接口继承该规范,因为接口是随着业务产生的,因此等有了接口再继承。
    2. 开发模式本身是为了业务而生,因此我们生成一个业务,这里以登录为例。先来分析下业务:

    EditText取得输入的UserName与Password

    校验(包括判断是否是空的, 是否符合输入规范比如不允许输入特殊字符)

    校验通过, 执行登录请求; 不通过,直接提示错误

    登录请求

    根据登录结果,提示登录成功或失败

    伴随着登录结果更新UI

    以上功能很容易就分好层了。

    View
    提示错误信息 / 提示登录结果
    获取EditText的UserName和Password

    Presenter
    校验
    执行登录操作

    Model
    登录请求,返回结果

    根据功能定义接口了,分别定义一个View的接口和Presenter的接口。还记得上面说的Contract与base-interface吗? 是的,定义的接口要继承IView与IPresenter, 而且由Contract统一管理

    public interface LoginContract {
    
        interface View extends IView {
    
            // View中的2个功能:
            // 1) 取得登录需要的username, password # 不需要对Presenter层提供调用
            // 2) 提示错误信息, 提示登录结果 # 需要Presenter层调用,因为校验和登录都是在Presenter层的
            // 因此2)是View层提供的对外方法,需要在接口中定义
    
            /**
             * 提示一个Toast
             *
             * @param msg
             */
            void showToast(String msg);
    
        }
    
        interface Presenter extends IPresenter {
    
            // Presenter中的2个功能:
            // 1) 校验 # 看你怎么写,既可以在View层中调用校验方法,也可以在Presenter层中,这里定义为直接在Presenter中校验,彻底和View解耦
            // 2) 登录 # 先执行校验,再执行登录,需要在View层点击登录时调用
            // 因此2)是Presenter对外层提供的方法,需要在接口中定义
    
            /**
             * 登录操作
             *
             * @param username
             * @param password
             */
            void login(String username, String password);
    
        }
    }
    

    以上Contract(称之为功能仓库)分别定义了View与Presenter接口,并添加了接口的定义过程分析。

    4) 接口定义完成了,下一步是什么呢? 肯定是实现接口,加入功能吧。定义Presenter与View分别实现接口,加入对应功能。

    public class LoginActivity extends AppCompatActivity implements LoginContract.View {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
        }
    
        @Override
        public void showToast(String msg) {
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        }
    }
    
    **********************************************************************************************************
    public class LoginPresenter implements LoginContract.Presenter {
    
        @Override
        public void login(String username, String password) {
            // 校验直接放在登录流程
            boolean isVerifySuccessully = verifyLoginInfo();
            if (isVerifySuccessully) {
                // 请求登录
                LoginModel.requestLogin(username, password, new LoginModel.RequestCallback() {
                    @Override
                    public void onResponse(boolean result) {
                        if (result) {
                            // 提示登录成功
                        } else {
                           // 提示登录失败
                        }
                    }
                });
            } else {
                // 校验失败,提示错误
            }
        }
    
        private boolean verifyLoginInfo() {
            // 这里校验登录信息
            // 校验帐号,密码是否为空
            // 校验帐号,密码是否符合要求
            return true;
        }
    }
    

    以上分别实现功能接口,生成了LoginActivity和LoginPresenter。 有些操作和架构无关,比如校验和登录请求,都知道怎么做,就不写了。这里只强调框架。
    因为还没有持有对象,现在还不能相互调用。那么实现了功能,下一步该做什么呢? 那就是层通信了,将两个层次View和Presenter关联起来形成完整的功能。

    public class LoginActivity extends AppCompatActivity implements LoginContract.View {
    
       LoginContract.Presenter mPresenter;
    
       Button loginBtn;
       EditText etUser, etPwd;
    
       @Override
       protected void onCreate(@Nullable Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
    
           mPresenter = new LoginPresenter(this);
    
           loginBtn.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   mPresenter.login(etUser.getText().toString(), etPwd.getText().toString());
               }
           });
       }
    
       @Override
       public void showToast(String msg) {
           Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
       }
    
    *********************************************************************************************************
    public class LoginPresenter implements LoginContract.Presenter {
    
       LoginContract.View mView;
    
       public LoginPresenter(LoginContract.View mView) {
           this.mView = mView;
       }
    
       @Override
       public void login(String username, String password) {
           // 校验直接放在登录流程
           boolean isVerifySuccessully = verifyLoginInfo();
           if (isVerifySuccessully) {
               // 请求登录
               LoginModel.requestLogin(username, password, new LoginModel.RequestCallback() {
                   @Override
                   public void onResponse(boolean result) {
                       if (result) {
                           // 提示登录成功
                           mView.showToast("登录成功");
                       } else {
                           // 提示登录失败
                           mView.showToast("登录失败");
                       }
                   }
               });
           } else {
               // 校验失败,提示错误
               mView.showToast("无效的帐号或密码");
           }
       }
    
       private boolean verifyLoginInfo() {
           // 这里校验登录信息
           // 校验帐号,密码是否为空
           // 校验帐号,密码是否符合要求
           return true;
       }
    }
    

    在LoginActivity的onCreate中,我们绑定了Presenter初始化,声明的是一个接口。在LoginPresenter的构造中,我们同时传入的View的接口。此时,View层与Presenter层相互引用的是对方的接口。在LoginActivity中模拟了登录操作,此时View和Presenter层的功能已经完整的关联在一起了。
    注意:LoginActivity在相对的生命周期中需要销毁Presenter引用,由于后面会封装,这里没加。
    走到这一步基本就是一个完整的MVP开发模式了,从划分层次到接口通信,其实还是挺简单的,不是么?下面继续来优化这个框架,我们考虑以下几个问题:

    每个Activity或者Fragment都要初始化或管理Presenter,累不累?
    同样的,每个Presenter都要管理View,累不累?

    那么,现在来继续优化一下MVP框架的使用。优化之前,我们先来考虑:

    Presenter基类抽取

    公共元素有哪些 ?

    Presenter公共元素,其实主要有两个: Context, View接口。注意:Presenter不要传入Activity的Context;如果需要用到Activity的Context, 那么Presenter层就不单纯了。那么只能是Application的Context。

    我们获取Application Context的方式有两种,AppContext(你的Application)的静态获取 和 Activity的getApplicationContext。这里使用传入的Application Context吧!

    很多网上View的获取是定义一个AttachView的方法, 这里使用在构造中直接传入。

    **
     * Created by archer.qi on 2017/2/6.
     */
    public abstract class BasePresenter<AttachView extends IView> {
        private Context mContext;
        private AttachView mView;
    
        public BasePresenter(Context context, AttachView view) {
            if (context == null) {
                throw new NullPointerException("context == null");
            }
            mContext = context.getApplicationContext();
            mView = view;
        }
    
        /**
         * 获取关联的View
         *
         * @return
         */
        public AttachView getAttachedView() {
            if (mView == null) {
                throw new NullPointerException("AttachView is null");
            }
            return mView;
        }
    
        /**
         * 获取关联的Context
         *
         * @return
         */
        public Context getContext() {
            return mContext;
        }
    
        /**
         * 清空Presenter
         */
        public void clearPresenter() {
            mContext = null;
            mView = null;
        }
    
        /**
         * View是否关联
         *
         * @return
         */
        public boolean isViewAttached() {
            return mView != null;
        }
    
        /**
         * 网络是否连接
         *
         * @return
         */
        public boolean isNetworkConnected() {
            if (mContext == null) {
                throw new NullPointerException("mContext is null");
            }
            return NetworkHelper.isNetworkConnected(mContext);
        }
    
        public abstract void start();
    
        public abstract void destroy();
    }
    

    以上是我们抽取的Presenter基类。实现了:

    1.初始化时绑定View接口,并在clear时清除接口
    2.自动获取ApplicationContext(还是建议不这样,直接传Application的Context)
    3.View状态判定
    4.网络连接判断(因为Presenter中执行网络请求比较频繁,你可以根据业务自定义多个方法)
    5.satrt初始化方法与destroy销毁方法(结合后面的MVPCompatActivity自动销毁)

    注意:在使用View时,请先判断View状态;否则View异常销毁时会报NullPoiterException。
    如果有线程或者Handler一定要在destroy中销毁,避免造成内存泄漏。

    继续看View的优化,包含Activity, Fragment, Layout与Adapter

    for Activity

    /**
     * MVP - Activity基类
     * Created by archer.qi on 2017/1/24.
     */
    public abstract class MVPCompatActivity<T extends BasePresenter> extends RootActivity {
        protected T mPresenter;
    
        @Override
        protected void onStart() {
            super.onStart();
            if (mPresenter == null) {
                mPresenter = createPresenter();
            }
            mPresenter.start();
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            mPresenter.clearPresenter();
            mPresenter = null;
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
            super.onSaveInstanceState(outState, outPersistentState);
            mPresenter.clearPresenter();
            mPresenter = null;
        }
    
        /**
         * 创建一个Presenter
         *
         * @return
         */
        protected abstract T createPresenter();
    
    }
    

    for Fragment

    /**
     * Created by archer.qi on 2017/3/1.
     */
    public abstract class MVPCompatFragment<T extends BasePresenter> extends RootFragment {
        protected T mPresenter;
    
        @Override
        public void onStart() {
            super.onStart();
            if (mPresenter == null) {
                mPresenter = createPresenter();
            }
            mPresenter.start();
        }
    
        @Override
        public void onStop() {
            super.onStop();
            if (mPresenter != null) {
                mPresenter.clearPresenter();
                mPresenter = null;
            }
        }
    
        protected abstract T createPresenter();
    
    }
    

    for Layout

    /**
     * @author qichunjie 2018/1/18
     */
    
    public abstract class MVPCompatLayout<T extends BasePresenter> extends RootLayout {
    
        protected T mPresenter;
    
        public MVPCompatLayout(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            mPresenter = createPresenter();
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            if (mPresenter != null) {
                mPresenter.clearPresenter();
                mPresenter = null;
            }
        }
    
        protected abstract T createPresenter();
    }
    

    for Adapter

    /**
     * @author qichunjie 2018/1/22
     */
    
    public abstract class MVPCompatRecyclerAdapter<T, P extends BasePresenter> extends RootRecyclerAdapter<T> {
        protected P mPresenter;
    
        public MVPCompatRecyclerAdapter(Context context, List data) {
            super(context, data);
        }
    
        protected abstract P createPresenter();
    
    
        @Override
        public void onViewAttachedToWindow(RecyclerViewHolder holder) {
            super.onViewAttachedToWindow(holder);
            mPresenter = createPresenter();
        }
    
        @Override
        public void onViewDetachedFromWindow(RecyclerViewHolder holder) {
            super.onViewDetachedFromWindow(holder);
            if (mPresenter != null) {
                mPresenter.clearPresenter();
                mPresenter = null;
            }
        }
    }
    

    通过继承以上View的Base, 可以自由实现初始化以及销毁。轻松实现MVP。

    最后,补充的RootActivity, 作为一个Base的Activity,是根据不同的业务决定里面的内容的,因此这里很少

    /**
     * @author archer.qi
     *         Created on 2017/6/27.
     */
    public abstract class RootActivity extends AppCompatActivity {
        protected Context mContext;
        protected Context mAppContext;
    
        private View mContentView;
    
        private Bundle mBundleObj;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mAppContext = getApplicationContext();
            mContext = this;
            mContentView = getLayoutInflater().inflate(getLayoutRes(), null);
            setContentView(mContentView);
            ButterKnife.bind(this);
            init();
        }
    
        protected abstract int getLayoutRes();
    
        protected abstract void init();
    
        /**
         * findViewById
         *
         * @param resId
         * @param <T>
         * @return
         */
        protected <T extends View> T $(int resId) {
            return (T) findViewById(resId);
        }
    
        /**
         * Toast
         *
         * @param toast
         */
        protected void showToast(String toast) {
            Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();
        }
    
        /**
         * get a bundle from reuse.
         *
         * @return
         */
        protected Bundle obtainBundle() {
            if (mBundleObj == null) {
                mBundleObj = new Bundle();
            } else {
                mBundleObj.clear();
            }
            return mBundleObj;
        }
    
    
    }
    

    通过以上分析, 是不是觉得So Easy! so TM EZ

    !!!!!

    相关文章

      网友评论

        本文标题:认识Mvp

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