Android中使用MVP架构相关知识梳理

Android中使用MVP架构相关知识梳理

综述

本文为了记录笔者对MVP架构的认知, 因为当前使用的主要是MVVM, 后面需要考虑尝试MVI或者其他架构, 也可能考虑尝试flutter, 所以将承上启下的MVP相关知识点总结梳理出来, 方便记忆以及后期的回顾.

Android中的MVP架构从MVC架构演变而来, 所以要谈论MVP就不得不先说MVC.
MVC是最传统的Android开发架构, 按照字面理解会分为三层

  • Model层: 负责实现业务逻辑, 一般包括业务逻辑和实体模型
  • View层: 负责展示页面, 一般对应布局文件
  • Controller层: 负责接收输入, 调用业务逻辑并更新页面的显示, 一般对应Activity/Fragment

这个三层的划分实际上并不差, 责任清晰, 包括早期的web开发使用的也是MVC架构.
但MVC开始慢慢被新架构替代, 因为Activity/Fragment不是一个完美的Controller, 它还承担了一部分view的职责. 由于Activity/Fragment既承载视图的显示逻辑,又包含业务逻辑. 这就导致Activity/Fragment如果用来承担Controller的职责的时候会特别的重, 很多MVC结构的项目中, 一个Controller一千多行, 甚至两千多行, 代码耦合度高,同时还难以维护和测试.

于是逐步发展出了MVP架构, 架构同样分了三层

  • Model层: 负责实现业务逻辑, 一般包括业务逻辑和实体模型
  • View层: 负责展示页面, 一般对应布局文件, 以及和显示逻辑强关联的Activity/Fragment
  • Presenter层: 负责存放页面状态, 根据用户的输入调用业务逻辑和显示逻辑

这样, 原本笨重的Activity/Fragment大半职责被拆分到了Presenter层.

概念

一个典型的MVP架构如下图所示

这里用户与View层交互, View通过与Presenter互相持有调用Presenter中实现的调用业务逻辑. Presenter通过持有Model实例并调用对应的方法来实现业务逻辑. 如果业务逻辑有返回数据, 那么数据将会返回到Presenter, 然后数据通过Presenter返回View, 最后被用户所感知.

当然只有图只是为了方便理解关系. 但关系总归要落于实处. 下面给出一个最简单的MVP架构示例以供参考.


//一个Presenter持有Activity的实例, 并在调用构造函数时获取Model类的实例
public class MainPresenter {

    private MainActivity view;
    private Model model;

    public MainPresenter(MainActivity view) {
        this.view = view;
        this.model = new Model();
    }

    //唯一的方法通过mode获取数据
    public void getData() {
        String data = model.getData();
        view.showResult(data);
    }
}

//一个Activity作为view层, 持有Presenter的实例
public class MainActivity extends AppCompatActivity {
    private MainPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        presenter = new MainPresenter(this);
        init();
    }

    private void init() {
        Button btnClick = findViewById(R.id.btn_click);
        btnClick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.getData();
            }
        });
    }

    void showResult(String message) {
        TextView tvResult = findViewById(R.id.tv_result);
        tvResult.setText(message);
    }
}

//一个model提供数据
public class Model {

    String getData() {
        return "通过Model获取的data";
    }
}

//layout略, 只要求一个id为btn_click的button和一个id为tv_result的text.

上面这个例子没有使用接口, 没有抽象类, 没有依赖注入或者Provider工厂, 没有使用回调或者数据总线返回getData的结果, 也没有用线程模拟延时, 各个类之间高度耦合, 而且还有内存泄露的问题.

但MVP和MVC两种架构之间最本质的区别给出来了, 那就是Activity从作为Controller的角色改为View的角色, 只负责接收用户的输入, 并显示结果. 而原本Activity肩负的调用Model并且获取结果后通过页面显示出来的职责被移交给了Presenter.

这里的核心是Activity和Presenter互相持有, 且原本MVC中Controller的职责被一分为二, 负责调度业务逻辑的部分被划分给了Presenter.

从整个操作流程分析, 用户通过View触发OnClickListener()回调, OnClickListener()回调调用被Activity持有的Presenter实例对应的方法, Presenter获取数据后通过Presenter持有的Activity实例调用showResult()方法将获取的数据显示到页面上.

MVP架构的可用实现

前面已经给出了一个mvp架构的最小示例了, 但是一个高度耦合的mvp框架是无法满足实际的使用需求, 所以在真正使用时一般至少需要引入接口, 使得View和Presenter之间互相解耦, 并准备好attachView和detachView两个方法, 方便Presenter和View的生命周期绑定. 这样才算是一个初步可用的mvp架构.

//使用contract即契约类汇总单个页面对应的View和Presenter接口, 方便维护
public interface MainContract {

    interface View {
        void showResult(String message);
    }

    interface Presenter {
        void attachView(View view);

        void detachView();

        void getData();
    }
}

//修改Presenter, 实现attachView和detachView方法, 避免内存泄露
public class MainPresenter implements MainContract.Presenter {

    private MainContract.View view;
    private Model model;

