You are on page 1of 49

Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

第 5章
30 分钟认识 UML 活动图

5.1 绘制 UML 活动图(Activity Diagram)


启动了 Astah,在主画面上点选< Diagram>,出现:

82
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

接着,点选<Activity Diagram>,就会出现一张空白的活动图,如下:

在空白的活动图上方,有一排活动图的元素(Element),简称「图素」,如下:

此列元素中的左边第 5 个就是「活动」(Action)图素。当你点选此图素(如
上图所示),然后移动鼠标(Cursor)到图表里的特定位置,并按下鼠标左键,就
在图表里出现一个活动图素,如下:

83
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这让你输入活动的名称,例如取名为:bindService(绑定服务),如下:

这个图素代表一项动作或活动。如果对照到大家所熟悉的程序码:

public class myActivity extends Activity implements OnClickListener {


// ……..
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// ……..
// ……..
bindService(new Intent("com.misoo.pk01.REMOTE_SERVICE"),
mConnection, Context.BIND_AUTO_CREATE);
}

上图就表达了这段程序码里,由 myActivity 引发(或主导)的活动:


z 绑定跨进程(Inter-Process)的远距服务。换句话说,就是与远距
的服务建立联机(Connection)。

84
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

一个活动图里,有许多项活动,而且有一定的顺序。所以必须标示整个
活动流程的起头。此时可以选取<InitialNode>图素,如下:

先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个启始(InitialNode)图示:

接着,使用<ControlFlow>图素,如下:

先点选了这个图素,接着将鼠标移动到启始(InitialNode)图示上,按住并拖拉
到 bindService 活动(图素),就出现:

85
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

其实,UML 活动图就是大家所熟悉的「流程图」(Flow Diagram)。从传统流程


图中,可知道流程图中有一个重要的元素是:决策(Decision)。为了表达这项
决策,可选取<Decision Node>图素,如下:

先点选了这个图素,接着将鼠标移动到活动图的任何位置,并按键,就
出现了一个决策(Decision)图示。接着,拉出一条<ControlFlow>图素(即流程箭
头),如下:

在 Android 跨进程(IPC)的通讯架构里,绑定服务时,会返回调用(Callback)
到 ServiceConnection. onServiceConnected()函数,如下述常见的程序码:

86
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

private ServiceConnection mConnection = new ServiceConnection() {


public void onServiceConnected( ComponentName className,
IBinder ibinder)
{ m_ibinder = ibinder; }
public void onServiceDisconnected(ComponentName className) {}
}

如果顺利绑定成功了,就能继续执行目标活动:调用远程服务(Remote
Service);也就是有条件地调用这个目标活动。于是:
z 拉出一个新活动:callService(调用远距服务)。
z 拉出一条新流程箭头。
z 点选上述新流程箭头,呈现出它的属性表(Attribute Table),如下:

输入条件属性之后,得到下图:

87
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

如果绑定不成功,就不能执行 callService(调用远距服务)了,转而执行(也
是有条件的执行)其它活动或结束整个流程了。例如,可选取<ActivityFinal>
图素,如下:

先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个流程结束(ActivityFinal)图素,并且填上它的条件,如下:

88
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

最后再从 callService 拉一条流程箭头到流程结束(ActivityFinal)图素,如


下图:

这项 callService 活动就对应到大家所熟悉的程序码:

private ServiceConnection mConnection = new ServiceConnection() {


public void onServiceConnected( ComponentName className,
IBinder ibinder)
{ m_ibinder = ibinder; }
public void onServiceDisconnected(ComponentName className) {}
}
public void onClick(View v) {
// ……..
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
// ……..

89
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

m_ibinder.transact(1, data, reply, 0); // callService


}

上述的活动图是由 Astah 自动取名的,其名称是:Activity Diagram0。若


想改名称,可以点选<Activity Diagram0><Modify Name>,如下:

