基于Flux的安卓应用设计模式的研究
Posted 上海交通大学计算机科学与工程系
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Flux的安卓应用设计模式的研究相关的知识,希望对你有一定的参考价值。
基于Flux的安卓应用设计模式的研究
徐健
上海交通大学计算机科学与工程系,上海,200030
摘要: 对比MVC,MVP以及Flux三者间的优缺点,发现后者更适用于业务逻辑复杂,功能模块繁多的大型项目。但要使用Flux并不是一件易事,其不是一个标准框架,而是一个设计思路。本文遵循其单向流的核心理念,通过事件总线框架实现整个数据流传递枢纽,指定基类和制定约束的方式,规范开发人员以声明式编程的方式来进行编码。以此发挥Flux设计思路的高度解耦作用。
关键词:设计模式;Flux;安卓应用
Research on DesignPattern of android Application Based on Flux
Jam Xu
Dept.of Computer Science&Engineering,Shanghai Jiao Tong University, Shanghai, 200030
Abstract: Compared to the advantages and disadvantages between MVC, MVP and Flux,Flux is found to be more suitable for large-scale projects with complicatedbusiness logic and large size of functional modules. Flux is not a standardframework, it is a design idea. This paper follows the core idea of itsunidirectional flow, implements the whole data flow transfer hub, specifies thebase class and the constraint way through the event bus framework, andregulates the developers to code in declarative programming way, so that we canplay a high degree of decoupling Flux design ideas.
Keywords: Design Pattern;Flux;Android Application
1 引言
随着项目需求的不断增加,对于大型项目而言,如何设计一个优秀的APP架构变得至关重要。出色的架构设计可以加快开发进程,减少漏洞,提高代码健壮性。使得整个项目从代码层面,逻辑更清晰,同时提高可维护性。
设计模式在架构过程中起了至关重要的作用,然而在安卓应用领域,传统MVC三层结构已经不足以应对大型项目,为此Facebook的Flux设计思路脱颖而出,其通过逻辑上的单向流通道,极大地降低了模块间的耦合。
本文吸取传统设计模式的优缺点,结合Flux的单向流设计思路,通过大型安卓项目的实践,设计了在安卓应用上,基于Flux的安卓应用设计模式的具体实现。
2设计模式
2.1 Android MVC
MVC的全名是:Model(模型) View(视图) Control(控制器)。其是较为传统,也较为通用的一种设计模式。通过将业务逻辑抽离出来放在控制器中,在修改视图界面时,不用修改业务逻辑部分实现代码[1]。通过将视图单独作为一层,可以让开发人员并行地开发视图和业务逻辑,从而提高开发效率。关系图:见图1。
图1 MVC关系图
在安卓应用开发过程中,可以说,MVC就是作为默认的设计模式。我们一般将xml布局文件作为视图层,Activity或Fragment等容器作为控制层,网络或数据库的数据作为模型层。xml布局文件并不独立于Activity或Fragment等容器而存在,其交互逻辑需要通过在Activity等容器内部实现。同时,模型通过Activity等容器的内部事件触发,并由其反馈的结果做对应的业务逻辑处理,将其更新至视图上。
这就导致控制层的代码过于臃肿。即使使用封装、抽离等思路,也无法避免模型层和视图层的过渡耦合。一旦耦合产生,那么当后期需求变更时,就非常容易出现“牵一发而动全身”的情况。而这种情况,则是我们开发过程中,需要极力避免的。
2.2 Android MVP
MVP是从经典的设计模式MVC演变而来的,它们的核心思想有着相同的地方,就是将业务逻辑的实现抽离出来,放在单独一层中处理[2]。对于MVP,其P层,即Presenter来负责实现这个功能。
图2 MVP关系图
MVP的M不同于MVC的M,其是对M的一个包装,提供了最简单的逻辑处理。其通过Presenter来隔离View和Model,通过访问Presenter接口的方式来进行视图的渲染,将三层间的耦合降低。关系图:见图2。
目前,MVP设计模式被应用在大部分安卓项目中。在一个典型的安卓应用中,M往往被定义为网络数据模型,也就是接口返回体。而V则是包含了xml布局、自定义控件、Activity/Fragment容器的集合体。Presenter就是一块封装良好的业务逻辑仓库,其包含了后台API的调用,网络数据的处理、解析,存储业务逻辑状态和视图状态标识,暴露set/get接口以供View层调用。
2.3 Android Flux
Flux最初是Facebook用来构建网页端应用的,其是React框架的延伸和拓展。其并非是一个形式化的框架,更多的是一个思想[3]。其通过单向数据流的方式,将一个业务逻辑事件链,拆分成若干个逻辑单元,通过声明式编程的思路,使得两两之间没有过多耦合。从而提高业务逻辑的可重用性、可维护性和健壮性。
目前已有一些大型安卓项目在使用Flux,但由于其没有一个固定结构,所以每个项目的架构不一定相同。但无论如何实现,都是遵循着单向数据流这个核心思想,围绕着声明式编程方式来搭建的。
3方案对比
3.1 可选方案
Flux的核心思想是单向数据流。在安卓中并没有一个完整的框架可以实现这个功能,可以通过规则制定和约束的方式,迫使开发人员,在实际编码过程中,按照单向数据流的方式来实现需求开发。
单向数据流的单元是事件。而事件的传递,则可以通过安卓自身的消息传递机制或通过事件总线的概念来实现。
3.2 Java事件传递机制
对于Java而言,在实例化某个对象后,需要收到该对象对应状态变化或结果时,往往都是通过Callback回调的方式来实现。通常的做法就是往该对象中实例化一个Interface并传入该对象,在该对象内部调用传入的Interface实例接口,以此来实现消息的传递。
安卓上层就是用Java实现的,所以这个做法在安卓编程中十分通用,也是大部分开发人员在需要实现消息传递场景时所采用的第一考虑的技术方案。但是这种实现方式,会导致两个对象间持有强关联关系,这是我们架构过程中要极力避免的。
3.3 事件总线
事件总线是一个基于观察者模式的框架[4]。在安卓端,通用的开源框架有OTTO,EventBus和RxBus。其中由于OTTO已经不再维护,并且如果只使用事件总线这一个概念,就没有必要将整个Rx库引入,所以可以选择GreenRobot的EventBus。
图3 EventBus示意图
由于EventBus是基于类来分发和接收的,所以通过该框架,我们可以轻松地将一个封装的事件类实例发送到事件总线上,且在声明接收的地方接收处理,两者之间只会存在对这个事件类的关联,见图3。凭借该机制可以轻松地从命令式编程切换至声明式编程[5]。开发人员不再考虑“我要干什么”,而是“我能干什么”。
4流程设计
4.1 流程图
见图4。
4.2 流程图说明
(1)以服务器交互举例,从服务器异步获取数据,也可以是一些数据库操作,本地文件读取等。
(2)通过ActionCreator产生某个有明确意义的Action。
(3)将该Action发送至事件总线的分发器。
(4)分发器将Action发送至声明接收该Action的Store。
(5)Store拆解收到Action后,直接发送Event或在接收若干个Action,集合所有事件后触发发送某个Event。
(6)将该Event发送至事件总线的分发器。
(7)分发器将Event发送至生接收该Action的ControlView。
(8)ControlView所渲染的页面上,触发一个用户行为,通知ActionCreator。
图4 Flux流程图
*注:单向数据流的框体中的虚线部分,意味着两者间的通信并不是直接交互,而是接收Dispatcher分发的一个个事件实例。
5详细设计
5.1 Dispatcher实现
Dispatcher作为Flux的四大组件之一,主要负责担当事件总线派发消息的职责。在绝大多数场景中,事件总线只有一条,所以Dispatcher一般被定义为单例模式。
在本文设计的方案中,Dispatcher主要负责分发Action和Event,其具体实现通过GreenRobot的EventBus落实。定义dispatch方法和post方法的入参分别为BaseAction和BaseEvent来约束,使用者必须调用对应接口,传入继承自BaseAction或BaseEvent的类。
public class Dispatcher {
private EventBusmBus;
publicDispatcher() {
mBus = newEventBus();
}
public void register(final Object o) {
mBus.register(o);
}
public void unregister(final Object o) {
mBus.unregister(o);
}
public void dispatch(BaseAction action) {
mBus.post(action);
}
public void post(BaseEvent event) {
mBus.post(event);
}
}
5.2 ControlView实现
在安卓上,ControlView的实现很简单,直接通过实现Activity或Fragment即可。在Activity的onCreate方法中注册Store,并在onDestroy方法中取消注册Store。增加有@Subscribe注解的方法,方法名可以是任意具有实际语义的定义,如onExitApp。
public class TestActivity extends Activity {
protected voidonCreate(Bundle savedInstanceState) {
Dispatcher.getInstance().register(TestStore.getInstance());
}
protected void onDestroy() {
Dispatcher.getInstance().unregister(TestStore.getInstance());
}
public void onBtnTestClick() {
// do something
}
// 声明接收该Even,开发人员不用关注何时何处会发出这个Event
@Subscribe
public void onExitApp(AppStore.ExitAppEvent event) {
finish();
}
}
5.3 ActionCreator实现
ActionCreator是Action的孵化器,在一个典型的金融或电商安卓应用中,APP需要频繁地与后台交互,这会产生大量的代码。我们将这些代码封装在这个ActionCreator中,Store不用关注其方法内的具体实现,只需要关注其所能发出的Action,并选择性地声明接收。
5.4 Action实现
Action就是一个标准的POJO对象[6],其封装了所需传递的源数据。它不提供任何改变其源数据的set接口,只暴露一些简单的get接口。所有源数据的修改,如重排序、筛选、改变数据等,这些都是与实际业务相关的逻辑,Action不封装这些逻辑。所有业务逻辑都放入Store中。
5.5 Store实现
Flux的Store其实就是担当了原来MVP中Presenter角色的部分功能,我们将Action创建相关的业务逻辑代码抽离出来放入了ActionCreator,那么Store就是在收到Action后,对其所携带的源数据根据具体的业务逻辑进一步处理的封装类。
同ControlView类似,通过增加有@Subscribe注解的方法,来声明接收某个Action。一个页面上往往会有多个请求,那就会有多个Action。Store的中文意思是仓库,我们在使用时,其在收到一个或多个Action后,解析Action的源数据,根据业务逻辑,将最终需要的内容保存至Store中储存起来。通过暴露get接口给ControlView调用,或进一步发出Event,给声明接收它的ControlView处理。
5.6 Event实现
在Flux的官方指导图中,并不存在Event这一组件。那么我们就需要考虑,当Store收到Action后,产生一个子事件时,如何通知ControlView接收。
Event这一组件,其基本作用就是在Store接收并分解若干Action后,发出新事件。这是一个很有用的组件,它可以将若干个Action合并,或是转义某个Action。重组或转义的过程,这是业务逻辑,而业务逻辑不是ControlView需要考虑的东西。这样对于ControlView而言,它只需要声明接收一个事件,而不需要关注这个事件究竟是通过几个Action的重组而来的,其内部的业务逻辑是什么。Event可以以push或pull的形式传递信息内容。
public class TestStore extends BaseStore {
private int flag;
// 对Action进行转义,以push形式传递用户个数
@Subscribe
public void onGetUserList(UserListRefreshAction action) {
post(new UserCountRefreshEvent(action.getList().size()));
}
// 等待两个Action都收到后,才发出某个Event
@Subscribe
public void onGetAction1(Action1 action) {
flag |= 1;
checkAndPost();
}
@Subscribe
public void onGetAction2(Action2 action) {
flag |= 2;
checkAndPost();
}
private void checkAndPost() {
if (flag == 3) {
post(new GetAllEvent());
}
}
5.7 规则和约束
所有Store、Action和Event都需要继承基类BaseStore、BaseAction、BaseEvent,用以Dispatcher做统一处理。
Event作为内部类定义在各自的Store中,允许ControlView声明接收其捆绑的Store发出的Event。
Action定义在各业务所在的包路径中,Store声明接收任意ActionCreator发出的Action,包与包之间隔离,互相之间不允许接收其他包发出的Action。如果有Action跨包的情况,统一将该Action定义在公共区。
6应用对比
6.1 逻辑耦合度
Flux就是MVP的一个扩展和延伸,且本文所讨论的场景是在大型应用以及复杂业务中,MVC已经不适用。所以在本章中所进行的对比,都是MVP与Flux。
在MVP中,其实已经很好的将业务的视图渲染逻辑和视图分离了。视图渲染逻辑,与业务逻辑是一个强关联,往往通过网络接口获取数据模型中的数据,通过业务逻辑要求,解析后进行视图渲染。在MVP中,我们将上述步骤,抽离出来,放在Presenter中完成。而视图容器,如Activity,只是对它的Presenter的接口有依赖,也就是耦合。
MVP通过这种方式,降低View和Model之间的耦合。既然Flux是MVP的延伸,Store和ActionCreator也发挥着类似的作用。除此之外,Flux独特的单向事件流的思想,更是可以让ControlView与Store解耦。ControlView只是声明需要接收的Event,并不关心是谁发出的,怎么发出的,何时发出的。这一点,是MVP无法完成的。
6.2 可扩展性
这里讨论的可扩展性,是指业务层面的可扩展性。在MVP中,业务逻辑是抽离在Presenter中的,业务视图是放在View中。在大部分场景下,View和Presenter是一一对应关系。然后有时候,一个View可能会依赖多个Presenter的视图渲染逻辑。当需要修改和新增Presenter时,View就需要修改对Presenter的依赖。
而当View与View之间需要通信时,其实是建立起了一种耦合。这是因为在安卓中,通信双方需要保证Intent中携带的数据,key所对应的value类型必须一致。当需要对通信内容进行扩展时,两个View以及其依赖的Presenter都需要做对应修改。改动量较大,可复用性非常低。
在Flux中,视图渲染是放在ControlView中的,渲染的内容则是由Store根据业务逻辑,解析若干Action后发出的。上文提到,ControlView不关心谁发出的,可以是任意Store。由于只存在对Event的依赖,所以需要扩展的部分就很简单了,无外乎新增Event或者增加修改已有Event的接口。同理,在ActionCreator和Store之间,没有任何依赖关系,Store只对Action依赖。
6.3 测试方法
以用户修改昵称为例,对比MVP和Flux实现的代码耦合程度。由于代码篇幅过长,这里用示意图和注释的方式加以说明。见图5。
通过示意图,可以看到在MVP实现方式中,Presenter和View都需要实例化注入到对方内,这就产生了耦合,而我们常常会将业务逻辑的处理写在Presenter中,这就给后续的业务修改和扩展造成了不便。
而Flux实现方式中,View和Store都是对于前者的声明接收,只是单向的依赖Event或Action,而不去关注这个Event或Action是哪个发出的,业务逻辑不是写在其单向依赖的这个Event或Action,而是ActionCreator和Store,这就给后续的业务修改和扩展提供了很大的便捷。
图5 对比示意图
举个例子,此时产品又提出需求,在其中一个页面,需要当用户修改了昵称并且在商城上购买一款产品时,才刷新用户昵称。在这种跨模块的场景下,再来看一下MVP和Flux实现的改动量。见图6。
图6 改动后的对比示意图
通过示意图可以明显看到,在MVP实现中,我们需要某个模块的Presenter嵌入到已有逻辑中,并不容易。而且非常容易在Activity残留一些判断逻辑,如:都回调了再刷新。而且这些都是业务逻辑,与实现了View的Activity无关。
在Flux实现中,只需要修改Store,为其增加一个声明,以接收“购物”的行为,并做后续的业务逻辑处理,然而对于ControlView而言,其依旧还是声明接收“刷新”事件,对于这个事件是怎么产生的,并不关心。这就将业务逻辑的实现和修改,与视图层完全隔离了,相互的耦合只是单向声明的Event或Action。所以在跨模块的依赖和通信场景中,Flux优于MVP。
6.4整体对比
从层级间耦合、代码的可扩展性和可阅读性通过表1进行对比,并给出其所建议应用的工程量级。
表1整体对比表
层级耦合 |
可扩展性 |
可阅读性 |
适应工程 |
|
MVC |
两两耦合 |
低 |
高 |
中小型工程 |
MVP |
V-M不耦合 |
中 |
高 |
大中型工程,模块间无依赖和通信 |
Flux |
单向依赖 |
高 |
中 |
大中型工程,且模块间有大量依赖和通信 |
7总结
本文介绍的基于Flux的安卓应用设计模式,遵循了Flux单向事件流的核心思想,采用事件总线框架作为流传递的媒介,通过规则和约束,使得开发人员以声明式编程的编码方式来完成需求开发。单向事件流使得组件间不存在双向绑定,代码易读且易于维护。以方法为最小单元,封装业务逻辑代码,提高复用性。
项目开发过程中,需求变更不可避免,通过这种单向绑定的方式,可以极大地降低代码修改量。但此设计模式不适合用于小型项目,通过单向事件流的方式拆解简单的业务逻辑没有太大意义,只会导致编码量增加。
但实际应用中,永远不存在一个最好的设计模式,需要架构师对于不同应用的不同场景,做出合理的判断并选择最合适的设计模式[8]。
参考文献:
Model-View-Controller:https://en.wikipedia.org/wiki/Model-view-controller
Model-View-Presenter:https://en.wikipedia.org/wiki/Model-view-presenter
Facebook Flux:http://facebook.github.io/flux
Publish-Subscribe:https://en.wikipedia.org/wiki/Publish-subscribe_pattern
Declarative Programming:https://en.wikipedia.org/wiki/Declarative_programming
POJO:https://en.wikipedia.org/wiki/Plain_old_Java_object
Android Flux:http://www.jianshu.com/p/896ce1a8e4ed
Design Pattern:https://www.zhihu.com/question/23757906
收稿时间:2018-11
发稿时间:2018-12
版权归上海交通大学计算机与工程系所有,如有转载请注明出处。
以上是关于基于Flux的安卓应用设计模式的研究的主要内容,如果未能解决你的问题,请参考以下文章
java计算机毕业设计基于安卓Android的健康管理APP
java计算机毕业设计基于安卓Android的校园财务流水系统APP