美文网首页Android开发知识
Android的MVP架构实践

Android的MVP架构实践

作者: Crazyerick | 来源:发表于2018-12-06 11:49 被阅读0次

    最近研读了许多关于Android架构的分享资料,同时查阅了Android官方MVP架构案例的源码,获益良多,在这以一个简单的Demo谈谈我对MVP架构理解,以及和传统MVC架构的对比。

    一、需求

    • 在一个Activity界面中有一个Button和一个TextView,用户点击Button后,模拟从服务器请求数据,并将数据更新到TextView中显示(本案例非常简单,简单到让人不想往下看,但道理是相通的,该例子已足以说明问题)
    • 界面大致长这样:


      com.erick.architecturedemo.jpg

    二、方案

    • Android中常见的架构有很多:MVC、MVP、MVVM、Clean、AAC等等,但目前最实用的还属MVP,因此本文重点对比MVC与MVP的实现方式及其优缺点
    MVC
    • MVC全称:Model-View-Controller

      • Model层:对应的是数据源以及数据结构等相关对象,一般数据的来源有本地数据库和远程服务器,主要负责网络请求,数据库处理,I/O操作
      • View层:对应的是xml布局文件和Java代码动态view部分,主要负责数据的展示与接收用户操作
      • Controller层:对应的是Activity/Fragment,Activity本来主要是作为初始化页面,展示数据的操作,但是因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多
    • MVC架构图如下:


      MVC架构图
    • 工作原理:当用户触发UI事件的时候,View层会发送请求到Controller层,接着Controller层去通知Model层处理数据,Model层处理完数据后直接显示在View层上

    • 使用MVC实现上述需求,具体代码如下:

    //Controller层+View层
    public class MVCActivity extends AppCompatActivity {
        private Button mMvcBtn;
        private TextView mMvcTv;
        private MVCModel model;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_mvc);
    
            mMvcBtn = findViewById(R.id.btn_mvc);
            mMvcTv = findViewById(R.id.tv_mvc);
            model = new MVCModel();
            mMvcBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // OnClickListener是属于view的,这里View直接访问了model
                    model.getData(new Callback1<String>() {
                        @Override
                        public void onCallback(String s) {
                            updateText(s);
                        }
                    });
                }
            });
        }
    
        //该方法属于Controller
        private void updateText(final String text) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mMvcTv.setText(text);
                }
            });
        }
    }
    
    
    //Model层
    public class MVCModel implements BaseModel {
        public void getData(final Callback1<String> callback1) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        //模拟网络请求
                        String msg = "来自网络的数据: " + new Random().nextInt(100);
                        callback1.onCallback(msg);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    
    • 代码很简单,当点击Button的时候,调用View的onClick方法,该方法直接调用Model的getData方法来请求数据,当请求到数据之后,回调Activity的updateText方法,最终调用View的setText方法更新TextView的展示,由于使用了回调,一定程度上降低了View层和Model层的耦合。这里并没有完全按照上诉架构图实现,原因是这里的Activity既充当Controller层又是View层,无法明显区分,后续Activity会演变得很臃肿,代码量会很庞大

    • 整个过程的流程图如下:


      MVC架构流程图.png
    • 从上面的流程图可以看出,View是可以直接访问Model的,从而View中会包含Model信息,不可避免的还要包括一些业务逻辑,因此Model和View之间是耦合的。同时Controller往往不仅要处理ui的呈现与事件的响应,而且还要负责和model的通信,三者之间强强关联,造成代码耦合

    • 总结下MVC架构的优缺点:

      1. Android开发中默认使用的框架,简单通用,易于上手,开发效率快
      2. 具有一定分层,model彻底解耦,controller和view并没有解耦,逻辑比较混乱,导致Activity 或Fragment很臃肿
      3. 开发难以维护,没有中间件接口做缓冲,难以替换底层的实现
      4. 各层之间耦合较高,可测试性较差,不利于分层测试的开展
    MVP
    • MVP 全称:Model-View-Presenter

      • Model层:对应的是数据源以及数据结构等相关对象,一般数据的来源有本地数据库和远程服务器,主要负责网络请求,数据库处理,I/O操作
      • View层:对应的不只是layout中的xml文件,还包括了Activity/Fragment作为视图的显示,这里的Activity专心只做视图相关的操作,比如UI布局,数据渲染,点击按钮交互等
      • Presenter层:对应的是控制器等相关对象,负责View层和Model层之间的通信,控制业务逻辑的调用
    • MVP架构图如下:


      MVP架构图
    • 工作原理:当出现View事件的响应或者生命周期变化时,首先去找Presenter,然后Presenter去找Model请求数据,Model获取到数据之后通知Presenter,Presenter再通知View更新数据展示

    • 在view和presenter两者之间的通信并不是想怎么调用就可以怎么调用的,他们之间有着一个标准的协议,就是在两者之间定义通用接口IContract,在这个接口中定义了view层中要暴露的接口,也定义了presenter层中需要暴露给view的接口,利用接口的方式将两者进行隔离,达到面向接口编程的目的

    • 使用MVP实现上述需求,具体代码如下:

    1. View和Presenter之间的接口契约类:MVPContract.java
    public interface MVPContract {
    
        interface IMVPModel extends BaseModel {
            void getData(Callback1<String> callback);
        }
    
        interface IMVPView extends BaseView<IMVPPresenter> {
            void updateText(String text);
            void showToast(String msg);
        }
    
        interface IMVPPresenter extends BasePresenter<IMVPView> {
            void request();
        }
    }
    
    • 契约类用于定义同一个界面的View接口和Presenter的具体实现,这样做的主要好处有:
      1. 通过规范的方法命名和注释可以清晰的看到整个页面的逻辑,提高代码的可读性
      2. 便于单元测试,通过Mock对象实现契约接口,可分别传入View和Presenter中进行测试
      3. 低耦合使得变更逻辑更容易,同时可以复用View或Presenter对象
    1. View层:MVPActivity.java
    public class MVPActivity extends BaseActivity<MVPContract.IMVPView, MVPContract.IMVPPresenter> implements MVPContract.IMVPView {
        private Button mMvpBtn;
        private TextView mMvpTv;
    
        @Override
        protected void initUI(Bundle savedInstanceState) {
            setContentView(R.layout.activity_mvp);
            mMvpBtn = findViewById(R.id.btn_mvp);
            mMvpTv = findViewById(R.id.tv_mvp);
            mMvpBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mPresenter != null) {
                        //这里将用户请求转发给Presenter
                        mPresenter.request();
                    }
                }
            });
        }
        ...
    
        @Override
        protected MVPContract.IMVPPresenter createPresenter() {
            return new MVPPresenter();
        }
    
        @Override
        public void setPresenter(MVPContract.IMVPPresenter presenter) {
            mPresenter = presenter;
        }
    
        @Override
        public void updateText(final String text) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mMvpTv.setText(text);
                }
            });
        }
    
        @Override
        public void showToast(String msg) {
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        }
    }
    
    • 这里Activity只处理UI逻辑,代码变得更加简洁,同时在父类BaseActivity中管理生命周期任务
    public abstract class BaseActivity<V extends BaseView, P extends BasePresenter<V>> extends AppCompatActivity {
        protected P mPresenter;
    
        @SuppressWarnings("unchecked")
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mPresenter = createPresenter();
            mPresenter.attachView((V) this);
            initUI(savedInstanceState);
            initParams();
            initData();
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            if (mPresenter != null) {
                mPresenter.onSaveInstanceState(outState);
            }
        }
    
        @Override
        public void onResume() {
            super.onResume();
            if (mPresenter != null) {
                mPresenter.onStart();
            }
        }
    
        @Override
        protected void onDestroy() {
            if (mPresenter != null) {
                mPresenter.onDestroy();
            }
            super.onDestroy();
        }
    
        protected abstract P createPresenter();
        protected abstract void initUI(Bundle savedInstanceState);
        protected abstract void initParams();
        protected abstract void initData();
    }
    
    1. Presenter层:MVPPresenter.java
    public class MVPPresenter extends AbstractPresenter<MVPContract.IMVPView, MVPContract.IMVPModel> implements MVPContract.IMVPPresenter {
    
        @Override
        protected MVPContract.IMVPModel createModel() {
            return new MVPModel();
        }
    
        @Override
        public void request() {
            if (model != null) {
                //这里请求Model层拉取数据
                model.getData(new Callback1<String>() {
                    @Override
                    public void onCallback(String s) {
                        if (isViewAttached()) {
                            getView().updateText(s);
                        }
                    }
                });
            }
        }
    }
    
    public interface BasePresenter<V extends BaseView> {
        void onStart();
        void onDestroy();
        void onSaveInstanceState(Bundle outState);
        void attachView(V view);
        void detachView();
    }
    
    • BasePresenter中定义了通用的接口方法,例如onStart()、attachView()等方法,同时为了代码复用,提供了抽象实现AbstractPresenter.java
    public abstract class AbstractPresenter<V extends BaseView, M extends BaseModel> implements BasePresenter<V> {
        protected WeakReference<V> viewRef;
        protected M model;
    
        public AbstractPresenter() {
            model = createModel();
        }
    
        @Override
        public void onStart() {
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
        }
    
        @Override
        public void onDestroy() {
            detachView();
            model.onDestroy();
        }
    
        @Override
        public void attachView(V v) {
            viewRef = new WeakReference<V>(v);
            viewRef.get().setPresenter(this);
        }
    
        @Override
        public void detachView() {
            if (viewRef != null) {
                viewRef.clear();
                viewRef = null;
            }
        }
    
        public void setModel(M m) {
            model = m;
        }
    
        protected V getView() {
            return viewRef == null ? null : viewRef.get();
        }
    
        protected boolean isViewAttached() {
            return viewRef != null && viewRef.get() != null;
        }
    
        protected abstract M createModel();
    }
    
    1. Model层:MVPModel.java
    //这里的Model层实现和MVC中的没有任何差别
    public class MVPModel implements MVPContract.IMVPModel {
        @Override
        public void getData(final Callback1<String> callback) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        //模拟从网络拉取数据
                        String msg = "来自网络的数据: " + new Random().nextInt(100);
                        callback.onCallback(msg);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    
    • 从代码上看可以发现比起传统的MVC,从代码数量上看似乎并没有减少反而增加了不少的代码和接口,但是逻辑调用更加清晰,各层之间的耦合度更低

    • 在MVPActivity中实现了MVPContract.IMVPView接口,并实现了updateText()和showToast()两个方法,这两个方法都属于UI逻辑。同时在父类的onCreate()的时候创建了一个presenter对象,并将自身绑定到presenter对象中。在onResume()的时候调用了presenter.onStart()方法,然后在onDestroy()的时候调用了presenter.onDestroy()方法。而当onClick事件响应的时候也只是转发请求presenter.request()方法,其余业务逻辑一概不理

    • 在MVPPresenter中实现了MVPContract.IMVPPresenter接口并实现了request()方法,通过从父类继承的attachView()方法绑定View,同时将自身传给View,实现两者之间的绑定。当Model获取数据后回调Presenter后,Presenter将调用了view的updateText方法,以此来对View视图的UI呈现以及交互做出相应的响应。而最后的onDestroy()方法则是用与释放对View的循环引用资源的,避免内存泄漏

    • MVP架构流程图如下:


      MVP架构流程图.png
    • 从上面流程图中可以看出,MVP中去除了View和Model之间的调用关系,从而彻底的分离了Model和View之间的关联与耦合,这样Model和View就不会直接交互了,所有的交互都由Presenter进行,Presenter充当了桥梁的角色,业务调用逻辑更加清晰明确

    • 总结下MVP架构的优缺点:

      1. 符合高内聚低耦合软件设计要求,并且尽可能的保证了开闭原则
      2. Model与View完全分离,便于并行开发,大大降低维护和交接成本
      3. Presener和View均可复用,一个Presener可以用于多个View,而不需要改变Presenter的逻辑,并且View可以进行组件化,提供调用接口即可
      4. 代码结构清晰,通过接口隔离,代码可测试性高,便于开展分层测试
      5. 对于很小的Demo来说构建复杂和麻烦,不适合短期、小型且以后不在做任何维护的模块开发
      6. 由于将所有业务逻辑都放在Presener层,Presener可能会变得笨重

    三、对比

    • 下面从几个方面对比下MVC与MVP,可以看出MVP基本完胜
    架构 开发速度 难度 低耦合 流行度 可测性 代码复用
    MVC 5星 1星 2星 1星 2星 1星
    MVP 4星 3星 4星 4星 4星 4星

    四、总结

    • 架构选型没有孰优孰劣,需要结合项目状况选择最合适的,下面是一般性经验:
      1. 不要为了使用设计模式或架构方法而使用
      2. 如果项目简单,没什么复杂性,未来改动也不大的话,建议使用MVC
      3. 对于工具类或者业务逻辑较重的产品级app,建议使用MVP

    相关文章

      网友评论

        本文标题:Android的MVP架构实践

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