就可以更改名称了,例如改名为:“ClientDiagram”。活动图的颗粒大小(细
腻度)决定于架构师所采取的抽象层级(Abstract Level)。例如,针对上述活动图,
可以稍微降低抽象层级,增添更多详细的内容。也就是提高了活动图的细腻
度 ( 或 称 降 低 颗 粒 度 ) 。 现 在 , 来 增 添 一 个 新 的 活 动 图 。 请 点 选 <Activity
Diagram0><Create Diagram><Add Activity Diagram>,如下:

90
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

并替新活动图取名如下:

把刚才的活动图里的一部分复制(Copy)过来,如下图:

91
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

在 Android 环境里,Client(即 Activity)在进行 bindService 活动时,会发送讯号


到 另 一 个 进 程 里 的 Service 。 为 了 表 达 这 项 讯 号 发 送 (Send) , 可 选 取
<SendSignalAction>图素:

先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个发送讯号(SendSignalAction)图素。然后,从 bindService 拉一条流程箭头到
发送讯号图素,如下图:

幕后的 Service 接到讯号时,会进行相关的绑定活动,例如执行 onBind()


函数等。Service 执行绑定活动之后(可能绑定成功或不成功),会回传讯号给

92
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

Client(例如,返回调用(Callback)到 ServiceConnection. onServiceConnected()函


数)。此时,Client 会接收(Accept)到对方回传来的讯号。为了表达这项讯号接
收,可点选<AcceptEventAction>图素:

先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个讯号接收(AcceptEventAction)图素。如下图:

依据接收到的讯号,判断是否定成功。如果成功,就继续执行目标活动:
调用远程服务(Remote Service)。反之,如果绑定不成功,就结束整个流程了。
于是得到活动图,如下:

93
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

上述活动图(ClientDiagramDetail 图)表达了 Client 发送与接收讯号的活


动。同样地,你也可以画一张活动图来表达 Service 端的接收与发送讯号的活
动。请产生一张新的活动图,如下:

Service 端先接收到 Client 传来的讯号,然后处理服务绑定的工作(活动),


再发送讯号给 Client。于是,绘制 Service 端的活动图,如下:

94
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这项 Binding Service 活动可对应到大家所熟悉的 Android 程序码:

public class myService extends Service {


private IBinder mBinder = null;
// …….
@Override
public IBinder onBind(Intent intent) { // accepting
return new myBinder(getApplicationContext()); // sending
}
}
public class myBinder extends Binder{
// …….
public myBinder(Context ctx){
// ……..
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
// ……..
}
}

一接收到讯号,myService 就执行 onBind()函数;执行完毕,就发送讯号,


透过 Android 框架,将 myBinder 的 IBinder 接口回传给 Client 端。

95
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

5.2 活动图的分区(Partition)
在 UML 里,以套件(Package)来组织模型(Model),套件可以包含多个小
套件,套件里可包含多张各种图表(Diagram)。这些图表就构成模型。例如,
刚才所绘制的 3 个图都是摆在 package0 里面,如下图:

在上图里,有一张图(名称为 ServiceDiagramDetail)呈现于画面上。先将鼠
标移到下图里,点按<红色 X>,就能关闭此图表了。
接 着 , 就 来 建 立 一 个 新 的 套 件 。 请 点 选 <Activity Diagram0><Create
Model><Add Package>,如下:

96
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

建了新套件,就能在套件里建立各种图表了。例如,可点选
<package1><Create Diagram><Add Activity Diagram>,如下:

97
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

于是,建了一个新的空白活动图,并自动取名为:Activity Diagram0,如
下:

在这张新的活动图表里,可以来认识活动图的其它重要图素。例如,请
点选<Partition [Horizontal]>图素,如下:

当你点选此图素(如上图所示),然后移动鼠标(Cursor)到图表上,按下鼠
标键,就出现一个分区(Partition)图素,如下:

98
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

兹填入「分区」的名称(例如“User”),得到下图:

这意味着,User 是此分区里的活动的执行者(Performer);也就是说,将由 User


来执行此分区里的活动。同样地,可以再拉出一个新的分区,如下图:

99
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

然后,将一个活动图里的各项活动分派给适当的执行者。例如下图:

100
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这活动图表达了:
z User(用户)做第 1 项活动:点选特定的 App(应用程序)。
z 接着,App 程序执行第 2 项活动:建立 UI 的布局(Layout),并显示
于画面上。执行这项活动的 Android 程序码如下:

public class ac01 extends Activity implements OnClickListener {


// …….
private Button btn, btn2, btn3;
private MediaPlayer mPlayer = null;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
btn = new Button(this); btn.setId(101);
btn.setText("play"); btn.setBackgroundResource(R.drawable.heart);
btn.setOnClickListener(this);
LinearLayout.LayoutParams param
= new LinearLayout.LayoutParams(80, 50);
param.topMargin = 10;
layout.addView(btn, param);
btn2 = new Button(this); btn2.setId(102);
btn2.setText("stop"); btn2.setBackgroundResource(R.drawable.heart);
btn2.setOnClickListener(this);
layout.addView(btn2, param);

btn3 = new Button(this); btn3.setId(103);


btn3.setText("exit"); btn3.setBackgroundResource(R.drawable.heart);
btn3.setOnClickListener(this);
layout.addView(btn3, param);
tv = new TextView(this); tv.setTextColor(Color.WHITE);
tv.setText("Ready");
LinearLayout.LayoutParams param2 =
new LinearLayout.LayoutParams(FP, WC);
param2.topMargin = 10;
layout.addView(tv, param2);
setContentView(layout);
}

101
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

z 然后,User 做第 3 项活动:触动(点选)UI 画面上的按钮(控件)事件。


z 接着,App 程序执行第 4 项活动:处理事件。执行这项活动的 Android
程序码如下:

public class ac01 extends Activity implements OnClickListener {


public TextView tv;
private MediaPlayer mPlayer = null;
public void onCreate(Bundle icicle) {
// ……..
}
public void onClick(View v) {
switch (v.getId()) {
case 101:
if(mPlayer != null) return;
mPlayer = MediaPlayer.create(this, R.raw.test_cbr);
try { mPlayer.start();
} catch (Exception e) {
Log.e("StartPlay", "error: " + e.getMessage(), e); }
break;
case 102:
if (mPlayer != null)
{ mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
break;
case 103: finish(); break;
}
}}

这项活动(即上述的第 4 项)是由 ac01 和 MediaPlayer 两个类别相互合作来


完成的,其详细合作、互动与讯息传递则由 UML 顺序图来呈现之。◆

102
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

高煥堂 老師 即將(6 月下旬)在 北京、深圳各講授


<Android 架構師、軟硬整合與應用框架開發> 公開課:

慶祝新開班,搶鮮特惠價,請勿錯過良機
詳細說明,請看<Android 論壇技術教育中心>網頁:
http://www.android1.net/?Forum32/thread-22965-1-1
或詢問:
AndroidEdu520@gmail.com (劉智勇收)

103
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

第 6 章
30 分钟认识 UML 状态图

6.1 绘制 UML 状态图(Statemachine Diagram)


启动了 Astah,在主画面上点选< Diagram>,出现:

104
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

接着,点选<Statemachine Diagram>,就会出现一张空白的状态机(Statemachine)
图,(简称为状态图),如下:

就可以绘制状态图了。

6.2 起步:单一状态
在空白的状态图上方,有一排状态图的元素(Element),简称「图素」,如
下:

此列元素中的左边第 3 个就是「状态」(State)图素。当你点选此图素(如
上图所示),然后移动鼠标(Cursor)到图表里的特定位置,并按下鼠标左键,就
在图表里出现一个状态图素,如下:

105
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这让你输入状态的名称,例如取名为:State1,如下:

一个状态图里,有许多个状态,而且有一定的事件触发规则。所以必须
标示整个事件流程的起头。此时可以选取<InitialPseudostate>图素,如下:

先点选了这个图素,接着将鼠标移动到状态图的任何位置,就出现了一
个启始(InitialPseudostate)图素:

106
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

接着,使用<Transition>图素,如下:

先点选了这个图素,接着将鼠标移动到启始(InitialPseudostate)图示上,按
住并拖拉到 State1 状态图素,就出现:

点选 State1 状态,呈现出它的属性表(Attribute Table);然后填入属性值,


如下:

107
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

这表示:当一支 App 程序系统转移(Transition)到 State0 状态时,会做(Do)这项


工作:show_layout_01。这表达了大家常见的 Android 绘图程序码,如下:

public class myActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
goto_State1();
}
void goto_State1()
{ show_layout_01(); }
void show_layout_01(){
LinearLayout layout = new LinearLayout(this);
// ……..
GraphicView gv = new GraphicView(this);
layout.addView(gv,param);
setContentView(layout_01);
}
}
public class GraphicView extends View{
// …….
@Override
protected void onDraw(Canvas canvas) {
// …….
}
}

