美文网首页
Google MVP Demo深入学习和总结

Google MVP Demo深入学习和总结

作者: itbird01 | 来源:发表于2022-02-27 09:30 被阅读0次

    Google在2016年推出了官方的Android MVP架构Demo,本文主要分析一下官方的MVP Demo,并且借由自己的一些经验,提出一些学习过程中,遇到的问题和自己的改进、封装措施。

    1.什么是MVP?

    MVP架构已经推出很多年了,现在已经非常普及了,我在这里就不过多介绍,简单的说,它分为以下三个层次:

    • Model:数据模型层,主要用来数据处理,获取数据;
    • View:显示界面元素,和用户进行界面交互;
    • Presenter: 是Model和View沟通的桥梁,不关心具体的View显示和Model的数据处理。View层中所有的逻辑操作都通过Presenter去通知Model层去完成,Model中获取的数据通过Presenter层去通知View层显示。
      MVP架构最大的好处,就是把传统MVC架构中View层和Control层的复杂关系完全解耦,View层只关心界面显示相关的工作即可,Model层仅获取数据,处理逻辑运算即可,各司其职,而不用关心其他工作。而且大家发现没有,这样设计的话,很好写单元测试代码,针对于View、Presenter、Model层我们可以分别选用适合的单元测试框架,不用再像MVC/MVVM一样,由于代码混在或者分离在多处,到处无法“单一职责”的去完成每个类的单元测试代码编写。

    2.官方Demo怎么去做的?

    整体包结构.png

    我们为了方便去分析,我这里简化代码,我们逐步分析,BaseView 和 BasePresenter,BaseView谷歌是这么写的(其实就是view的接口,展示view),以下样例为了理解,我简化处理了部分代码

    package com.itbird.mvp.google;
    
    /**
     * Google Demo
     * Created by itbird on 2022/2/25
     */
    public interface BaseView<T> {
        //View中,设置presenter对象,使View可以持有Presenter对象引用
        void setPresenter(T presenter);
    }
    

    将presenter 传值在里面,传值的作用,为了能让开发者使用presenter去操作view。presenter是主要作用于与网络数据沟通,获取model,处理数据一类的。
    将presenter 传值在里面,传值的作用,为了能让开发者使用presenter去操作view。presenter是主要作用于与网络数据沟通,获取model,处理数据一类的。

    package com.itbird.mvp.google;
    
    /**
     * Google Demo
     * Created by itbird on 2022/2/25
     */
    public interface BasePresenter {
    }
    

    start主要执行一些初始化一类的东西,之后为view和presenter建立契约类,构建出如下契约类

    package com.itbird.mvp.google;
    
    /**
     * Google Demo
     * Created by itbird on 2022/2/25
     */
    public interface TaskDetailContract {
        interface View extends BaseView<Presenter> {
            //界面UI刷新方法
            void updateTextView(String s);
        }
    
        interface Presenter extends BasePresenter {
            void loadDataFromModel();
        }
    }
    
    

    为了方便解读,我进行了精简(删除了部分方法),我们分析一下,这里多了一个契约类,其实里面就是把两个接口写在了一起,一个用来给具体的我们的界面实现类继承去实现具体的UI展示和刷新方法,一个用来给具体的Presenter操作类继承去实现具体的逻辑业务操作,这个过程中,Google的设计意图是,由于setPresenter(T presenter)接口的存在,View类拥有Presenter的对象引用,所以可以操作Presenter类的具体业务逻辑方法。
    我们看一下Google的具体Demo样例:
    TaskGoogleActivity

    package com.itbird.mvp.google;
    
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import com.itbird.R;
    
    public class TaskGoogleActivity extends AppCompatActivity implements TaskDetailContract.View {
    
        private static final String TAG = TaskGoogleActivity.class.getSimpleName();
        private TaskDetailContract.Presenter mPresenter;
        private TextView mTextView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.mvp_view_test);
            findViewById(R.id.textview);
            Log.e(TAG, TAG + " onCreate");
            new TaskGooglePresenter(this);
        }
    
        @Override
        public void setPresenter(TaskDetailContract.Presenter presenter) {
            mPresenter = presenter;
        }
    
        @Override
        public void updateTextView(String s) {
            mTextView.setText(s);
        }
    }
    

    TaskGooglePresenter

    package com.itbird.mvp.google;
    
    public class TaskGooglePresenter implements TaskDetailContract.Presenter {
        private static final String TAG = TaskGooglePresenter.class.getSimpleName();
        TaskDetailContract.View mView;
    
        public TaskGooglePresenter(TaskDetailContract.View view) {
            mView = view;
            mView.setPresenter(this);
        }
    
        @Override
        public void loadDataFromModel() {
            //TODO :loaddata,此处可以用model、或者进行业务操作
            //调用界面方法,进行数据刷新
            mView.updateTextView("loaddata success!!!");
        }
    }
    

    小结
    我们单从设计来说, Google MVP Demo的确向我们展示了MVP的优点:
    1)业务、数据、视图分离、解耦
    2)由于完全分离,所以我们很方便针对于每层去选取对应的单元测试模式,例如针对于数据层可以使用(Junit+Mockito)、视图层可以使用(AndroidJunitRunner+Espresso)、业务层可以使用(Junit+Mockito)
    3)通过Contract 契约类,完全将视图、业务相关的接口,封装放在了一个地方,简单明了,针对于开发者来说,不容易忘记和辅助养成习惯
    但是,大家发现了没有,这里有两个坑存在:
    1)setPresenter接口完全没有必要存在,因为Presenter对象一定是在View类中new出来的,我既然都有它自己的对象了,我干嘛还要在Presenter内部,去调用mView.setPresenter(this);,再返回View中,再去保存一个引用,代码看着很怪
    2)我们知道MVP的精髓在与,P、V肯定要相互交互,他们互相要持有对方的引用,通过上面一点,我们知道Presenter对象一定是在View类中new出来的,所以View肯定有Presenter对象的引用,这个没问题了。但是Presenter要想拥有View的引用,只能通过Presenter构造方法、或者Presenter内部有一个setView方法,让开发者自己去调用setView或者实现带参构造方法,从设计角度来说,这明显是一个坑

    3.MVP的优化和封装

    基于上面Google MVP demo的分析,有绝对的优点,但我们也发现了,有Demo设计上的缺陷,这时作为一个有想法的开发者来说,我们就想要自己去封装解决一下。
    优化重构需求为:设计一个MVP模式的Demo工程,开发者不需要去关心View 、Presenter的绑定关系,设计结构里面可以考虑到资源的统一释放和步骤的约束
    1)首先我们分析一下需求,我们想要直接在框架里面,把Presenter、View的相关步骤约束在一起,那这时自然而然的就想到了Java的模板设计模式,我们把Presenter的初始化函数可以和Activity的生命周期关联在一起,这样开发者就不需要去关心什么时候调用Presenter的初始化函数了
    2)由第二节分析可知,实际上View已经持有Presenter的引用,所以实际上BaseView中不需要Present的T的泛型,也就不需要setPresenter方法了
    3)由上面分析可知,Presenter需要去持有View的引用,那么我们可以把这个方法放入BasePresenter中,这样开发者肯定就不会忘记了
    4)这些方法,我们想要约束开发者必须去实现,那么按照模板设计模式来说,BasePresenter、BaseActivity肯定是抽象类,里面有抽象方法,是要开发者必须去实现的

    设计如下:


    image.png

    IPresenter

    package com.itbird.mvp.my;
    
    /**
     * Created by itbird on 2022/2/25
     */
    public interface IPresenter {
        //view绑定
        void onAttach(IView view);
        //view解绑
        void onDetach();
    }
    

    IView

    package com.itbird.mvp.my;
    
    /**
     * Created by itbird on 2022/2/25
     */
    public interface IView {
    }
    

    IView接口只是定义视图,不再需要内部通过接口,绑定presenter,因为我们知道presenter就是在view中初始化的
    BaseActivity

    package com.itbird.mvp.my;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    import androidx.annotation.Nullable;
    
    /**
     * Activity基类
     * Created by itbird on 2022/2/25
     */
    public abstract class BaseActivity extends Activity implements IView {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getLayoutID());
            initPresenter();
            initView();
        }
    
        /**
         * 界面布局id
         */
        public abstract int getLayoutID();
    
        /**
         * 开发者只需关注,去在此方法中,实现presenter初始化
         */
        public abstract void initPresenter();
    
        /**
         * 控件初始化
         */
        public abstract void initView();
    }
    

    BaseActivity为提供给开发者的activity基类,是一个抽象类,里面通过重写onCreate方法,实现了setContentView、initPresenter、initView的顺序化,开发者不再需要去关心什么时候去初始化presenter,只需要实现initPresenter抽象方法即可。
    BasePresenter

    package com.itbird.mvp.my;
    
    /**
     * Presenter基类
     * Created by itbird on 2022/2/25
     */
    public abstract class BasePresenter<V extends IView> implements IPresenter {
        V mView;
    
        /**
         * 绑定view,一般在初始化中调用该方法
         */
        @Override
        public void onAttach(IView view) {
            mView = (V) view;
        }
    
        /**
         * 断开view,一般在onDestroy中调用
         */
        @Override
        public void onDetach() {
            mView = null;
        }
    
        /**
         * 是否与View建立连接
         * 每次调用业务请求的时候都要出先调用方法检查是否与View建立连接
         */
        public boolean isViewAttached() {
            return mView != null;
        }
    }
    

    我们看到BasePresenter实现了IPresenter的onAttach、onDetach方法,从而在BasePresenter进行了view与presenter的绑定和解绑动作,同时我们也知道activity如果要继承BaseActivity,那么就是IView的子类,所以BasePresenter中,需要通过泛型指定为子类类型,这样才可以保证BasePresenter中存储的为BaseActivity的子类(即:具体的activity)。
    TaskMyPresenter

    package com.itbird.mvp.my;
    
    public class TaskMyPresenter extends BasePresenter<TaskMyContract.View> implements TaskMyContract.Presenter {
        private static final String TAG = TaskMyPresenter.class.getSimpleName();
    
        @Override
        public void loadDataFromModel() {
            //TODO :loaddata,此处可以用model、或者进行业务操作
            //调用界面方法,进行数据刷新
            if (isViewAttached()) {
                mView.updateTextView("loaddata success!!!");
            }
        }
    }
    

    TaskMyActivity

    package com.itbird.mvp.my;
    
    import com.itbird.R;
    
    public class TaskMyActivity extends BaseActivity implements TaskMyContract.View {
    
        private static final String TAG = TaskMyActivity.class.getSimpleName();
        TaskMyPresenter mPresenter;
    
        @Override
        public int getLayoutID() {
            return R.layout.mvp_view_test;
        }
    
        @Override
        public void initPresenter() {
            mPresenter = new TaskMyPresenter();
            mPresenter.onAttach(this);
        }
    
        @Override
        public void initView() {
    
        }
    
        @Override
        public void updateTextView(String s) {
    
        }
    }
    

    TaskMyContract

    package com.itbird.mvp.my;
    
    /**
     * my Demo
     * Created by itbird on 2022/2/25
     */
    public interface TaskMyContract {
        interface View extends IView {
            //界面UI刷新方法
            void updateTextView(String s);
        }
    
        interface Presenter extends IPresenter {
            void loadDataFromModel();
        }
    }
    
    

    相关文章

      网友评论

          本文标题:Google MVP Demo深入学习和总结

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