活用Android的Message Queue

活用Android的Message Queue(1/3)

1. Message Queue的角色

l 在你的Android程式裡,新誕生一個線程,或稱執行緒(Thread)時,並不會自動建立其Message Loop。

l Android裡並沒有Global的Message Queue資料結構,例如,不同APK裡的物件不能透過Massage Queue來交換訊息(Message)。

l 一個線程可以誕生一個Looper之物件,由它來管理此線程裡的Message Queue。

l 你可以誕生Handler之物件來與Looper溝通,以便push新訊息到Message Queue裡;或者接收Looper(從Message Queue取出)所送來的訊息。

l 線程A的Handler物件參考可以傳遞給別的線程,讓別的線程B或C等能送訊息來給線程A(存於A的Message Queue裡)。

l 線程A的Message Queue裡的訊息,只有線程A所屬的物件可以處理之。

l 使用Looper.myLooper可以取得目前線程的Looper物件參考值。

l 使用mHandler = new EevntHandler(Looper.myLooper()); 可誕生用來處理目前線程的Handler物件;其中,EevntHandler是Handler的子類別。

l 使用mHandler = new EevntHandler(Looper.getMainLooper()); 可誕生用來處理main線程的Handler物件;其中,EevntHandler是Handler的子類別。

2. 範例之一:Looper物件之角色

Looper類別用來管理特定線程內物件之間的訊息交換(Message Exchange)。你的應用程式可以誕生許多個線程,或稱執行緒(Thread)。而一個線程可以誕生許多個物件,這些物件之間常常需要互相交換訊息。如果有這種需要,您可以替線程誕生一個Looper類別之物件,來擔任訊息交換的管理工作。Looper物件會建立一個MessageQueue資料結構來存放各物件傳來的訊息(包括UI事件或System事件等)。如下圖:

clip_image001

每一個線程(Thread,或稱「執行緒」)裡可含有一個Looper物件以及一個MessageQueue資料結構。在你的應用程式裡,可以定義Handler的子類別來接收Looper所送出的訊息。

//----- Looper_01範例 -----

package com.misoo.kx04;

import android.app.Activity;

import android.graphics.Color;

import android.os.Bundle;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

public TextView tv;

private EventHandler mHandler;

private Button btn, btn2, btn3;

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.setBackgroundResource(R.drawable.heart);

btn.setText("test looper");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(100,50);

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new Button(this);

btn2.setId(102);

btn2.setBackgroundResource(R.drawable.ok_blue);

btn2.setText("exit");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

}

public void onClick(View v) {

switch(v.getId()){

case 101:

Looper looper;

looper = Looper.myLooper();

mHandler = new EventHandler(looper);

mHandler.removeMessages(0);

// 清除整個MessageQueue裡的事件,確保不會通知到別人

String obj = "This my message!";

Message m = mHandler.obtainMessage(1, 1, 1, obj);

// 組裝成一個Message物件

mHandler.sendMessage(m);

// 將Message物件送入MessageQueue裡

break;

case 102:

finish();

break;

}

}

//------------------------------------------------------

class EventHandler extends Handler

{

public EventHandler(Looper looper) {

super(looper);

}

@Override

public void handleMessage(Message msg) {

tv.setText((String)msg.obj);

}

}

}

//-------------------------------------------------------

說明:

此程式啟動時,目前線程(即主線程, main thread)已誕生了一個Looper物件,並且有了一個MessageQueue資料結構。

指令:looper = Looper.myLooper();

就呼叫Looper類別的靜態myLooper()函數,以取得目前線程裡的Looper物件之參考值。

指令:mHandler = new EventHandler(looper);

誕生一個EventHandler之物件來與Looper溝通。Activity等物件可以藉由EventHandler物件來將訊息傳給Looper,然後放入MessageQueue裡;EventHandler物件也扮演Listener的角色,可接收Looper物件所送來的訊息。如下圖:

clip_image002

指令:Message m = mHandler.obtainMessage(1, 1, 1, obj);

先誕生一個Message物件,並將資料存入次物件裡。

指令:mHandler.sendMessage(m);

就透過mHandler物件而將訊息m傳給Looper,然後放入MessageQueue裡。

此時,Looper物件看到MessageQueue裡有訊息m,就將它廣播出去,mHandler物件接到此訊息時,會呼叫其handleMessage()函數來處理之,於是輸出"This my message!"於畫面上,如下:

clip_image003

