by 高煥堂 2016/2/15

 

5.1 前言

   在Android里,一个Activity对象可管控多个画面Layout的呈现和变换。在架构师的观点下,Layout的变换可视为Activity对象状态(State)的转移(Transition)。因此,工程界所熟悉的状态机(State Machine)机制,就能做为Android UI( User Interface)幕后的控制架构了。而UML提供了状态图(Statechart),可用来绘制状态机的状态变换行为。于此,使用UML建模工具来绘制状态图,以建立交互设计师与UI代码开发者之间的沟通桥梁,。

 

5.2 状态及其转移

    Activity对象就像一个人,某事件(如失恋)发生时,常导致状态之改变;而不同状态会引发不同之行动。事件(Event)、状态(State)和行动(Action)是人们掌握Activity对象行为时所关注的要素。首先,我们可藉由UML状态图来表达事件触发状态转移的情境,如下图1所示。

 

     图1、对象的状态转移

   此图表示:当事件X发生时,若C条件成立,就触发Activity对象从状态1转移到状态2。再看看更复杂的状态变化,如下图2。

 

             图2、较复杂的状态转移

  自然界的对象,在其生命期中,内部状态常不断变化。有些过程是循环的,有些则是分阶段的。例如,红绿灯也有3个状态,并且周而复始地变换。

  

5.3 巢状的状态

   状态可组成巢状(Nested)的结构,如下图3所示。

 

            图3、巢状的状态结构

    状态1内含两个小状态,这种巢状结构又称为阶层(Hierarchical)结构。在图1-3里,如果状态1和2是在层级1(即Level 1),则状态4和5就属于层级2。一个状态机可以有许多层级,各层级可以有许多状态。当我们说Activity对象处于状态1时,意谓着,它若不是处于状态4就是处于状态5。图1-3表达了:

   当Activity对象处于状态4,而且发生事件Q时,就会转移到状态2。

   当它处于状态4或5,而且发生事件X时,都会转移到状态2。

   当它处于状态2,而且发生事件M时,会转移到状态1里面的状态5。

   当它处于状态2,而且发生事件Y时,都会转移到状态1里面的状态4或5。至于确实转移到状态4,还是状态5呢?针对这一点,图1-3的表达并不清楚。图1-4就清晰多了。

 

5.4  预设起始状态

   刚才说过,状态1里头又分为两个小状态,当进入到状态1时,必定是处于「正常」或「溢领」两个状态之一。图1-4中的 l代表预设(Default)之意,每次进入状态1时,必先进入状态4,这个状态4就称为状态1内的「预设起始状态」(Default Starting State)。

 

              图4、预设起始状态

当它处于状态2,而且发生事件Y时,都会转移到状态1,此时会立即进入状态4。

  

5.5 历史状态

 刚才提到,l代表“Default”之意,每次进入状态1时,必先进入状态4。不过,有些情况下,是有些例外的。例如,一旦离开状态1而转移到状态2之后,再进入状态1时,就不一定进入状态4了,而是依先前离开状态1时的最后停留的(The Most Recently Visited)小状态而定了。例如图5所示。

 

              图5、历史状态

   假设目前是处于状态4,发生了X事件,就离开状态4,转移到状态2。此时如果再发生R事件,就又进入状态4,因为刚才是由此状态4出来的,这就是历史状态(History State);在图里,以H表示之。为了实现这个状态图,程序必须在离开状态1的时刻,将其最后停留的状态(状态4或状态5)记录起来(成为历史状态),以便返回到H时能回复到其历史状态。

 

5.6 多层级的状态机

  基于上述的巢状机制,我们可以设计更多层级的状态机,如图6所示。

 

            图6、多层巢状的状态机

   当Activity对象处于状态2,而且发生事件Y时,就会转移到状态6。当它先前处于状态7,发生了X事件,就离开状态7(及状态5),转移到目前的状态2。此时如果再发生R事件,就又进入历史状态5,因为先前是由此状态5出来的。进入状态5之后,立即进入状态6。请留意,此刻并不返回到状态7,因为状态5里并未表明其历史状态。我们也可以在状态5里表明其历史状态,如图7所示。 

 

             图7、多层的历史状态

    当它先前处于状态7,发生了X事件,就离开状态7(及状态5),转移到目前的状态2。此时如果再发生R事件,就又进入历史状态5,因为就状态1而言,其最后停留的是状态5。进入状态5之后,立即进入历史状态7,因为就状态5而言,其最后停留的是状态7。

 

5.7 特殊情境:尚未有历史状态

     刚才提到,在图1-7里,一旦进入状态5,就立即进入历史状态7。然而,如果是第1次进入(先前未曾进入过)状态5,尚未有历史状态,那该怎么办呢?此时,可表达如图8所示。

 

          图8、历史状态 + 预设起始状态

    一旦进入状态5,就进入历史状态。如果是第1次进入,尚未纪录其历史状态,就进入预设起始状态6。反之,如果已有了历史状态,就进入历史状态。

 

5.8 顺移状态(Transient State)

     顺移状态是一种特殊的状态,它并不会等待事件发生来触发转移到下一个状态。一旦进入顺移状态,会执行其内部动作,执行完毕就立即(离开而)转移到下一个状态。不过,经常在离开之前,会依据特定条件而决定转移到那一个后续状态。如图9所示。

 

             图9、顺移状态

   事件X发生而触发从状态1转移到状态2,有时会携带mode值到状态2,或是在状态2执行其内部动作时取得mode值(从Database或计算而得),于是根据mode值而判断转移到状态3或状态4。记得,执行完毕状态2的动作后,立即离开状态2,不需等待任何事件之发生。

  

