MVC安卓实践
上周写了《MVC简述》,如果就这样结束MVC探索之旅,感觉还是停留在表面,前人不是有把学习过程分为几个阶段的嘛:知、学、练、熟、精。我只是达到了“知”,所以希望通过这篇文章进入到“学”和“练”的境界。
安卓出来有好多年了,刚出来的时候,差不多是09、10年吧。那时,我正在做flex,心中还有一个RIA梦,结果html 5和手机蓬勃发展,一切都变了,现在还没有回个神来。所以,前端开发要紧跟时代,只能不断学习和动作迅速。虽说很多东西再变,但也有一些没变的,就像做房子,做房子的材料在变,很久以前是木头,后来是砖瓦、再到现在的钢筋水泥,以后还不知道是什么,但是房子结构设计,比如说稳固性,一些古老的建筑至今为什么保存完好,地震的时候为什么没有倒塌,仍然是现在很多建筑师参考的范本。回到开发,MVC不管用Flex,还是安卓,都有它的意义。所以今天我就来班门弄斧一下,看看在安卓开发中怎么用MVC思想来设计。
我在这里先把这篇文章的内容、形式做一下限定。因为时间有限,不可能搞一个android的MVC框架出来,想搞,自己现在也没有那个能力。android是基于Activity的,所以在这篇文章里,我将选用一个场景或者案例来对一个Activity 进行MVC的实现。
场景
这个场景是这样的:用户有一个习惯,喜欢计帐目,所以需要一个账目清单,每有一次花销,就会把它记入这个清单里面,并且需要知道总的花销数目。
图1:用例图
放到实际功能上,可以这样说:
1, 用户打开应用后,显示一个了账单的列表,每行显示两列,一个是条目名,一个是费用。最后一行的条目名是合计,费用是上面各列的总费用。
2, 在清单下方有一个添加按钮,点击后会弹出一个对话框,里面有两个输入框,分别用于输入名称和费用;还有确定、取消按钮,当确定按钮被按下后,账单列表中会增加一行条目,并且总费用会增加。
设计分析
在这个例子中Model(模型)很好确认,模型的数据就是账单List,行为就是统计费用总和。对于View和Controller的切割可能有不同的意见。首先我们来看用户行为,有三个:
1, 添加按钮被点击,触发新建条目对话框;
2, 对话框上的确定被按钮点击,新添加一个条目,关闭对话框;
3, 对话框上的取消按钮被点击,关闭对话框。
这些行为应该放在控制器里面,但是实在是很简单,所以很多人可能就不会再创建一个控制器类来封装他们,而是把它们直接放在Activity里面。这样做也有一定的理由,因为android的视图已经用layout.xml给代替了,并且Activity负责一些窗口状态的处理,比如窗口隐藏的话,onPause方法会被调用,所以Activity天生就具备一些控制器特质。
我还是偏向于为Activity的View单独创建控制器,以独立于Activity。Activity作为窗口控制器,是View控制器的上级,所以它们分别是:
Model: 账单,负责保存所有账目,并且可以汇总。
View: 用layout.xml表示.主要有账单列表视图和添加新帐目视图。账单列表视图是主Activiy的ContentView。当点击添加按钮后,会弹出一个对话框,里面包含了添加新帐目视图。
Controller: 所有的Activity、Dialog以及对应的视图控制器。它们负责创建视图,建立视图和模型的关系以及监控用户操作、系统状态等等。
实现
实现包括各组成部分的初始化,以及之间的交互和结束。我们先来看看初始化。
初始化
在这里我们主要讨论它们初始化的位置,由谁来初始化它们。
视图控制器一般都由上级控制器(Activity, Dialog)创建。控制器内部负责监控视图上的操作,但是视图不一定初始化了,而Activity或者Dialog(更准确是DialogFragment) 知道什么时候视图被创建,继而可以交给控制器来监控用户行为。如果视图控制器由第三方创建,那么等到一定时机,需要被通知视图准备好了。而视图创建好了,只有上级控制器知道,所以这需要通过中介来达到目的,有点曲线救国的意思。
视图由Activity或者DialogFragment创建,这个不多说,android系统决定的。
模型由视图控制器创建。控制器负责读取模型数据并且负责把数据赋值给视图,这样模型将独立于Activity。在android 里没有flex或者wpf里的数据绑定的概念,不会主动读取数据,所以视图是一个被动视图,需要控制器来负责绑定数据和视图的关系。
图2:账单类图
交互
这里交互可以看成两部分,一部分是控制器监控用户行为、处理用户行为;另一部分是MVC各部分的通讯。
我们先看第一部分。Android设计的特别好的一点就在这里,UI所有的用户交互都可以通过设置监听器来获得。控制器主要任务就是给各个UI组件设置监听器来捕获用户行为,然后作相应处理。如果说该行为的影响范围只涉及到本视图或者本数据模型,那到这里就结束了,但是世上没有这么好的事情,不能太容易让你们这些程序员拿高工资!所以就有第二部分,需要通知app的其他部分来处理这些行为。
在本例子中就有一个这样的行为。在添加账目按确定按钮后,控制器监控到该行为,并且可以得到添加账目的名称和费用,然后需要把这个账目添加到账单中,但是该控制器没有账单信息。这个账单在哪呢?在ContentViewController里面,所以需要控制器之间的通讯。
ControllerBase实现了这个功能:它有一个控制器消息集中器,当Controller构造的时候,就会被注册到控制器消息集中器上面,所以集中器知道所有的控制器,当其中一个控制器发送消息的时候,其他的控制器都会收到该消息。如下图所示:
图3:消息传送
那么MVC之间还有哪些通讯? 理论上还有:
1, Model和Model, Model和View,Model和Controller。
在MVC里,Model变化会引发事件或者触发监听器。View和Controller监控模型,模型不会主动去和View、控制器通讯。在android里视图是被动视图,Model引起视图变化,只能通过控制器来改变视图,所以说Model和View的通讯可以看成是Model和Controller通讯。如果控制器是Model对应的,这个问题就不是问题,因为控制器本来就用来监控Model。如果这个控制器是第三方的,我们姑且不说这是否合理,有两个途径:
l model a -> model b -> controller b
l model a -> controller a -> controller b
2, View和View,View和Model、View和Controller。
a) View 和View 可以通过控制器来传递。比如view a -> controller a -> controller b -> view b。
b) view 不能直接改变Model,所以需要通过控制器,所以就是View和Controller通讯的问题。
c) View和Controller通讯也可以通过控制器之间通讯来达到。比如 view a -> controller a -> controller b。
3, Controller和Controller, Controller和View , Controller和Model。
a) Controller和Controller: 需要控制器间通讯。
b) Controller和View:可以通过控制器通讯实现。比如 controller a -> controller b -> view b
c) Controller和Model: 也可以通过控制器通讯实现。比如controller a -> controller b -> model b
所以综上分析,在不违背MVC原则下,控制器间通讯十分必要,其他的通讯都可以通过它来转发。但是怎么找到对应的控制器,比如说控制器 a需要改model b,怎么知道哪个控制器和model b对应呢?并且该控制器还存在,估计是一个问题。所以在设计模型的时候,尽量不要出现这种情况。
结束
在这个例子中,因为控制器消息集中器里面注册了所有的控制器,所以在控制器销毁前,需要向消息集中器取消注册。