3. 範例之二:由別的線程送訊息到主線程的Message Queue

//----- Looper_02範例 -----

package com.misoo.kx04;

import android.app.Activity;

import android.graphics.Color;

import android.os.Bundle;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

public TextView tv;

private myThread t;

private Button btn, btn2, btn3;

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.setBackgroundResource(R.drawable.heart);

btn.setText("test looper");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(100,50);

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new Button(this);

btn2.setId(102);

btn2.setBackgroundResource(R.drawable.ok_blue);

btn2.setText("exit");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

}

public void onClick(View v) {

switch(v.getId()){

case 101:

t = new myThread();

t.start();

break;

case 102:

finish();

break;

}

}

//------------------------------------------------------

class EHandler extends Handler {

public EHandler(Looper looper) {

super(looper);

}

@Override

public void handleMessage(Message msg) {

tv.setText((String)msg.obj);

}

}

//------------------------------------------------------

class myThread extends Thread{

private EHandler mHandler;

public void run() {

Looper myLooper, mainLooper;

myLooper = Looper.myLooper();

mainLooper = Looper.getMainLooper();

String obj;

if(myLooper == null){

mHandler = new EHandler(mainLooper);

obj = "current thread has no looper!";

}

else {

mHandler = new EHandler(myLooper);

obj = "This is from current thread.";

}

mHandler.removeMessages(0);

Message m = mHandler.obtainMessage(1, 1, 1, obj);

mHandler.sendMessage(m);

}

}

}

//-------------------------------------------------------

Android會自動替主線程建立Message Queue。在這個子線程裡並沒有建立Message Queue。所以,myLooper值為null,而mainLooper則指向主線程裡的Looper物件。於是,執行到指令:

mHandler = new EHandler(mainLooper); 此mHandler屬於主線程。

指令:mHandler.sendMessage(m);

就將m訊息存入到主線程的Message Queue裡。mainLooper看到Message Queue裡有訊息,就會處理之,於是由主線程執行到mHandler的handleMessage()函數來處理訊息。此程式輸出畫面為:

clip_image004

4. 結語:

l Message Loop的用途很廣。請你參閱高煥堂所寫的Android系列書籍,尤其是其中的第4本書:<> 。

l 以上只是本文的前半段而已,請你繼續閱讀後半段。

---- END ---

1. 複習Message Queue的角色

在上一篇裡,介紹了Android的Thread、Looper、Message Queue和Handler四者間之關係。

茲先複習如下:

l UI thread 通常就是main thread,而Android啟動程式時(即誕生Process時)會替它建立一個Message Queue。

l 當然需要一個Looper之物件,來管理該Message Queue。

l 我們可以誕生Handler之物件來push新訊息到Message Queue裡;或者接收Looper(從Message Queue取出)所送來的訊息。

l 線程A的Handler物件參考可以傳遞給別的線程,讓別的線程B或C等能送訊息來給線程A(存於A的Message Queue裡)。

l 線程A的Message Queue裡的訊息,只有線程A所屬的物件可以處理之。

了解了四者間之關係後,在本篇裡,就能來思考如何讓主線程與子線程之間互相溝通了。包括,子線程push訊息到主線程的Message Queue裡,並觸發主線程去執行某項工作(即執行某個函數)。

2. 由別的線程送訊息到主線程的Message Queue(續)

在上一篇文章裡,使用如下程式片段:

// class ac01 extends Activity {

// ………

public void onClick(View v) {

switch(v.getId()){

case 101:

t = new myThread();

t.start();

break;

case 102:

finish();

break;

}

}

//------------------------------------------------------

class EHandler extends Handler {

public EHandler(Looper looper) {

super(looper);

}

@Override

public void handleMessage(Message msg) {

tv.setText((String)msg.obj);

}

}

//------------------------------------------------------

class myThread extends Thread{

private EHandler mHandler;

public void run() {

Looper myLooper, mainLooper;

myLooper = Looper.myLooper();

mainLooper = Looper.getMainLooper();

String obj;

if(myLooper == null){

mHandler = new EHandler(mainLooper);

obj = "current thread has no looper!";

}

else {

mHandler = new EHandler(myLooper);

obj = "This is from current thread.";

}

mHandler.removeMessages(0);

Message m = mHandler.obtainMessage(1, 1, 1, obj);

mHandler.sendMessage(m);

}

}

}

這個mHandler定義於myThread類別裡,而且由子線程執行指令:

mHandler = new EHandler(mainLooper);

