Android简明开发教程十九:线程 Bezier曲线
Android中使用线程Thread的方法和Java SE相同。和大多数OS系统一样,Android中也有称为UI Thread的主线程。UI Thread 主要用来给相应的Widget分发消息,包括绘制(Drawing)事件。UI Thread 也是用来处理用户交互事件的线程。比如:如果你按下屏幕上某个按钮,UI 线程则将Touch 事件通知对应的控件(Widgets),Widget 则将其状态设置成“按下”,并把“重绘”(Invalidate)事件发到Event Queue中去。 UI线程从Event Queue中读取事件后通知Widgets重画自身。
如果你的应用设计不好的话, UI线程的这种单线程模式就会导致非常差的用户响应性能。特别是你将一些费时的操作如网络访问或数据库访问也放在UI线程中,这些操作会造成用户界面无反应,最糟糕的是,如果UI线程阻塞超过几秒(5秒),著名的ANR对话框就会出现:
所以在设计应用时,需要把一些费时的任务使用单独的工作线程来运行避免阻塞UI线程,但是如果在工作线程中想更新UI线程的话,不能直接在工作线程中更新UI,这是因为UI线程不是“Thread Safe”。因此所有UI相关的操作一般必须在UI Thread中进行。
Android OS提供了多种方法可以用在非UI线程访问UI线程。
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
- Handler
Bezier 示例动态显示Bezier曲线,使用了Activity.runOnUiThread 来更新屏幕,完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
public class Bezier extends Graphics2DActivity implements OnClickListener,Runnable{ /** * The animation thread. */ private Thread thread; private volatile boolean stopThread= false ; private boolean stopOrNot= false ; boolean drawn; /** * The random number generator. */ static java.util.Random random = new java.util.Random(); /** * The animated path */ Path path = new Path(); /** * Red brush used to fill the path. */ SolidBrush brush = new SolidBrush(Color.RED); private static final int NUMPTS = 6 ; private int animpts[] = new int [NUMPTS * 2 ]; private int deltas[] = new int [NUMPTS * 2 ]; long startt, endt; private Button btnOptions; @Override protected void drawImage() { drawDemo( 100 , 100 ); } public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.beziers); graphic2dView = (GuidebeeGraphics2DView) findViewById(R.id.graphics2dview); btnOptions = (Button) findViewById(R.id.btnStopStart); btnOptions.setOnClickListener( this ); reset( 100 , 100 ); if (thread == null ) { thread = new Thread( this ); thread.start(); } } @Override public void onClick(View view) { if (!stopOrNot){ btnOptions.setText( "Start" ); stopThread= true ; } else { stopThread= false ; btnOptions.setText( "Stop" ); if (thread == null ) { thread = new Thread( this ); thread.start(); } } stopOrNot=!stopOrNot; } /** * Generates new points for the path. */ private void animate( int [] pts, int [] deltas, int i, int limit) { int newpt = pts[i] + deltas[i]; if (newpt <= 0 ) { newpt = -newpt; deltas[i] = (random.nextInt() & 0x00000003 ) + 2 ; } else if (newpt >= limit) { newpt = 2 * limit - newpt; deltas[i] = -((random.nextInt() & 0x00000003 ) + 2 ); } pts[i] = newpt; } /** * Resets the animation data. */ private void reset( int w, int h) { for ( int i = 0 ; i < animpts.length; i += 2 ) { animpts[i + 0 ] = (random.nextInt() & 0x00000003 ) * w / 2 ; animpts[i + 1 ] = (random.nextInt() & 0x00000003 ) * h / 2 ; deltas[i + 0 ] = (random.nextInt() & 0x00000003 ) * 6 + 4 ; deltas[i + 1 ] = (random.nextInt() & 0x00000003 ) * 6 + 4 ; if (animpts[i + 0 ] > w / 2 ) { deltas[i + 0 ] = -deltas[i + 0 ]; } if (animpts[i + 1 ] > h / 2 ) { deltas[i + 1 ] = -deltas[i + 1 ]; } } } final Runnable updateCanvas = new Runnable() { public void run() { int offsetX = (graphic2dView.getWidth() - SharedGraphics2DInstance.CANVAS_WIDTH) / 2 ; int offsetY = (graphic2dView.getHeight() - SharedGraphics2DInstance.CANVAS_HEIGHT) / 2 ; graphic2dView.invalidate(offsetX,offsetY, offsetX+ 100 ,offsetY+ 100 ); } }; /** * Sets the points of the path and draws and fills the path. */ private void drawDemo( int w, int h) { for ( int i = 0 ; i < animpts.length; i += 2 ) { animate(animpts, deltas, i + 0 , w); animate(animpts, deltas, i + 1 , h); } //Generates the new pata data. path.reset(); int [] ctrlpts = animpts; int len = ctrlpts.length; int prevx = ctrlpts[len - 2 ]; int prevy = ctrlpts[len - 1 ]; int curx = ctrlpts[ 0 ]; int cury = ctrlpts[ 1 ]; int midx = (curx + prevx) / 2 ; int midy = (cury + prevy) / 2 ; path.moveTo(midx, midy); for ( int i = 2 ; i <= ctrlpts.length; i += 2 ) { int x1 = (curx + midx) / 2 ; int y1 = (cury + midy) / 2 ; prevx = curx; prevy = cury; if (i < ctrlpts.length) { curx = ctrlpts[i + 0 ]; cury = ctrlpts[i + 1 ]; } else { curx = ctrlpts[ 0 ]; cury = ctrlpts[ 1 ]; } midx = (curx + prevx) / 2 ; midy = (cury + prevy) / 2 ; int x2 = (prevx + midx) / 2 ; int y2 = (prevy + midy) / 2 ; path.curveTo(x1, y1, x2, y2, midx, midy); } path.closePath(); // clear the clipRect area before production graphics2D.clear(Color.WHITE); graphics2D.fill(brush, path); this .runOnUiThread(updateCanvas); } public void run() { Thread me = Thread.currentThread(); if (!drawn) { synchronized ( this ) { graphics2D.clear(Color.WHITE); graphics2D.fill(brush, path); graphic2dView.refreshCanvas(); drawn = true ; } } while (thread == me && !stopThread) { drawDemo( 100 , 100 ); } thread = null ; } } |
除了上述的方法外,Android还提供了AsyncTask类以简化工作线程与UI线程之间的通信。这里不详述。此外,上面Bezier曲线动画在屏幕上显示时有闪烁的现象,这是动态显示图像的一个常见问题,后面将专门讨论。