本文共 10018 字,大约阅读时间需要 33 分钟。
好的展示了MVP各个组件间的关系。
从图中可以看出,View层不再和Model层关联,他们之间通过Presenter层关联,这里就出明显的感觉出P层的任务会比较重,逻辑会相对其他层复杂,同时也是MVP中最关键的层。
在MVP架构中将这三层分别抽象到各自的接口当中。通过接口将层次之间进行隔离,而Presenter对View和Model的相互依赖也是依赖于各自的接口。这点符合了接口隔离原则,也正是面向接口编程。在Presenter层中包含了一个View接口,并且依赖于Model接口,从而将Model层与View层联系在一起。而对于View层会持有一个Presenter成员变量并且只保留对Presenter接口的调用,具体业务逻辑全部交由Presenter接口实现类中处理。
面向接口编程:每个层次不是直接向其上层提供服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,而不依赖具体类。
代码分析
项目说明
本文主要分析:
基础MVP架构和响应式MVP架构
目前Google在GitHub上面公布7个项目:
每个项目都是便签App,都采用MVP架构但是每个项目都会有些不同。目前网络上大多数都是分析第一个,作为其他项目的基础,对比分析找出两者的差异和相同点,是本文的主要内容。
为了简洁,约定有:
mvp:指代todo-mvp项目
mvp-rxjava:指代todo-mvp-rxjava项目
响应式MVP:作为mvp-rxjava的中文描述
基础类分析
本章主要分析mvp和mvp-rxjava两个项目基础类的差异,分析同样的功能MVP和响应式MVP的实现的差异和提取出相同点。
基础类BaseView
BaseView作为所有的View层的父类,功能是实现P层的依赖注入。mvp和mvp-rxjava都采用一样逻辑。
代码如下:
public interface BaseView { void setPresenter(T presenter);}
View层的具体实现类xxFragment实现接口,就能够得到和它关联的P层的注入。
private AddEditTaskContract.Presenter mPresenter;@Override public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) { mPresenter = checkNotNull(presenter); }
而注入的时机肯定就是在P层已经得到实例化之后,所以我们在对应的P层构造方法中可以看到这样的代码:
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository, @NonNull AddEditTaskContract.View addTaskView) { mTaskId = taskId; mTasksRepository = checkNotNull(tasksRepository); mAddTaskView = checkNotNull(addTaskView); mAddTaskView.setPresenter(this); }
上面这3段代码,给我感觉是这样的。
基础类BasePresenter
BasePresenter作为所有P层的父类,主要实现V层和P层生命周期同步。
这里响应式MVP明显的和MVP不同,
MVP的P层父类代码:
public interface BasePresenter { void start();}
在View层的具体实现类xxFragment的生命周期onResume中启动通过注入得到的P层实例开始P层的工作。
@Override public void onResume() { super.onResume(); mPresenter.start(); }
响应式MVP的P层父类:
public interface BasePresenter { void subscribe(); void unsubscribe();}
在View层的具体实现类xxFragment中就需要做两步操作,同步P层和V层的生命周期
@Override public void onResume() { super.onResume(); mPresenter.subscribe(); } @Override public void onPause() { super.onPause(); mPresenter.unsubscribe(); }
这么写的原因是,RxJava的特点决定的。
响应式编码中数据Model是可以观察到的数据流,已经准备好数据,随时等待发射。
我们需要做的就是,在需要数据的点开始订阅数据,接收数据。不再需要数据就取消订阅数据,让数据不再发送。这在使用RxJava是很重要的操作。
一般使用RxJava的MVC架构项目中,如果使用Fragment做为数据的主要展示类,就直接定义内部变量CompositeSubscription对象订阅者集合,在onDestroy生命周期回调中统一操作,取消正在等待的订阅,因为当前View已经不可见了。
代码是这样的:
public abstract class BaseFragment extends Fragment { private CompositeSubscription mCompositeSubscription; public void addSubscription(Subscription s) { if (this.mCompositeSubscription == null) { this.mCompositeSubscription = new CompositeSubscription(); } this.mCompositeSubscription.add(s); } @Override public void onDestroy() { super.onDestroy(); if (this.mCompositeSubscription != null) { this.mCompositeSubscription.unsubscribe(); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
而在响应式MVP架构中P层作为控制逻辑的主要实现,就需要和V层的生命周期同步,把这段代码搬到P层中。
在我的另一篇博文中有具体代码和分析。
Contract契约类
不同于其他的MVP项目,官方的MVP架构中都定义有xxContract契约类,把P层和V层的接口统一写在契约类中,能够更清晰的看到在Presenter层和View层中有哪些功能,方便我们以后的维护。这是其他MVP架构没有的类。mvp和mvp-rxjava都采用一样逻辑。
每个契约类都定义了P层的数据操作方法和V层控制UI的方法,
并能够通过参数传入需要的值。
每个模块的契约类都是需要我们根据具体的需求进行抽象,定义方法和参数的。
下面的代码是,添加任务模块的契约类,通过方法名可以大概了解V层和P层需要具体是实现的逻辑功能。
public interface AddEditTaskContract { interface View extends BaseView<Presenter> { void showEmptyTaskError(); void showTasksList(); void setTitle(String title); void setDescription(String description); boolean isActive(); } interface Presenter extends BasePresenter { void createTask(String title, String description); void updateTask( String title, String description); void populateTask(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
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层的绑定。
new TaskDetailPresenter( taskId, Injection.provideTasksRepository(getApplicationContext()), taskDetailFragment );
看到上面的代码,感觉下图非常符合
View层
说了这么多终于到MVP的View层了,官方MVP架构中Fragment作为View层实现类。
分层之后Fragment的代码就简洁多了。
implements实现相关接口方法,做视图操作,分发给P层做处理。得到P层回调展示数据。
下面的代码,作为示例,它实现同级视图控制。
上文提到: 甚至在布局文件中和Fragment同级的FloatingActionButton
控件也由Fragment控制
FloatingActionButton fab = (FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mPresenter.editTask(); } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
同样Menu也重写了onCreateOptionsMenu()
方法和onOptionsItemSelected()
方法控制菜单视图。
Presenter层
每个包中的xxPresenter类是某个具体的P层控制类。因为Model数据层有负责了数据的读取功能,P层代码量会减少一些,但是逻辑不会简单,因为它负责M/V两层的通信。
比如从M层得到数据,做逻辑判断分发给V层。或者是响应V层某个操作逻辑判断之后,发送给M层作读写操作,最后回调操作是否成功的结果。
而它的数据来源Model层,就是刚才Activity类里面的依赖注入进来的。
private final TasksRepository mTasksRepository;
值得一提的是,响应式MVP和MVP在获取数据方面会有不同。
MVP的P层获取数据逻辑
在P层和M层的交互中值得注意的有两个问题。
MVP中P层通过接口回调得到数据,如下代码:
mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() { @Override public void onTaskLoaded(Task task) { if (!mTaskDetailView.isActive()) { return; } mTaskDetailView.setLoadingIndicator(false); if (null == task) { mTaskDetailView.showMissingTask(); } else { showTask(task); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
响应式MVP的P层数据获取逻辑
上面的两个问题,在响应式MVP里可以得到很好的解决。
如果你用过RxJava,下面的代码,相信就不需要我说什么了。可以跳过下面的说明。
mTaskDetailView.setLoadingIndicator(true); Subscription subscription = mTasksRepository .getTask(mTaskId) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer () { @Override public void onCompleted() { mTaskDetailView.setLoadingIndicator( false); } @Override public void onError(Throwable e) { } @Override public void onNext(Task task) { showTask(task); } }); mSubscriptions.add(subscription);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
代码说明:
subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。
所以M层发送过来的数据,只要在P层两行代码搞定线程切换,然后直接订阅就等待数据的到达。同时加入订阅者管理集合在相应的生命周期统一取消。
Model层
感觉上面说了这么多,Model层几乎都说完了。
数据层负责数据的在本地或者远程读写数据,每个应用的表示和存储形式都不会有些不同。
官方的MVP数据使用SQL存储,外部再维护一个的TasksRepository
做数据缓存。
MVP架构通过接口回调分发数据,响应式MVP通过Observable得到可观察数据。
如果结合Retrofit网络框架,哪响应式MVP的Model层数据来源。就是可以是。
Retrofit的静态构造方法构造网络请求对象,传入接口,代理模式生成出数据,P层就可以直接拿到数据了。
当然这只是我的初步想法,打算正用于我的个人项目。
图解
通过上面对MVP每一个模块功能的分析,最后上一张图,Android官方MVP整个项目的逻辑图。
需要具体代码可以到上Clone
总结
- 通过对比分析清晰了官方MVP的架构逻辑。
- 每个类都尽量符合面向对象设计原则,采用单一职责原则。整个项目架构清晰分工明确。
- 水平有限,请大家指正。依赖注入这块我也不是很懂,具体的需要大家去Google。
- 最后对响应式MVP结合Retrofit提出一点自己的构想。
参考