來誕生EHandler之物件;但是這個mHandler確是屬於main線程的(用來存取主線程的MessageQueue),所以指令:

mHandler.sendMessage(m);

是將m丟到主線程的MessageQueue裡。

此外,我們也可以將mHandler定義於ac01類別裡。如下程式範例:

//----- Looper_03範例 -----

package com.misoo.kx04;

import android.app.Activity;

import android.content.Context;

import android.graphics.Color;

import android.os.Bundle;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

public TextView tv;

private myThread t;

private Button btn, btn2;

EventHandler h;

Context ctx;

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

ctx = this;

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);

btn.setId(101);

btn.setBackgroundResource(R.drawable.heart);

btn.setText("test looper");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(100,50);

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new Button(this);

btn2.setId(102);

btn2.setBackgroundResource(R.drawable.ok_blue);

btn2.setText("exit");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

}

public void onClick(View v) {

switch(v.getId()){

case 101:

h = new EventHandler(Looper.myLooper());

t = new myThread();

t.start();

break;

case 102:

finish();

break;

}

}

//------------------------------------------------

public class EventHandler extends Handler {

public EventHandler(Looper looper) {

super(looper);

}

@Override

public void handleMessage(Message msg) {

((Activity)ctx).setTitle((String)msg.obj);

}

}

//------------------------------------------------------

class myThread extends Thread{

public void run() {

String obj = "from myThread";

Message m = h.obtainMessage(1, 1, 1, obj);

h.sendMessage(m);

}

}

}

//------------------------------------------------------

指令:

h = new EventHandler(Looper.myLooper());

此h是屬於main線程的(用來存取主線程的MessageQueue)。在myThread類別裡的指令:

h.sendMessage(m);

雖然是由子線程執行該指令,還是將m丟到主線程的MessageQueue裡。於是,子線程所執行的run()函數,就順利將m丟給主線程(的Message Queue),並觸發了主線程去執行handleMessage()函數了。顯示出畫面如下:

圖1

上述的指令:

myLooper = Looper.myLooper();

mainLooper = Looper.getMainLooper();

………

mHandler = new EHandler(mainLooper);

………

mHandler = new EHandler(myLooper);

………

都明顯地指明mHandler是負責存取哪一個線程的Message Queue。不過,有時候並不需要特別指明。例如上述的onClick()函數和EventHandler類別,可改寫為:

//----- Looper_03aa 範例 -----

// class ac01 extends Activity {

// ………

public void onClick(View v) {

switch(v.getId()){

case 101:

h = new EventHandler();

t = new myThread();

t.start();

break;

case 102:

finish();

break;

}

}

//------------------------------------------------

public class EventHandler extends Handler {

@Override

public void handleMessage(Message msg) {

((Activity)ctx).setTitle((String)msg.obj);

}

}

//------------------------------------------------------

class myThread extends Thread{

public void run() {

String obj = "from myThread";

Message m = h.obtainMessage(1, 1, 1, obj);

h.sendMessage(m);

}

}

}

指令:h = new EventHandler();

就等於:h = new EventHandler(Looper.myLooper());

它建立了目前線程(Current Thread)的EventHandler物件。於此,是由main線程執行此指令的,所以此EventHandler物件是用來存取main線程的Message Queue。

上述程式將handleMessage()定義於EventHandler類別內,也可以直接定義於ac01類別之內。於是上述程式,也相當於:

//----- Looper_03bb 範例 -----

// class ac01 extends Activity {

// ………

public void onClick(View v) {

switch(v.getId()){

case 101:

h = new Handler(){

public void handleMessage(Message msg) {

((Activity)ctx).setTitle((String)msg.obj);

}};

t = new myThread();

t.start();

break;

case 102:

finish();

break;

}

}

//------------------------------------------------------

class myThread extends Thread{

public void run() {

String obj = "from myThread...";

Message m = h.obtainMessage(1, 1, 1, obj);

h.sendMessage(m);

}

}

}

其執行結果是一樣的。

3. 由主線程送訊息給子線程(續)

上述範例裡,是由子線程丟訊息給主線程。本節將介紹如何從主線程丟訊息給子線程。其方法是:當子線程執行run()函數時,就誕生一個子線程的Handler物件。之後,當主線程執行ac01::onClick()函數時,就藉由此Handler物件參考而push訊息給子線程。例如下述範例:

//----- Looper_04範例 -----

package com.misoo.kx04;

import android.app.Activity;

