Android的MVP架构实践

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

一、需求

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


    Android的MVP架构实践_第1张图片
    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架构图如下:


    Android的MVP架构实践_第2张图片
    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() {
                    @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 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会演变得很臃肿,代码量会很庞大

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


    Android的MVP架构实践_第3张图片
    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架构图如下:


    Android的MVP架构实践_第4张图片
    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 callback);
    }

    interface IMVPView extends BaseView {
        void updateText(String text);
        void showToast(String msg);
    }

    interface IMVPPresenter extends BasePresenter {
        void request();
    }
}
  • 契约类用于定义同一个界面的View接口和Presenter的具体实现,这样做的主要好处有:
    1. 通过规范的方法命名和注释可以清晰的看到整个页面的逻辑,提高代码的可读性
    2. 便于单元测试,通过Mock对象实现契约接口,可分别传入View和Presenter中进行测试
    3. 低耦合使得变更逻辑更容易,同时可以复用View或Presenter对象
  1. View层:MVPActivity.java
public class MVPActivity extends BaseActivity 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> 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 implements MVPContract.IMVPPresenter {

    @Override
    protected MVPContract.IMVPModel createModel() {
        return new MVPModel();
    }

    @Override
    public void request() {
        if (model != null) {
            //这里请求Model层拉取数据
            model.getData(new Callback1() {
                @Override
                public void onCallback(String s) {
                    if (isViewAttached()) {
                        getView().updateText(s);
                    }
                }
            });
        }
    }
}

public interface BasePresenter {
    void onStart();
    void onDestroy();
    void onSaveInstanceState(Bundle outState);
    void attachView(V view);
    void detachView();
}
  • BasePresenter中定义了通用的接口方法,例如onStart()、attachView()等方法,同时为了代码复用,提供了抽象实现AbstractPresenter.java
public abstract class AbstractPresenter implements BasePresenter {
    protected WeakReference 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);
        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 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架构流程图如下:


    Android的MVP架构实践_第5张图片
    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架构实践)