    @Override
    public void attachView(MainContract.View view) {
        this.view = view;
        this.model = new Model();
    }

    @Override
    public void detachView() {
        this.view = null;
        this.model = null;
    }

    //唯一的方法通过mode获取数据
    public void getData() {
        String data = model.getData();
        view.showResult(data);
    }
}

//修改Activity, 在恰当的生命周期调用Presenter的attachView()和detachView()方法
public class MainActivity extends AppCompatActivity implements MainContract.View {
    private MainContract.Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        presenter = new MainPresenter();
        presenter.attachView(this);
        init();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.detachView();
        presenter = null; //可以不写
    }

    private void init() {
        Button btnClick = findViewById(R.id.btn_click);
        btnClick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.getData();
            }
        });
    }

    @Override
    public void showResult(String message) {
        TextView tvResult = findViewById(R.id.tv_result);
        tvResult.setText(message);
    }
}

//model部分没有变化
public class Model {

    String getData() {
        return "通过Model获取的data";
    }
}
//同样, layout略

以上示例相较于一开始用于理解的版本初步解决了两个问题, 一个是内存泄露, 一个是Presenter和View的解耦. 属于可用范畴, 当然正常使用仍然应该加上依赖注入或者使用Provider进行手动注入来进一步解耦.

进阶版本的MVP架构

在上述版本中MVP架构仍然存在一些问题, 比如如果有多个页面每个页面都需要调用attachView()和detachView()方法, 这么做很麻烦而且也担心遗漏所导致的内存泄露, 所以如果要进一步拓展, 可以考虑引入基类和WeakReference 并且使用依赖注入框架或手动注入来尽可能解耦合. 参考下面这个拓展写法.

//首先是两个以base命名的基类
public abstract class BaseActivity<V, P extends BasePresenter<V>> extends AppCompatActivity{
    protected P presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = getPresenter();
        presenter.attachView(getCurrentView());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.detachView();
    }

    protected abstract P getPresenter();
    protected abstract V getCurrentView();
}

public abstract class BasePresenter<V> {
     private Reference<V> mViewRef;

    public void attachView(V view) {
        mViewRef = new WeakReference<V>(view);
        this.onAttach();
    }

    public void detachView() {
        mViewRef.clear();
        mViewRef = null;
        this.onDetach();
    }

    protected boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    protected V getView() {
        return mViewRef != null ? mViewRef.get() : null;
    }

    protected abstract void onAttach();
    protected abstract void onDetach();
}

//一般来说应该选择一个依赖注入框架比如ButterKnife, Dagger等, 但是那就超出本文想要讨论的范畴了, 所以这里给出手动注入的做法
public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        initModuleProvider();
    }

    void initModuleProvider(){
        ModuleProvider.getInstance();
    }
}

public class ModuleProvider {

    private static volatile ModuleProvider instance;
    private static final Object mSync = new Object();

    private ModuleProvider(){}

    public static ModuleProvider getInstance(){
        if (instance == null){
            synchronized (mSync){
                if (instance == null){
                    instance = new ModuleProvider();
                }
            }
        }
        return instance;
    }

    private final Model model = new Model();

    private Reference<MainPresenter> mainPresenterRef;

    public Model getModel(){
        return model;
    }

    public MainPresenter getMainPresenter() {
        if (mainPresenterRef == null || mainPresenterRef.get() == null){
            mainPresenterRef = new WeakReference<>(new MainPresenter());
        }
        return mainPresenterRef.get();
    }
}

//然后是一直没有改变的model类
public class Model {

    public String getData() {
        return "通过Model获取的data";
    }
}

//最后是正常的Contract, 以及View和Presenter
public interface MainContract {

    interface View {
        void showResult(String message);
    }

    interface Presenter{
        void getData();
    }
}


public class MainActivity extends BaseActivity<MainContract.View, MainPresenter> implements MainContract.View {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected MainPresenter getPresenter() {
        return ModuleProvider.getInstance().getMainPresenter();
    }

    @Override
    protected MainContract.View getCurrentView() {
        return this;
    }

    private void init() {
        Button btnClick = findViewById(R.id.btn_click);
        btnClick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.getData();
            }
        });
    }

    @Override
    public void showResult(String message) {
        TextView tvResult = findViewById(R.id.tv_result);
        tvResult.setText(message);
    }
}

public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter{
    private Model model;

    @Override
    protected void onAttach() {
        model = ModuleProvider.getInstance().getModel();
    }

    @Override
    protected void onDetach() {
        model = null;
    }

    //唯一的方法通过mode获取数据
    public void getData() {
        if (!isViewAttached()){
            return;
        }
        String data = model.getData();
        getView().showResult(data);
    }
}

以上就是一个比较完整的MVP架构, View使用Fragment也是类似的做法. 不过为了控制大小尽量避免引入三方依赖, 作为Model层示例的Model类也只是给出了一个样子货.
实际使用时可以拓展为Repository层架构或者直接Clean Architect架构, 两者结合就称得上是一个完整的android项目结构了.

posted @ 2024-07-03 18:27  地维藏光  阅读(107)  评论(0)    收藏  举报