这项 show_layout_01 工作显示一个画面,如下:

108
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

从上述程序码,可看到:当这 myActivity(即 App)处于 State1 状态时,


Android 框架(Framework)会不断触发 onDraw 事件,引发 myActivity 去执行
onDraw() 函 数 , 以 便 刷 新 画 面 上 的 图 形 。 为 了 表 达 这 个 事 件 , 可 选 取
<Transition>图素,如下:

先点选了这个图素,接着将鼠标移动到 State1 图素,按住并拖拉到此


状态图素外,选择在两个位置,各按一次键;再回到 State1 图素内按键,才
放开,就出现一条状态转移线(State Transition Line):

109
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

填入事件名称:onDraw。然后,点选这条转移线,按右键,出现:

兹选取<Line Style><Curve>,图形就转变成为弧形的状态转移线了,如下:

110
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

当用户决定结束掉这个 App 的执行时,触发了 Finished 事件,就结束整个流


程了。于是,可选取<FinalState>图素,如下:

先点选了这个图素,接着将鼠标移动到状态图的任何位置,就出现了一
个结束状态(FinalState)图素,并且填上它的条件,如下:

以上是最简单的状态图,只有一个状态。其中,onDraw 事件是由 Android


框架不断地重复触发的。

6.3 多个状态
自然界的事物,在其生命期中,状态常不断变化。有些过程是循环的,
有些则是分阶段的。例如,红绿灯之状态是周而复始地变换;其中<红灯亮
>、<绿灯亮>和<黄灯亮>分别是红绿灯对象的 3 种状态。
在 Android 里,一个 App 常常提供多个画面布局(Layout)。在 App 执行
期中,布局常不断变化,有些循环的周而复始地变换、有些则是分阶段的。
所以,布局的变换呈现出 App 状态的转移(Transition)。现在,就来绘制 UML
状态图来呈现 App 画面布局的变化(即状态转移)情境。

111
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

6.3.1 建立模型

兹选取<Create Diagram><Add Statemachine Diagram>来产生一张新的状态


图:

针对一支 Android 的 App 程序码,在这张新状态图里,拉出两个状态:

112
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

当 App 在状态 01 时,会做(Do):show_layout_01。而当处于状态 02 时,


会做(Do):show_layout_02。于是,输入 Do 行为的名称如下:

在 layout_01 画面和 layout_02 画面上,都有绘图区。Android 框架


(Framework)会不断触发 onDraw 事件,引发 App 去执行 onDraw()函数,以
便重绘画面 layout 上的图形。于是,可选取<Transition>图素,拉出两条状
态转移线,如下:

113
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

在状态 01 时,会执行 show_layout_01()函数,显示出 layout_01 画面如


下:

此时,如果按下<A>键(触发一个事件),App 就转移到状态 02。


在状态 02 时,则执行 show_layout_02()函数,显示出 layout_02 画面如下:

114
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

此时,如果按下[Y]按钮(触发一个事件),就转移(返回)状态 01;如果按
下[Z]按钮,就结束了。现在,就在状态图上,来呈现上述的事件和状态转移
情境。首先,从状态 01 拉一条状态转移线到状态 02;并在其属性表里输入事
件和条件名称,如下:

115
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

调整一下图形,看起来更美观一些。如下:

接着,从状态 02 拉一条状态转移线到状态 01;并输入事件和条件名称,如下:

116
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

当用户按下<A>键或是[Y]按钮时,Android 框架会发出 OnKeyDown 或


