目录
5、 线程之间的通信架构
认识Looper与Handler对象
主线程丢信息给自己
子线程丢信息给主线程
替子线程诞生Looper与MQ
5、 线程之间的通信架构
认识Looper与Handler对象
- 当主线程诞生时,就会去执行一个代码循环(Looper),以便持续监视它的信息队列(Message Queue简称MQ)。当UI事件发生了,通常会立即丢一个信息(Message)到MQ,此时主线程就立即从MQ里面取出该信息,并且处理之。
- 例如,用户在UI画面上按下一个Button按钮时, UI事件发生了,就会丢一些信息到MQ里,其中包括onClick信息,于是,主线程会及时从MQ里取出onClick信息,然后调用Activity的onClick()函数去处理之。处理完毕之后,主线程又返回去继续执行信息循环,继续监视它的MQ,一直循环下去,直到主线程的生命周期的终了。
- 通常是进程被删除时,主线程才会被删除
- Android里有一个Looper类别,其对象里含有一个信息循环(Message Loop)。也就是说,一个主线程有它自己专属的Looper对象,此线程诞生时,就会执行此对象里的信息循环。此外,一个主线程还会有其专属的MQ信息结构。如下图所示:
- 由于主线程会持续监视MQ的动态,所以在程序的任何函数,只要将信息(以Message类别的对象表示之)丢入主线程的MQ里,就能与主线程沟通了。
- 在Android里,也定义了一个Handler类别,在程序的任何函数里,可以诞生Handler对象来将Message对象丢入MQ里,而与主线程进行沟通。
- 在Android的预设情况下,主线程诞生时,就会拥有自己的Looper对象和MQ(即Message Queue)数据结构
- 然而,主线程诞生子线程时,于预设情形下,子线程并不具有自己的Looper对象和MQ。由于没有Looper对象,就没有信息回圈(Message Loop),一旦工作完毕了,此子线程就结束了。
- 既然没有Looper对象也没有MQ,也就不能接受外来的Message对象了。则别的线程就无法透过MQ来传递信息给它了。
- 那么,如果别的线程(如主线程)需要与子线程通讯时,该如何呢? 答案是:替它诞生一个Looper对象和一个MQ就行了。
主线程丢信息给自己
- Handler是Android框架所提供的基类,用来协助将信息丢到线程的MQ里。
- 兹撰写个范例程序Rx01,来将信息丢到主线程的MQ里,如下:
// ac01.java
//……..
public class ac01 extends Activity implements OnClickListener {
private Handler h;
public void onCreate(Bundle icicle) {
//……..
h = new Handler(){
public void handleMessage(Message msg) {
setTitle((String)msg.obj);
}
};
}
public void onClick(View v) {
switch (v.getId()) {
case 101:
h.removeMessages(0);
Message m = h.obtainMessage(1, 1, 1, "this is my message.");
h.sendMessage(m); // 将Message送入MQ里
break;
case 102:
finish(); break;
}}}
- 当主线程执行到onCreate()函数里的指令:
h = new Handler(){
// ………
}
- 就诞生一个Handler对象,可透过它来把信息丢到MQ里。
- 当执行到onClick()函数里的指令:
//………………
h.removeMessages(0);
Message m = h.obtainMessage(1, 1, 1,"this is my message.");
h.sendMessage(m); - 就将Message对象送入MQ里。
- 当主线程返回到信息回圈时,看到MQ里有个Message对象,就取出来,并执行handleMessage()函数,将Message对象里所含的字符串显示于画面上。
子线程丢信息给主线程
- 子线程也可以诞生Handler对象来将Message对象丢到主线程的MQ里,又能与主线程通讯了。兹撰写个范例程序Rx02如下:
// ac01.java
// ……….
public class ac01 extends Activity implements OnClickListener {
private Handler h;
private Timer timer = new Timer();
private int k=0;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//………
h = new Handler(){
public void handleMessage(Message msg) {
setTitle((String)msg.obj);
}
};
}
public void onClick(View v) {
switch (v.getId()) {
case 101:
TimerTask task = new TimerTask(){
@Override
public void run() {
h.removeMessages(0);
Message m = h.obtainMessage(1, 1, 1,
Thread.currentThread().getName() + " :"+String.valueOf(k++));
h.sendMessage(m);
}
};
timer.schedule(task, 500, 1500); break;
case 102:
finish(); break;
}
}
}
- 就启动一个Timer的线程,名字叫:” Timer-0” ;然后,由它来定时重复执行TimerTask::run()函数,就不断将Message对象丢到主线程的MQ里。此时主线程会持续处理MQ里的Message对象,将其内的字符串显示于画面上。
- 于是,子执行透过Handler对象而将信息丢到主线程的MQ,进而成功地将信息显示于画面上。
替子线程诞生Looper与MQ
- 如果别的线程(如主线程)需要与子线程通讯时,该如何呢? 答案是:替它诞生一个Looper对象和一个MQ就行了。兹撰写个范例程序Rx03如下:
// ac01.java //…… public class ac01 extends Activity implements OnClickListener { private Thread t; private Handler h; private String str; public void onCreate(Bundle icicle) { //…….. t = new Thread(new Task()); t.start(); } public void onClick(View v) { switch(v.getId()){ case 101: Message m = h.obtainMessage(1, 33, 1, null); h.sendMessage(m); break; case 102: setTitle(str); break; case 103: h.getLooper().quit(); finish(); break; } } class Task implements Runnable { public void run() { Looper.prepare(); h = new Handler(){ public void handleMessage(Message msg) { str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1); } }; Looper.loop(); } } }
-
Step-1: 一开始,由主线程执行onCreate()函数。 主线程继续执行到指令:
t = new Thread(new Task());
t.start(); -
就诞生一个子线程,并启动子线程去执行Task的run()函数,而主线程则返回到信息回圈,并持续监视MQ的动态了。
-
Step-2: 此时,子线程执行到run()函数里的指令:
Looper.prepare(); -
就诞生一个Looper对象,准备好一个信息回圈(Message Loop) 和MQ数据结构
-
继续执行到指令:
h = new Handler(){
//…..
} -
就诞生一个Handler对象,可协助将信息丢到子线程的MQ上。
-
接着继续执行到指令:
Looper.loop(); -
也就开始执行信息回圈,并持续监视子线程的MQ动态了。
-
Step-3: 当用户按下UI按钮时, UI事件发生了, Android将此UI事件的信息丢到主线程的MQ,主线程就执行onClick()函数里的指令:
Message m = h.obtainMessage(1, 33, 1, null);
h.sendMessage(m); -
主线程藉由h将该Message对象(内含整数值33)丢入子线程的MQ里面,然后主线程返回到它的信息循环(Looper),等待UI画面的事件或信息。
-
Step-4: 子线程看到MQ有了信息,就会取出来,调用handleMessage()函数:
public void handleMessage(Message msg) {
str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);
} -
来处理之,就设定的str的值。请留意,此刻子线程因为不能碰触UI控件,所以无法直接将str值显示于画面上。
-
Step-5: 当用户按下<show value>按钮时,主线程就执行onClick()函数,将str值显示于画面上。 于是,实现主线程与子线程之间的双向沟通了。