5.9 并行(Concurrent)状态

    在巢状态里,常常发现内含多个独立而并行(Separate Concurrent)运作的状态转移图,如图10所示。

 

          图10、并行状态转移

此图表达了:

  • 当Activity对象处于状态2,而且发生事件C时,就会转移到状态1,并且立即同时进入状态3和状态5。此刻,状态1的内涵由两个小状态3和5所组成的。
  • 此刻,如果发生事件Q,右上角的状态机就会转移到状态6,此刻,状态1的内涵由两个小状态3和6所组成的。
  • 接着,如果发生事件A,就离开状态1(并记录其历史状态),转移到目前的状态2。此时如果再发生事件B,就又进入历史状态3和6,因为先前是由状态5出来时,状态5之内涵是两个小状态3和6所组成的。
  • 当Activity对象处于状态2,而且发生事件Z时,整个状态机就结束了。

 

5.10 状态变量(State Variable)

 

  为了记录各层级状态转移的现况,我们通常会设立不同的变量来表示之,如图11所示。

 

        图11、并行状态转移

   在图11里,含有两个层级,状态1和状态2属于上层,而状态3、4、5和6则属于下层。各层必须各使用一个状态变量来记录之。例如变量A就是上层的变量,而变量B和C则为下层的变数。

从图11里,可得知:状态1与状态2是不会同时存在的,不是处于状态1就是处于状态2,所以它们之间有箭头代表状态的转移。同理,状态3与状态4也是不会并存的;还有状态5与状态6也是不会并存的。但是,状态3或状态4是会跟状态5或状态6并存的。因此,需要用到3个状态变量:

  • 变数A的值为1或2。
  • 变数B的值为3或4。
  • 变数C的值为5或6。 

依据这些变量值,就能记录状态机的现况,以及记忆其历史状态。例如,如果发生事件B,进入状态5时,查看变量值,如果B = 4且C = 5,就知道其历史状态就是状态4和状态5的组合了。 

 

5.11 子状态机(Submachine)

   针对一个复杂的状态机,我们可以使用子状态机来分层简化;就像一个复杂的程序,我们常常将它分解为多个子程序(Subprogram)一样。虽然本质上状态机的复杂性如故,但是经过如此的分而治之后,去让人们在管理上简化许多。例如,上图11里面的状态1有些复杂,可以将它独立出来成为一个子状态机。于是原来的一张图,就分为两张图了。如下的图12和图13。

       

      图12 、主状态机                                图13、子状态机

 

   这样将一个复杂的状态机分解为多个简单的小状态机,即能达到分而治之的简化目的;更能进一步分工,将各子状态机分派给不同的人去绘制。

  

5.12 事件与动作(Operation)

   外来的事件(Event)会触发对象的状态(State)转移,而对象转移到新状态会做出新的行为(Behavior)。换句话说,事件的发生不仅会触发状态转移,同时也会触发对象去执行某些动作。例如,人们接到红灯传来的讯息,就知道停下来。含苞待放的花散发出令人期望的讯息,而凋谢的花带给人另一种心情;看到盛开的花会想买它,而不会去买凋谢的花。如下图14。 

 

                 图14、事件、状态与活动

   当对象进入Stste1时,就会执行两个动作:

  • 将1值指定给state_var_A变数。
  • 执行show_layout_01()函数。

还有,当OnKeyDown事件发生时,如果isCube值为false,就做两件事

  • 执行show_layout_02()函数。
  • 进入State3 (当然也进入State2)。

当对象进入Stste3时,就会执行两个动作:

  • 将2值指定给state_var_A变数。
  • 将3值指定给state_var_B变数。

 

5.13  状态机与Android的天作之合

Android 是状态机动力的来源

      当Android应用程序执行时,Android会不断发出讯息(表示事件发生)给我们所设计的状态机,持续推动状态机的运转。Android 成为状态机运转的动力来源。如图15所示。

画面布局(Layout)与状态的联想

   如何从Android画面的变化中找出状态及其转移呢?这是大家最常提出的问题。其最容易的答案是:从Android的Layout联想到状态。一开始,不需要太完美的切入点,只要一个Layout对应到一个状态就行了。

状态机直接与Android亲密互动

  请留意,有人常误解这里使用状态机的目的。一般而言,使用状态机(或UML的状态图)的目的有二:

  • 做为系统分析与设计的工具:以发掘User的需求为主,先规划状态机,然后才思考如何在Android画面上实现该状态机之设计。
  • 做为创造高可靠度的工具:以严格控制系统为主,先完成画面Layout的设计(方法途径不拘),然后设计状态机来精确控制User与系统的互动行为。

     在本书里,我们是针对上述的第2项目的而使用状态机。

  

                      图15、Android推动状态机的运转

    当User按下<A>键,Linux操作系统侦测到此事件发生了,会传达讯息给Android应用框架(Framwork),Android就发出OnKeyDown讯息(或称为事件)给我们设计的状态机,就推动状态机的运转了。同样地,当User画面上的CheckBox时,Android就发出OnCheckedChanged讯息(或称为事件)给状态机,触发状态机的转移了。

  

~ End ~