MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读

来源:互联网 发布:vb soapclient 编辑:程序博客网 时间:2024/06/05 09:18

介绍

MVP这个架构一直是Android开发社区讨论的焦点,每个人都有自己的分析理解众说纷纭。直到GitHub上Google官方发布用MVP架构搭建的项目。感觉是时候分析了。

MVP架构简介


MVP架构简介

  对于一个应用而言我们需要对它抽象出各个层面,而在MVP架构中它将UI界面和数据进行隔离,所以我们的应用也就分为三个层次。

  • View:对于View层也是视图层,在View层中只负责对数据的展示,提供友好的界面与用户进行交互。在Android开发中通常将Activity或者Fragment作为View层。
  • Model:对于Model层也是数据层。它区别于MVC架构中的Model,在这里不仅仅只是数据模型。在MVP架构中Model它负责对数据的存取操作,例如对数据库的读写,网络的数据的请求等。
  • Presenter:对于Presenter层他是连接View层与Model层的桥梁并对业务逻辑进行处理。在MVP架构中Model与View无法直接进行交互。所以在Presenter层它会从Model层获得所需要的数据,进行一些适当的处理后交由View层进行显示。这样通过Presenter将View与Model进行隔离,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离。更好的使得单元测试得以实现。

下图很好的展示了MVP各个组件间的关系。 
MVP架构

从图中可以看出,View层不再和Model层关联,他们之间通过Presenter层关联,这里就出明显的感觉出P层的任务会比较重,逻辑会相对其他层复杂,同时也是MVP中最关键的层。

  在MVP架构中将这三层分别抽象到各自的接口当中。通过接口将层次之间进行隔离,而Presenter对View和Model的相互依赖也是依赖于各自的接口。这点符合了接口隔离原则,也正是面向接口编程。在Presenter层中包含了一个View接口,并且依赖于Model接口,从而将Model层与View层联系在一起。而对于View层会持有一个Presenter成员变量并且只保留对Presenter接口的调用,具体业务逻辑全部交由Presenter接口实现类中处理。

面向接口编程:每个层次不是直接向其上层提供服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,而不依赖具体类。

代码分析

项目说明

本文主要分析: 
基础MVP架构todo-mvp和响应式MVP架构todo-mvp-rxjava

目前Google在GitHub上面公布7个项目: 
7个项目
每个项目都是便签App,都采用MVP架构但是每个项目都会有些不同。目前网络上大多数都是分析第一个todo-mvp,作为其他项目的基础,对比分析todo-mvp-rxjava找出两者的差异和相同点,是本文的主要内容。 
为了简洁,约定有:

mvp:指代todo-mvp项目 
mvp-rxjava:指代todo-mvp-rxjava项目 
响应式MVP:作为mvp-rxjava的中文描述

基础类分析

本章主要分析mvp和mvp-rxjava两个项目基础类的差异,分析同样的功能MVP和响应式MVP的实现的差异和提取出相同点。

基础类BaseView

BaseView作为所有的View层的父类,功能是实现P层的依赖注入。mvp和mvp-rxjava都采用一样逻辑。 
代码如下:

[代码]java代码:

?
1
2
3
publicinterface BaseView<t> {
    voidsetPresenter(T presenter);
}</t>

View层的具体实现类xxFragment实现接口,就能够得到和它关联的P层的注入。

[代码]java代码:

?
1
2
3
4
5
6
7
    //内部变量 从setPresenter方法注入
    privateAddEditTaskContract.Presenter mPresenter;
@Override
 
    publicvoid setPresenter(@NonNullAddEditTaskContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }

而注入的时机肯定就是在P层已经得到实例化之后,所以我们在对应的P层构造方法中可以看到这样的代码:

[代码]java代码:

?
1
2
3
4
5
6
7
8
publicAddEditTaskPresenter(@NullableString taskId, @NonNullTasksDataSource tasksRepository,
            @NonNullAddEditTaskContract.View addTaskView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository);
        mAddTaskView = checkNotNull(addTaskView);
        //向V层注入自己 自己就是对应的P层实例
        mAddTaskView.setPresenter(this);
    }

上面这3段代码,给我感觉是这样的。 
互相注入

基础类BasePresenter

BasePresenter作为所有P层的父类,主要实现V层和P层生命周期同步。 
这里响应式MVP明显的和MVP不同, 
MVP的P层父类代码:

[代码]java代码:

?
1
2
3
publicinterface BasePresenter {
    voidstart();
}

在View层的具体实现类xxFragment的生命周期onResume中启动通过注入得到的P层实例开始P层的工作。

[代码]java代码:

?
1
2
3
4
5
@Override
    publicvoid onResume() {
        super.onResume();
        mPresenter.start();
    }

响应式MVP的P层父类:

[代码]java代码:

?
1
2
3
4
publicinterface BasePresenter {
    voidsubscribe();//开启订阅
    voidunsubscribe();//结束订阅
}

在View层的具体实现类xxFragment中就需要做两步操作,同步P层和V层的生命周期

[代码]java代码:

