Android的消息傳遞機制是另一種形式的“事件處理”,這種機制主要是爲了解決Android應用的多線程問題——Android平臺只允許UI線程修改Activity裏的組件,這樣就會導致新啓動的線程無法動態改變界面組件的屬性值。但實際開發中,需要讓新的線程能夠改變界面組件的屬性值,這就需要藉助於Handler的消息傳遞機制來實現了。
Handler類簡介:
Handler類主要作用:
- 在新啓動的線程中發送消息
- 在主線程中獲取、處理消息
通過回調的方法——重寫Handler類中處理消息的方法來讓主線程能適時地出口i新啓動線程所發送的消息。,當新啓動的線程發送消息時,消息會發送到與之關聯的MessageQueue,而Handler會不斷地從MessageQueue中獲取並處理消息——這將導致Handler類中處理消息的方法被回調。
Handler類中包含如下用於發送、處理消息的方法:
- void handleMessage(Message msg):處理消息的方法。通常被重寫。
- final boolean hasMessages(int what):檢查消息隊列中是否包含what屬性爲指定值的消息。
- final boolean hasMessages(int what,Object object):檢查消息隊列中是否包含what屬性爲指定值且object屬性爲指定對象的消息。
- 多個重載的Message obtainMessage():獲取消息。
- sendEmptyMessage(int what):發送空消息。
- final boolean sendEmptyMessageDelayed(int what,long delayMillis):指定多少毫秒後發送空消息。
- final boolean sendMessage(Message msg ):立即發送消息。
- final boolean sendMessageDelayed(Message msg,long delayMillis):指定多少毫秒後發送消息。
下面的一個小例子可以通過新線程來週期性地改變ImageView所顯示的圖片。
public class MainActivity extends Activity {
//定義週期性改變的圖片的ID
int[] imagesId = new int[]{
R.drawable.a,
R.drawable.b,
R.drawable.c,
R.drawable.d,
R.drawable.e
};
int currentImageId = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView show = (ImageView) findViewById(R.id.show);
final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
//判斷該消息是否是本程序發送的
if(msg.what == 0x123){
//動態地改變顯示的圖片的ID
show.setImageResource(imagesId[currentImageId++]);
if(currentImageId>4){
currentImageId = 0;
}
}
}
};
//定義一個計時器,讓該計時器週期性地執行指定任務
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
//新啓動的線程無法訪問Activity裏的組件
//所以需要Handler發送消息
Message msg = new Message();
msg.what = 0x123;
//發送消息
mHandler.sendMessage(msg);
}
}, 0, 800);
}
}
當新線程發送消息時,Handler 的handleMessage(Message msg)方法被自動回調,來改變Activity中組件的屬性。
與Handler一起工作的幾個組件(爲了更好地理解Handler的工作原理):
- Handler:把消息發送給Looper管理的MessageQueue,並負責處理Lopper分給它的消息。
- Message:Handler接收和處理的消息對象。
- Looper:每個線程只能有一個Looper。它的loop方法負責讀取MessageQueue中的消息,並將消息分給對應的Handler處理。
- MessageQueue:消息隊列,先進先出方式管理Message,程序創建Looper對象時會在它的構造器中創建MessageQueue對象。源碼如下:
private Looper(){
mQueue = new MessageQueue();
mRn = true;
mThread = Thread.currentThread();
}
通過源碼可知,無法通過構造器來創建Looper對象,程序在初始化Looper時會創建一個與之關聯的MessageQueue,這個MessageQueue負責管理消息。
如果要Handler正常工作,就要在當前線程中必須有一個Looper對象:
- 主UI線程中,系統已經初始化了一個Looper對象,因此程序直接創建Handler即可,然後就可通過Handler來發送消息、處理消息。
- 我們自己啓動的子線程,必須自己創建一個Looper對象,並啓動它。創建Looper對象調用它的prepare()方法即可(prepare()方法保證了每一個線程最多隻有一個Looper對象),然後調用Looper靜態loop()方法來啓動它(loop()使用死循環,不斷從MessageQueue中取出消息,傳遞給對應的Handler處理)。
在線程中使用Handler的步驟:
- 調用Looper的prepare()方法創建Looper對象,同時會有與之匹配的MessageQueue被創建
- 創建Handler子類實例,重寫handlerMessage()方法,負責處理來自其他線程的消息。
- 調用Looper的loop()方法啓動Looper。
例:利用新線程計算質數
public class MainActivity extends Activity {
//2~UPPER_NUM範圍內的質數
static final String UPPER_NUM = "upper";
EditText etNum;
//定義一個線程類用於計算質數
CalThread calThread;
class CalThread extends Thread{
public Handler handler;
public void run(){
//Looper的prepare方法用於創建Looper對象,同時創建與之對應的MessageQueue
Looper.prepare();
handler = new Handler(){
//重寫處理消息的方法
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
if(msg.what==0x123){
//獲取從2到upper的質數
int upper = msg.getData().getInt(UPPER_NUM);
List<Integer> nums = new ArrayList<Integer>();
outer:
for(int i = 2;i<upper;i++){
for(int j = 2;j<Math.sqrt(i);j++){
if(i!=2&&i%j==0){
continue outer;
}
}
nums.add(i);
}
//把得到的質數顯示出來
Toast.makeText(MainActivity.this, nums.toString(),Toast.LENGTH_LONG).show();
}
}
};
//啓動Looper
Looper.loop();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etNum = (EditText) findViewById(R.id.etNum);
calThread = new CalThread();
calThread.start();
}
//
public void cal(View v){
//創建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER_NUM, Integer.parseInt(etNum.getText().toString()));
msg.setData(bundle);
//向新線程中的Handler發送消息
calThread.handler.sendMessage(msg);
}
}
運行該程序,根據輸入的數據計算該範圍內的質數都會交給新線程,前臺UI不受影響。