Android中的线程
在Android平台中多线程应用很广泛,在UI更新、游戏开发和耗时处理(网络通信等)等方面都需要多线程。Android线程涉及的技术有:Handler;Message;MessageQueue;Looper;HandlerThread。
Android线程应用中的问题与分析
为了介绍这些概念,我们把计时器的案例移植到Android系统上,按照在Frame方式修改之后的代码清单8-4,完整代码请参考chapter8_3工程中 chapter8_3代码部分。
【代码清单8-4】
public
class chapter8_3 extends Activity {
private
String
TAG
=
"
chapter8_3
"
;
private
Button btnEnd;
private
TextView labelTimer;
private
Thread clockThread;
private
boolean
isRunning
=
true
;
private
int
timer
=
0
;
@Override
public
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnEnd
=
(Button) findViewById(R.id.btnEnd);
btnEnd.setOnClickListener(
new
OnClickListener() {
@Override
public
void onClick(View v) {
isRunning
=
false
;
}
});
labelTimer
=
(TextView) findViewById(R.id.labelTimer);
/*
线程体是Clock对象本身,线程名字为
"
Clock
"
*/
clockThread
=
new
Thread(
new
Runnable() {
@Override
public
void run() {
while
(isRunning) {
try {
Thread.currentThread().sleep(
1000
);
timer
++
;
labelTimer.setText(
"
逝去了
"
+
timer
+
"
秒
"
);
Log
.d(TAG,
"
lost time
"
+
timer
);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
clockThread.start();
/*
启动线程
*/
}
}
程序打包运行结果出现了异常,如图8-8所示。
▲图8-8 运行结果异常图
我们打开LogCat窗口,出错日志信息如图8-9所示。
▲图8-9 出错日志
系统抛出的异常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI处理必须由创建它的线程更新,而不能在其他线程中更新。上面的错误原因就在于此。
现在分析一下上面的案例,在上面的程序中有两个线程:一个主线程和一个子线程,它们的职责如图8-10所示。
由于labelTimer是一个UI控件,它是在主线程中创建的,但是它却在子线程中被更新了,更新操作在clockThread线程的run()方法中实现,代码如下:
▲图8-10 线程职责
/*
线程体是Clock对象本身,线程名字为
"
Clock
"
*/
clockThread
=
new
Thread(
new
Runnable() {
@Override
public
void run() {
while
(isRunning) {
try {
Thread.currentThread().sleep(
1000
);
timer
++
;
labelTimer.setText(
"
逝去了
"
+
timer
+
"
秒
"
);
Log
.d(TAG,
"
lost time
"
+
timer
);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
这样的处理违背了Android多线程编程规则,系统会抛出异常“Only the original thread that created a view hierarchy can touch its views”。
要解决这个问题,就要明确主线程和子线程的职责。主线程的职责是创建、显示和更新UI控件、处理UI事件、启动子线程、停止子线程;子线程的职责是计算逝去的时间和向主线程发出更新UI消息,而不是直接更新UI。它们的职责如图8-11所示。
▲图8-11 线程职责
主线程的职责是显示UI控件、处理UI事件、启动子线程、停止子线程和更新UI,子线程的职责是计算逝去的时间和向主线程发出更新UI消息。但是新的问题又出现了:子线程和主线程如何发送消息、如何通信呢?
在Android中,线程有两个对象—消息(Message)和消息队列(MessageQueue)可以实现线程间的通信。下面再看看修改之后的代码清单8-5,完整代码请参考chapter8_4工程中chapter8_4代码部分。
【代码清单8-5】
public
class chapter8_4 extends Activity {
private
String
TAG
=
"
chapter8_3
"
;
private
Button btnEnd;
private
TextView labelTimer;
private
Thread clockThread;
private
boolean
isRunning
=
true
;
private
Handler handler;
@Override
public
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnEnd
=
(Button) findViewById(R.id.btnEnd);
btnEnd.setOnClickListener(
new
OnClickListener() {
@Override
public
void onClick(View v) {
isRunning
=
false
;
}
});
handler
=
new
Handler() {
@Override
public
void handleMessage(Message msg) {
switch (msg.what) {
case
0
:
labelTimer.setText(
"
逝去了
"
+
msg.obj
+
"
秒
"
);
}
}
};
labelTimer
=
(TextView) findViewById(R.id.labelTimer);
/*
线程体是Clock对象本身,线程名字为
"
Clock
"
*/
clockThread
=
new
Thread(
new
Runnable() {
@Override
public
void run() {
int
timer
=
0
;
while
(isRunning) {
try {
Thread.currentThread().sleep(
1000
);
timer
++
;
/*
labelTimer.setText(
"
逝去了
"
+
timer
+
"
秒
"
);
*/
Message msg
=
new
Message();
msg.obj
=
timer
;
msg.what
=
0
;
handler.sendMessage(msg);
Log
.d(TAG,
"
lost time
"
+
timer
);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
clockThread.start();
/*
启动线程
*/
}
有的时候为了将Android代码变得更加紧凑,把线程的创建和启动编写在一条语句中,如下面chapter8_5的代码片段。代码清单8-6所示,完整代码请参考chapter8_5工程中 chapter8_5代码部分。