?
01
02
03
04
05
06
07
08
09
10
11
@Override
    publicvoid onResume() {
        super.onResume();
        mPresenter.subscribe();//V层获得焦点 开始订阅
    }
 
    @Override
    publicvoid onPause() {
        super.onPause();
        mPresenter.unsubscribe();//V层失去焦点 取消订阅
    }

这么写的原因是,RxJava的特点决定的。

响应式编码中数据Model是可以观察到的数据流,已经准备好数据,随时等待发射。

我们需要做的就是,在需要数据的点开始订阅数据,接收数据。不再需要数据就取消订阅数据,让数据不再发送。这在使用RxJava是很重要的操作。

一般使用RxJava的MVC架构项目中,如果使用Fragment做为数据的主要展示类,就直接定义内部变量CompositeSubscription对象订阅者集合,在onDestroy生命周期回调中统一操作,取消正在等待的订阅,因为当前View已经不可见了。

代码是这样的:

[代码]java代码:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//父类统一提供管理方法
publicabstract class BaseFragment extendsFragment {
         privateCompositeSubscription mCompositeSubscription; //这个类的内部是由Set<subscription> 维护订阅者
 
    //提供给子类的方法
    publicvoid addSubscription(Subscription s) {
        if(this.mCompositeSubscription == null) {
            this.mCompositeSubscription = newCompositeSubscription();
        }
 
        this.mCompositeSubscription.add(s);
    }
 
     @Override
    publicvoid onDestroy() {
        super.onDestroy();
          //在销毁时统一取消
        if(this.mCompositeSubscription != null) {
            this.mCompositeSubscription.unsubscribe();
        }
 
    }
 
}</subscription>

而在响应式MVP架构中P层作为控制逻辑的主要实现,就需要和V层的生命周期同步,把这段代码搬到P层中。

在我的另一篇博文RxAndroid和Retrofit使用记录-有关网络调用和生命周期中有具体代码和分析。

Contract契约类

不同于其他的MVP项目,官方的MVP架构中都定义有xxContract契约类,把P层和V层的接口统一写在契约类中,能够更清晰的看到在Presenter层和View层中有哪些功能,方便我们以后的维护。这是其他MVP架构没有的类。mvp和mvp-rxjava都采用一样逻辑。

每个契约类都定义了P层的数据操作方法和V层控制UI的方法, 
并能够通过参数传入需要的值。 
每个模块的契约类都是需要我们根据具体的需求进行抽象,定义方法和参数的。 
下面的代码是,添加任务模块的契约类,通过方法名可以大概了解V层和P层需要具体是实现的逻辑功能。

[代码]java代码:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
publicinterface AddEditTaskContract {
 
    interfaceView extendsBaseView<presenter> {
 
        voidshowEmptyTaskError();
 
        voidshowTasksList();
 
        voidsetTitle(String title);
 
        voidsetDescription(String description);
 
        booleanisActive();
    }
 
    interfacePresenter extendsBasePresenter {
 
        voidcreateTask(String title, String description);
 
        voidupdateTask( String title, String description);
 
        voidpopulateTask();
    }
}</presenter>

Activity绑定类

在官方的MVP架构中Activity类不再负责任何的View层功能。mvp和mvp-rxjava都采用一样逻辑。

  • 普通的View控件都包含在V层的Fragment中。
  • 甚至在布局文件中和Fragment同级的FloatingActionButton控件也由Fragment控制
  • 同样布局文件中和Fragment同级的Menu菜单视图,也由Fragment控制。

这样使得Fragment才变成真正的View层。而使得Activity符合面向对象设计原则的SRP(单一职责原创,Single Responsibility Principle)。

而Activity最重要的功能就是P层对M/V层的绑定。

[代码]java代码:

?
1
2
3
4
5
6
7
// Create the presenter
       //P层的构造 依赖注入
       newTaskDetailPresenter(
               taskId,//P层需要的关键数据 任务id
               Injection.provideTasksRepository(getApplicationContext()),//Model层的注入
               taskDetailFragment//View层
       );

看到上面的代码,感觉下图非常符合 
MV层注入P层

View层

说了这么多终于到MVP的View层了,官方MVP架构中Fragment作为View层实现类。 
分层之后Fragment的代码就简洁多了。 
implements实现相关接口方法,做视图操作,分发给P层做处理。得到P层回调展示数据。 
下面的代码,作为示例,它实现同级视图控制。

上文提到: 甚至在布局文件中和Fragment同级的FloatingActionButton控件也由Fragment控制

<code class="hljs java has-numbering" style="word-wrap: normal; display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; background: transparent;"><br style="word-wrap: break-word;" /></code><ul class="pre-numbering" style="word-wrap: break-word; margin: 0px; padding: 6px 0px 40px; box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">1</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">2</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">3</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">4</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">5</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">6</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">7</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">8</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">9</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">10</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">11</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">12</li></ul><ul class="pre-numbering" style="word-wrap: break-word; margin: 0px; padding: 6px 0px 40px; box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">1</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">2</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">3</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">4</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">5</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">6</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">7</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">8</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">9</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">10</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">11</li><li style="word-wrap: break-word; margin: 0px; padding: 0px 5px; list-style: disc; box-sizing: border-box;">12</li></ul>