OnClick 事件,而这些事件成为推动状态机运转的动力,让状态机在状态 01
与状态 02 之间来回转移。OnKeyDown 事件会带来 KeyCode 值,如果 KeyCode
值为’A’,才会转移到状态 02。同样地,OnClick 事件发生时,会带来 Button
参考(Reference)值;如果其值为 ButtonY,才会转移到状态 01。如果其值为
ButtonZ,就结束状态机了。

6.3.2 对应的 Android 程序码

上述的模型,可以落实到 Android 应用程序码,如下:

// ac01.java
package com.misoo.ppxx;
import android.app.Activity;
// …….
public class ac01 extends Activity implements OnClickListener {
private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
private GraphicView gv = null;

117
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

private LinearLayout current_layout = null;


private int state_var_A = 0;

@Override public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(R.layout.main);
goto_state_1();
}
void goto_state_1() {
state_var_A = 1;
show_layout_01();
}
void show_layout_01() {
setTitle("状态1");
state_entry_action();
LinearLayout layout_01 = new LinearLayout(this);
current_layout = layout_01;
layout_01.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams param =
new LinearLayout.LayoutParams(150, 200);
param.leftMargin = 1; param.topMargin = 3;
gv = new GraphicView(this);
layout_01.addView(gv,param);
setContentView(layout_01);
}
void goto_state_2() {
state_var_A = 2;
show_layout_02();
}
void show_layout_02() {
setTitle("状态2");
state_entry_action();
LinearLayout.LayoutParams para;
LinearLayout layout_02 = new LinearLayout(this);
current_layout = layout_02;
layout_02.setOrientation(LinearLayout.VERTICAL);
para = new LinearLayout.LayoutParams(150, 200);
para.leftMargin = 1; para.topMargin = 3;
layout_02.addView(gv,para);

LayoutInflater inflate = (LayoutInflater) getSystemService(


Context.LAYOUT_INFLATER_SERVICE);
LinearLayout in_yz_layout
= (LinearLayout)inflate.inflate(R.layout.yz, null);
Button y_btn = (Button)in_yz_layout.findViewById(R.id.y_btn);
Button z_btn = (Button)in_yz_layout.findViewById(R.id.z_btn);
y_btn.setId(101); y_btn.setOnClickListener(this);
z_btn.setId(102); z_btn.setOnClickListener(this);

para = new LinearLayout.LayoutParams(WC, WC);


para.leftMargin = 5; para.topMargin = 15;
layout_02.addView(in_yz_layout, para);
setContentView(layout_02);
}

118
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

void state_entry_action(){
if( current_layout != null)
current_layout.removeAllViewsInLayout();
}
@Override public boolean onKeyDown(int keyCode, KeyEvent msg) {
switch(keyCode) {
case KeyEvent.KEYCODE_A:
if(state_var_A == 1) this.goto_state_2();
break;
}
return true;
}
public void onClick(View v) {
switch(v.getId()) {
case 101:
if(state_var_A == 2) this.goto_state_1();
break;
case 102: finish(); break;
}}}

指令:gv = new GraphicView(this); 诞生一个GraphicView对象。当变换为


layout_01 时,会把gv加入layout_01里。一旦转换到layout_02 时,必须先调
用state_entry_action()函数,其内部的指令:
current_layout.removeAllViewsInLayout();

会从layout_01(即current_layout)里将gv移除,才能再加入到layout_02。如果在
goto_state_1()和goto_stste_2()里使用指令:
new GraphicView(this);
各自诞生一个GraphicView 对象,于是两个Layout并没有共享GraphicView对
象,就不必调用state_entry_action()函数了。此GraphicView类别的程序码如下:

// GraphicView.java
package com.misoo.ppxx;
// …….
public class GraphicView extends View{
private Paint paint= new Paint();
GraphicView(Context ctx) { super(ctx); }
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
paint.setColor(Color.GRAY);
paint.setStrokeWidth(3);
int line_x = 10; int line_y = 50;
canvas.drawLine(line_x, line_y, line_x+120, line_y, paint);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(2);
canvas.drawText("这是GraphicView绘图区", line_x, line_y + 50, paint);
int pos = 70;

119
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

paint.setColor(Color.RED);
canvas.drawRect(pos-5, line_y - 5, pos+5, line_y + 5, paint);
paint.setColor(Color.YELLOW);
canvas.drawRect(pos-3, line_y - 3, pos+3, line_y + 3, paint);
}}

