Professional Documents
Culture Documents
第 5章
30 分钟认识 UML 活动图
82
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
接着,点选<Activity Diagram>,就会出现一张空白的活动图,如下:
在空白的活动图上方,有一排活动图的元素(Element),简称「图素」,如下:
此列元素中的左边第 5 个就是「活动」(Action)图素。当你点选此图素(如
上图所示),然后移动鼠标(Cursor)到图表里的特定位置,并按下鼠标左键,就
在图表里出现一个活动图素,如下:
83
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
这让你输入活动的名称,例如取名为:bindService(绑定服务),如下:
这个图素代表一项动作或活动。如果对照到大家所熟悉的程序码:
84
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
一个活动图里,有许多项活动,而且有一定的顺序。所以必须标示整个
活动流程的起头。此时可以选取<InitialNode>图素,如下:
先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个启始(InitialNode)图示:
接着,使用<ControlFlow>图素,如下:
先点选了这个图素,接着将鼠标移动到启始(InitialNode)图示上,按住并拖拉
到 bindService 活动(图素),就出现:
85
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
先点选了这个图素,接着将鼠标移动到活动图的任何位置,并按键,就
出现了一个决策(Decision)图示。接着,拉出一条<ControlFlow>图素(即流程箭
头),如下:
在 Android 跨进程(IPC)的通讯架构里,绑定服务时,会返回调用(Callback)
到 ServiceConnection. onServiceConnected()函数,如下述常见的程序码:
86
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
如果顺利绑定成功了,就能继续执行目标活动:调用远程服务(Remote
Service);也就是有条件地调用这个目标活动。于是:
z 拉出一个新活动:callService(调用远距服务)。
z 拉出一条新流程箭头。
z 点选上述新流程箭头,呈现出它的属性表(Attribute Table),如下:
输入条件属性之后,得到下图:
87
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
如果绑定不成功,就不能执行 callService(调用远距服务)了,转而执行(也
是有条件的执行)其它活动或结束整个流程了。例如,可选取<ActivityFinal>
图素,如下:
先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个流程结束(ActivityFinal)图素,并且填上它的条件,如下:
88
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
这项 callService 活动就对应到大家所熟悉的程序码:
89
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
就可以更改名称了,例如改名为:“ClientDiagram”。活动图的颗粒大小(细
腻度)决定于架构师所采取的抽象层级(Abstract Level)。例如,针对上述活动图,
可以稍微降低抽象层级,增添更多详细的内容。也就是提高了活动图的细腻
度 ( 或 称 降 低 颗 粒 度 ) 。 现 在 , 来 增 添 一 个 新 的 活 动 图 。 请 点 选 <Activity
Diagram0><Create Diagram><Add Activity Diagram>,如下:
90
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
并替新活动图取名如下:
把刚才的活动图里的一部分复制(Copy)过来,如下图:
91
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个发送讯号(SendSignalAction)图素。然后,从 bindService 拉一条流程箭头到
发送讯号图素,如下图:
92
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
先点选了这个图素,接着将鼠标移动到活动图的任何位置,就出现了一
个讯号接收(AcceptEventAction)图素。如下图:
依据接收到的讯号,判断是否定成功。如果成功,就继续执行目标活动:
调用远程服务(Remote Service)。反之,如果绑定不成功,就结束整个流程了。
于是得到活动图,如下:
93
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
94
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
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”),得到下图:
99
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
然后,将一个活动图里的各项活动分派给适当的执行者。例如下图:
100
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
这活动图表达了:
z User(用户)做第 1 项活动:点选特定的 App(应用程序)。
z 接着,App 程序执行第 2 项活动:建立 UI 的布局(Layout),并显示
于画面上。执行这项活动的 Android 程序码如下:
101
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
102
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
慶祝新開班,搶鮮特惠價,請勿錯過良機
詳細說明,請看<Android 論壇技術教育中心>網頁:
http://www.android1.net/?Forum32/thread-22965-1-1
或詢問:
AndroidEdu520@gmail.com (劉智勇收)
103
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
第 6 章
30 分钟认识 UML 状态图
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 状态图素,就出现:
107
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
@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
109
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
填入事件名称:onDraw。然后,点选这条转移线,按右键,出现:
兹选取<Line Style><Curve>,图形就转变成为弧形的状态转移线了,如下:
110
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
先点选了这个图素,接着将鼠标移动到状态图的任何位置,就出现了一
个结束状态(FinalState)图素,并且填上它的条件,如下:
6.3 多个状态
自然界的事物,在其生命期中,状态常不断变化。有些过程是循环的,
有些则是分阶段的。例如,红绿灯之状态是周而复始地变换;其中<红灯亮
>、<绿灯亮>和<黄灯亮>分别是红绿灯对象的 3 种状态。
在 Android 里,一个 App 常常提供多个画面布局(Layout)。在 App 执行
期中,布局常不断变化,有些循环的周而复始地变换、有些则是分阶段的。
所以,布局的变换呈现出 App 状态的转移(Transition)。现在,就来绘制 UML
状态图来呈现 App 画面布局的变化(即状态转移)情境。
111
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
6.3.1 建立模型
112
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
113
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
114
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
此时,如果按下[Y]按钮(触发一个事件),就转移(返回)状态 01;如果按
下[Z]按钮,就结束了。现在,就在状态图上,来呈现上述的事件和状态转移
情境。首先,从状态 01 拉一条状态转移线到状态 02;并在其属性表里输入事
件和条件名称,如下:
115
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
调整一下图形,看起来更美观一些。如下:
116
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
// 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
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;
}}}
会从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)
<?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
<?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>
120
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
6.4.1 建立模型
建立了一张空白的状态图之后,先拉出一个初期状态,如下图:
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
// ac01.java
package com.misoo.pks01;
// ……..
126
Android 架構師手冊_建模與圖形思考 By 高煥堂 2012
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;
}
UML 状态图所能呈现的并不仅限于画面布局的变化而已,还可以应用到
各种类别(如 MediaPlayer 播放器的运转状态等)。不过,Android 的画面布局是
人人的眼睛可观察到的外观变化,所以本章就拿画面布局的建模来当范例,
可让大家易于认识 UML 状态图。
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