import android.content.Context;

import android.graphics.Color;

import android.os.Bundle;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

public TextView tv;

private myThread t;

private Button btn, btn2;

private Handler h;

private Context ctx;

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

ctx = this;

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);

btn.setId(101);

btn.setBackgroundResource(R.drawable.heart);

btn.setText("test looper");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(100,50);

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new Button(this);

btn2.setId(102);

btn2.setBackgroundResource(R.drawable.ok_blue);

btn2.setText("exit");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

//------------------------

t = new myThread();

t.start();

}

public void onClick(View v) {

switch(v.getId()){

case 101:

String obj = "mainThread";

Message m = h.obtainMessage(1, 1, 1, obj);

h.sendMessage(m);

break;

case 102:

h.getLooper().quit();

finish();

break;

}

}

//------------------------------------------------

public class EventHandler extends Handler {

public EventHandler(Looper looper) {

super(looper);

}

@Override

public void handleMessage(Message msg) {

((Activity)ctx).setTitle((String)msg.obj);

}

}

//------------------------------------------------

class myThread extends Thread{

public void run() {

Looper.prepare();

h = new Handler(){

public void handleMessage(Message msg) {

EventHandler ha = new

EventHandler(Looper.getMainLooper());

String obj = (String)msg.obj + ", myThread";

Message m = ha.obtainMessage(1, 1, 1, obj);

ha.sendMessage(m);

}

};

Looper.loop();

}

}

}

當子線程執行run()函數時,誕生一個主線程的EventHandler物件,並且藉之而push訊息給主線程了。就進行了兩個線程之間的互相交換訊息,也是兩個函數或物件間之交換訊息。此程式輸出畫面為:

上述範例定義了Thread的子類別。也可以將子線程包含到Runnable類別裡,如下:

//----- Looper_04aa範例 -----

package com.misoo.kx04;

import android.app.Activity;

import android.content.Context;

import android.graphics.Color;

import android.os.Bundle;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

import android.widget.TextView;

public class ac01 extends Activity implements OnClickListener {

private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

private final int FP = LinearLayout.LayoutParams.FILL_PARENT;

public TextView tv;

private RR r;

private Button btn, btn2;

private Handler h;

private Context ctx;

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

ctx = this;

LinearLayout layout = new LinearLayout(this);

layout.setOrientation(LinearLayout.VERTICAL);

btn = new Button(this);

btn.setId(101);

btn.setBackgroundResource(R.drawable.heart);

btn.setText("test looper");

btn.setOnClickListener(this);

LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(100,50);

param.topMargin = 10;

layout.addView(btn, param);

btn2 = new Button(this);

btn2.setId(102);

btn2.setBackgroundResource(R.drawable.ok_blue);

btn2.setText("exit");

btn2.setOnClickListener(this);

layout.addView(btn2, param);

tv = new TextView(this);

tv.setTextColor(Color.WHITE);

tv.setText("");

LinearLayout.LayoutParams param2 =

new LinearLayout.LayoutParams(FP, WC);

param2.topMargin = 10;

layout.addView(tv, param2);

setContentView(layout);

//------------------------

r = new RR();

}

public void onClick(View v) {

switch(v.getId()){

case 101:

String obj = "mainThread";

Message m = h.obtainMessage(1, 1, 1, obj);

h.sendMessage(m);

break;

case 102:

h.getLooper().quit();

finish();

break;

}

}

//------------------------------------------------

public class EventHandler extends Handler {

public EventHandler(Looper looper) {

super(looper);

}

@Override

public void handleMessage(Message msg) {

((Activity)ctx).setTitle((String)msg.obj);

}

}

//------------------------------------------------

public class RR implements Runnable {

public RR() {

Thread aThread = new Thread(null, this, "RR");

aThread.start();

}

public void run() {

Looper.prepare();

h = new Handler(){

public void handleMessage(Message msg) {

EventHandler ha = new EventHandler(Looper.getMainLooper());

String obj = (String)msg.obj + ", myThread";

Message m = ha.obtainMessage(1, 1, 1, obj);

ha.sendMessage(m);

}

};

Looper.loop();

}

}

}

當子線程執行到RR()函數時,誕生一個子線程,並執行run()函數,就將訊息丟給主線程了。

4. 結語:

Message Loop的用途很廣。請你參閱高煥堂所寫的Android系列書籍,尤其是其中的第4本書:<> 。

你可能感兴趣的:(thread,android,OS,Exchange,FP)