在 layout_02 里,指令:
LinearLayout in_yz_layout
= (LinearLayout)inflate.inflate(R.layout.yz, null);

使用到 xy.xml 定义的布局(Layout)。其内容为:

// xy.xml (Layout)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button android:id="@+id/y_btn"
android:background="@drawable/x_jude"
android:layout_width="60dip"
android:layout_height="60dip"
android:layout_marginLeft="6dip"
android:padding="10dip"
android:text="@string/y" />
<Button android:id="@+id/z_btn"
android:background="@drawable/x_sky"
android:layout_width="60dip"
android:layout_height="60dip"
android:layout_marginLeft="12dip"
android:padding="10dip"
android:text="@string/z" />
</LinearLayout>

在此布局里,用到 strings.xml 所定义的子串常数,其内容为:

// strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, ac01</string>
<string name="app_name">sx02</string>
<string name="y">Y</string>
<string name="z">Z</string>
</resources>

以上是从观察画面布局的变化,来规划 UML 状态图(模型)。然后,基于


状态图而落实到 Android 应用程序码。

120
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

6.4 巢状的状态(Nested State)


状态可组成巢状(Nested)的结构。也就是,一个状态可以包含另一个状态
(称为小状态),或者包含多个小状态;这形成了一种巢状(Nested)的结构。
现在,就来绘制巢状(Nested)的状态图。

6.4.1 建立模型

兹选取<Create Diagram><Add Statemachine Diagram>来产生一张新的状态


图:

建立了一张空白的状态图之后,先拉出一个初期状态,如下图:

121
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

进入这个初期状态时,就执行 show_layout_01()函数,显示出一个画面布
局,例如:

上述状态图说明了,当用户按下<exit>按钮时,就结束整个状态机了。
然而,如果用户按下<play>或<stop>按钮时,可以进入另一个状态:播放 MP3
音乐。兹以 UML 状态图表示如下:

122
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

接下来,点选<State>图素:

点选了图素,然后移动鼠标到<状态_Play>范围内,并按下鼠标左键,就
在图表里出现一个小状态,如下:

123
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

在此图里,<状态_Play>做(Do)了一项工作:play MP3。现在,把这项工
做搬移到小状态里,并且在状态名称上加上一个状态编号,如下图:

124
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

此图说明了,如果用户按下<play>按钮时,就进入<状态 2_Play>,而且
立即进入<状态 3_Playing>去播放 MP3 音乐。
如果播放音乐完毕时,不想停留在<状态 3_Playing>,也不想离开<状态
2_Play>的话,可以在拉出一个新的小状态,如下图:

125
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

如果将巢状的状态图对应到 App 程序码时,可在图里贴上状态变量(State


Variable),以便在程序码能藉由状态变量。由于上图是两层的巢状状态图,必
须设置两个状态变量(如 state_var_A 或 state_var_B 等)来纪录目前的状态。例
如,当 App 处于<状态 3_Playing>时,应该:
z 将变数 state_var_A 值设为 2,
z 同时也将变量 state_var_B 值设为 3。

6.4.2 落实为 Android 应用程序码

上述模型,可以轻易落实为 Android 的应用程序码,如下:

// ac01.java
package com.misoo.pks01;
// ……..

126
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