[代码]java代码:

?
01
02
03
04
05
06
07
08
09
10
11
12
//得到和自己同级的View
// Set up floating action button
        FloatingActionButton fab =
                (FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task);
 
        //响应点击事件 分发给P层
        fab.setOnClickListener(newView.OnClickListener() {
            @Override
            publicvoid onClick(View v) {
                mPresenter.editTask();
            }
        });


同样Menu也重写了onCreateOptionsMenu()方法和onOptionsItemSelected()方法控制菜单视图。

Presenter层

每个包中的xxPresenter类是某个具体的P层控制类。因为Model数据层有负责了数据的读取功能,P层的代码逻辑也很简单。 
从M层得到数据,做逻辑判断分发给V层。或者是向M层发送某个读写操作,回调操作是否成功的结果。 
而它的数据来源Model层,就是刚才Activity类里面的依赖注入进来的。

[代码]java代码:

?
1
2
//数据层 通过构造方法得到的依赖注入
privatefinal TasksRepository mTasksRepository;

值得一提的是,响应式MVP和MVP在获取数据方面会有不同。

MVP的P层获取数据逻辑

在P层和M层的交互中值得注意的有两个问题。

  • 数据层读写操作会发生在子线程,而P层最终需要将数据发送给V层做UI主线程操作。所以要在Model层某个具体的数据发送类做线程处理。P层的回调才能正确的将数据分发给V层显示。
  • 既然使用到多线程,如果使用Handler线程间通信,在M层的操作上如果子线程的某个操作比较耗时很久才返回数据,而V层的Fragment已经退出调用了onDestroy方法,这时如果P层还持有对V层的引用向他发送数据,有可能会导致内存泄露。

    总之MVP项目架构,耗时任务没有处理好就有可能发生异常或者内存泄露。

MVP中P层通过接口回调得到数据,如下代码:

[代码]java代码:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
mTasksRepository.getTask(mTaskId,newTasksDataSource.GetTaskCallback() {
            @Override
            publicvoid onTaskLoaded(Task task) {
                // The view may not be able to handle UI updates anymore
                if(!mTaskDetailView.isActive()) {
                    return;
                }
                mTaskDetailView.setLoadingIndicator(false);
                if(null== task) {
                    mTaskDetailView.showMissingTask();
                }else{
                    showTask(task);
                }
            }

响应式MVP的P层数据获取逻辑

上面的两个问题,在响应式MVP里可以得到很好的解决。 
如果你用过RxJava,下面的代码,相信就不需要我说什么了。可以跳过下面的说明。

[代码]java代码:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
mTaskDetailView.setLoadingIndicator(true);
       Subscription subscription = mTasksRepository
               .getTask(mTaskId)//取出可观察数据 Observable<task>
               .subscribeOn(Schedulers.io())//在IO线程 产生数据
               .observeOn(AndroidSchedulers.mainThread())//在UI线程 分发数据
               .subscribe(newObserver<task>() {
                   @Override
                   publicvoid onCompleted() {
                       mTaskDetailView.setLoadingIndicator(false);
                   }
 
                   @Override
                   publicvoid onError(Throwable e) {
 
                   }
 
                   @Override
                   publicvoid onNext(Task task) {
                       showTask(task);
                   }
               });
       mSubscriptions.add(subscription);//加入集合 才能在及时取消订阅</task></task>

代码说明:

subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。

observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。

所以M层发送过来的数据,只要在P层两行代码搞定线程切换,然后直接订阅就等待数据的到达。同时加入订阅者管理集合在相应的生命周期统一取消。

Model层

感觉上面说了这么多,Model层几乎都说完了。 
数据层负责数据的在本地或者远程读写数据,每个应用的数据结构表示和存储形式都不会有些不同。 
官方的MVP数据使用SQL数据库存储,外部再维护一个懒汉式单例的TasksRepository做数据缓存。 
MVP架构通过接口回调分发数据,响应式MVP通过Observable得到可观察数据。

如果结合Retrofit网络框架,哪响应式MVP的Model层数据来源。就是可以是ServiceGenerator。 
Retrofit的静态构造方法构造网络请求对象,传入接口,代理模式生成出数据,P层就可以直接拿到数据了。 
当然这只是我的初步想法,打算正用于我的个人项目。

图解

通过上面对MVP每一个模块功能的分析,最后上一张图,Android官方MVP整个项目的逻辑图。 
MVP项目图解

需要具体代码可以到GitHub上Clone

总结

  • 通过对比分析清晰了官方MVP的架构逻辑。
  • 每个类都尽量符合面向对象设计原则,采用单一职责原则。整个项目架构清晰分工明确。
  • 水平有限,请大家指正。依赖注入这块我也不是很懂,具体的需要大家去Google。
  • 最后对响应式MVP结合Retrofit提出一点自己的构想。
转自:李可乐
0 0
原创粉丝点击