public class ac extends Activity implements OnClickListener {


private int state_var_A = 0;
private int state_var_B = 0;
private Button PlayBtn, StopBtn, ExitBtn;
private MediaPlayer mPlayer = null;

public void onCreate(Bundle icicle) {


super.onCreate(icicle);
goto_state_1();
}

public void onClick(View v) {


switch (v.getId()) {
case 101:
if(state_var_A == 1) goto_state_2();
break;
case 102:
if(state_var_A == 2&&state_var_B == 4){
stopMP3(); goto_state_1();
}
break;
case 103: finish();
break;
}}

public void show_layout_1(){


LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(80, 50);
param.topMargin = 10;

PlayBtn = new Button(this);


PlayBtn.setId(101); PlayBtn.setText("play");
PlayBtn.setTextSize(12); PlayBtn.setTextColor(Color.BLACK);
PlayBtn.setBackgroundResource(R.drawable.heart);
PlayBtn.setOnClickListener(this);
layout.addView(PlayBtn, param);

StopBtn = new Button(this);


StopBtn.setId(102); StopBtn.setText("stop");
StopBtn.setTextSize(12); StopBtn.setTextColor(Color.BLACK);
StopBtn.setBackgroundResource(R.drawable.heart);
StopBtn.setOnClickListener(this);
layout.addView(StopBtn, param);

ExitBtn = new Button(this);


ExitBtn.setId(103); ExitBtn.setText("exit");
ExitBtn.setTextSize(12); ExitBtn.setTextColor(Color.BLACK);
ExitBtn.setBackgroundResource(R.drawable.heart);
ExitBtn.setOnClickListener(this);
layout.addView(ExitBtn, param);
setContentView(layout);

127
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

void goto_state_1(){
state_var_A = 1; state_var_B = 0;
show_layout_1();
}

void goto_state_2(){
state_var_A = 2;
if(mPlayer != null) return;
mPlayer = MediaPlayer.create(this, R.raw.mylover);
goto_state_3();
}

void goto_state_3(){
state_var_B = 3;
playMP3();
goto_state_4();
}

void goto_state_4(){
state_var_B = 4;
}

public void playMP3(){


try{ mPlayer.start();
}catch(Exception e){
e.printStackTrace();
}
}

public void stopMP3(){


try{ mPlayer.stop();
}catch(Exception e){
e.printStackTrace();
}
}
}

UML 状态图所能呈现的并不仅限于画面布局的变化而已,还可以应用到
各种类别(如 MediaPlayer 播放器的运转状态等)。不过,Android 的画面布局是
人人的眼睛可观察到的外观变化,所以本章就拿画面布局的建模来当范例,
可让大家易于认识 UML 状态图。

6.5 状态图 VS. 活动图


许多人常常把 UML 的状态(State)与活动(Action or Activity)混淆在一起
了。其实是很容易区别的。活动图比较偏于<描述>一个复杂系统的工作流程
(Workflow);而状态图比较偏于<驾驭>这个复杂系统的行为。由于绘制状态图
时,不要拘泥于:系统本质上有多少状态。而应该问的是:分为那些状态最

128
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

能帮助组织您的软件程序,以达到驾驭(即控制)系统之目标。欲达到控制之目
的,首先必须对可能之事件加以分组(Grouping)。因为特定事件会引发特定之
行为,所以一旦对事件分组了,也意味着对行为分组了。

使用 UML 状态图之目的就是:藉之将系统之可能事件(Event)及
行为(Behavior)加以分组,并且分而治之。

对事件能分组得愈棒就是愈好的状态图。因为设计出精致的状态图,能
让我们的控制程序更有效驾驭系统之行为,对外在环境的事件或讯息,做出
最适当之反应。兹以下图为例:

可以将上图视为<地鼠>的家,一个状态就是一间<房间>,于是此图里共
有 4 个房间。当它处于<状态 1>房间里,只有两个事件(即 onClick[PlayBtn]和

129
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012

onClick[ExitBtn])才能从房间里把它叫出来。同理,当它处于<状态 3>房间里,
只有两个事件(即 Finished 和 onClick[StopBtn])才会引发它离开该房间。还有,
当它处于<状态 4>房间里,就只有一个事件(即 onClick[StopBtn])会引发它离开
该房间,转移到<状态 1>房间。
请记得,不要把上图里的状态转移(Transition)箭头看成活动图里的流程
(Flow)箭头。否则,就很难弄清楚如何将上图的<状态 4_waiting>落实为 App
程序码了。你可以仔细参考上一小节(即 6.4.2 节)里的 Android 程序码范例。

~~ Continued ~~

130

You might also like