前言
Android的消息傳遞機制是另外一種形式的“事件處理”,這種機制主要是為了解決Android應用中多線程的問題,在Android中不允許Activity新啟動的線程通路該Activity裡的UI元件,這樣會導緻新啟動的線程無法改變UI元件的屬性值。但實際開發中,很多地方需要在工作線程中改變UI元件的屬性值,比如下載下傳網絡圖檔、動畫等等。本篇部落客要介紹Handler是如何發送與處理線程上傳遞來的消息,并講解Message的幾種傳遞資料的方式,最後均會以小Demo來示範。
Handler
Handler,它直接繼承自Object,一個Handler允許發送和處理Message或者Runnable對象,并且會關聯到主線程的MessageQueue中。每個Handler具有一個單獨的線程,并且關聯到一個消息隊列的線程,就是說一個Handler有一個固有的消息隊列。當執行個體化一個Handler的時候,它就承載在一個線程和消息隊列的線程,這個Handler可以把Message或Runnable壓入到消息隊列,并且從消息隊列中取出Message或Runnable,進而操作它們。
Handler主要有兩個作用:
- 在工作線程中發送消息。
- 在UI線程中擷取、處理消息。
上面介紹到Handler可以把一個Message對象或者Runnable對象壓入到消息隊列中,進而在UI線程中擷取Message或者執行Runnable對象,是以Handler把壓入消息隊列有兩大體系,Post和sendMessage:
- Post:Post允許把一個Runnable對象入隊到消息隊列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。
- sendMessage:sendMessage允許把一個包含消息資料的Message對象壓入到消息隊列中。它的方法有:sendEmptyMessage(int)、sendMessage(Message)、sendMessageAtTime(Message,long)、sendMessageDelayed(Message,long)。
從上面的各種方法可以看出,不管是post還是sendMessage都具有多種方法,它們可以設定Runnable對象和Message對象被入隊到消息隊列中,是立即執行還是延遲執行。
Post
對于Handler的Post方式來說,它會傳遞一個Runnable對象到消息隊列中,在這個Runnable對象中,重寫run()方法。一般在這個run()方法中寫入需要在UI線程上的操作。
在Handler中,關于Post方式的方法有:
- boolean post(Runnable r):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,立即執行。
- boolean postAtTime(Runnable r,long uptimeMillis):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,在特定的時間執行。
- boolean postDelayed(Runnable r,long delayMillis):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,延遲delayMills秒執行
- void removeCallbacks(Runnable r):從消息隊列中移除一個Runnable對象。
下面通過一個Demo,講解如何通過Handler的post方式在新啟動的線程中修改UI元件的屬性:
1 package com.bgxt.datatimepickerdemo;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.view.View;
7 import android.widget.Button;
8 import android.widget.TextView;
9
10 public class HandlerPostActivity1 extends Activity {
11 private Button btnMes1,btnMes2;
12 private TextView tvMessage;
13 // 聲明一個Handler對象
14 private static Handler handler=new Handler();
15
16 @Override
17 protected void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 setContentView(R.layout.message_activity);
20
21 btnMes1=(Button)findViewById(R.id.btnMes1);
22 btnMes2=(Button)findViewById(R.id.btnMes2);
23 tvMessage=(TextView)findViewById(R.id.tvMessage);
24 btnMes1.setOnClickListener(new View.OnClickListener() {
25
26 @Override
27 public void onClick(View v) {
28 // 新啟動一個子線程
29 new Thread(new Runnable() {
30 @Override
31 public void run() {
32 // tvMessage.setText("...");
33 // 以上操作會報錯,無法再子線程中通路UI元件,UI元件的屬性必須在UI線程中通路
34 // 使用post方式修改UI元件tvMessage的Text屬性
35 handler.post(new Runnable() {
36 @Override
37 public void run() {
38 tvMessage.setText("使用Handler.post在工作線程中發送一段執行到消息隊列中,在主線程中執行。");
39 }
40 });
41 }
42 }).start();
43 }
44 });
45
46 btnMes2.setOnClickListener(new View.OnClickListener() {
47
48 @Override
49 public void onClick(View v) {
50 new Thread(new Runnable() {
51 @Override
52 public void run() {
53 // 使用postDelayed方式修改UI元件tvMessage的Text屬性值
54 // 并且延遲3S執行
55 handler.postDelayed(new Runnable() {
56
57 @Override
58 public void run() {
59 tvMessage.setText("使用Handler.postDelayed在工作線程中發送一段執行到消息隊列中,在主線程中延遲3S執行。");
60
61 }
62 }, 3000);
63 }
64 }).start();
65
66 }
67 });
68 }
69 }
效果展示:
有一點值得注意的是,對于Post方式而言,它其中Runnable對象的run()方法的代碼,均執行在UI線程上,是以對于這段代碼而言,不能執行在UI線程上的操作,一樣無法使用post方式執行,比如說通路網絡,下面提供一個例子,使用post方式從網際網路上擷取一張圖檔,并且顯示在ImageView中。
1 package com.bgxt.datatimepickerdemo;
2
3 import org.apache.http.HttpResponse;
4 import org.apache.http.client.HttpClient;
5 import org.apache.http.client.methods.HttpGet;
6 import org.apache.http.impl.client.DefaultHttpClient;
7 import org.apache.http.util.EntityUtils;
8
9 import android.app.Activity;
10 import android.app.ProgressDialog;
11 import android.graphics.Bitmap;
12 import android.graphics.BitmapFactory;
13 import android.os.Bundle;
14 import android.os.Handler;
15 import android.view.View;
16 import android.widget.Button;
17 import android.widget.ImageView;
18
19 public class HandlerPostActivity2 extends Activity {
20 private Button btnDown;
21 private ImageView ivImage;
22 private static String image_path = "http://ww4.sinaimg.cn/bmiddle/786013a5jw1e7akotp4bcj20c80i3aao.jpg";
23 private ProgressDialog dialog;
24 // 一個靜态的Handler,Handler建議聲明為靜态的
25 private static Handler handler=new Handler();
26 @Override
27 protected void onCreate(Bundle savedInstanceState) {
28 super.onCreate(savedInstanceState);
29 setContentView(R.layout.asynctask_activity);
30
31 btnDown = (Button) findViewById(R.id.btnDown);
32 ivImage = (ImageView) findViewById(R.id.ivSinaImage);
33
34 dialog = new ProgressDialog(this);
35 dialog.setTitle("提示");
36 dialog.setMessage("正在下載下傳,請稍後...");
37 dialog.setCancelable(false);
38
39 btnDown.setOnClickListener(new View.OnClickListener() {
40 @Override
41 public void onClick(View v) {
42 // 開啟一個子線程,用于下載下傳圖檔
43 new Thread(new MyThread()).start();
44 // 顯示對話框
45 dialog.show();
46 }
47 });
48 }
49
50 public class MyThread implements Runnable {
51
52 @Override
53 public void run() {
54 // 下載下傳一個圖檔
55 HttpClient httpClient = new DefaultHttpClient();
56 HttpGet httpGet = new HttpGet(image_path);
57 HttpResponse httpResponse = null;
58 try {
59 httpResponse = httpClient.execute(httpGet);
60 if (httpResponse.getStatusLine().getStatusCode() == 200) {
61 byte[] data = EntityUtils.toByteArray(httpResponse
62 .getEntity());
63 // 得到一個Bitmap對象,并且為了使其在post内部可以通路,必須聲明為final
64 final Bitmap bmp=BitmapFactory.decodeByteArray(data, 0, data.length);
65 handler.post(new Runnable() {
66 @Override
67 public void run() {
68 // 在Post中操作UI元件ImageView
69 ivImage.setImageBitmap(bmp);
70 }
71 });
72 // 隐藏對話框
73 dialog.dismiss();
74 }
75 } catch (Exception e) {
76 e.printStackTrace();
77 }
78 }
79
80 }
81 }
效果展示:
Message
Handler如果使用sendMessage的方式把消息入隊到消息隊列中,需要傳遞一個Message對象,而在Handler中,需要重寫handleMessage()方法,用于擷取工作線程傳遞過來的消息,此方法運作在UI線程上。下面先介紹一下Message。
Message是一個final類,是以不可被繼承。Message封裝了線程中傳遞的消息,如果對于一般的資料,Message提供了getData()和setData()方法來擷取與設定資料,其中操作的資料是一個Bundle對象,這個Bundle對象提供一系列的getXxx()和setXxx()方法用于傳遞基本資料類型的鍵值對,對于基本資料類型,使用起來很簡單,這裡不再詳細講解。而對于複雜的資料類型,如一個對象的傳遞就要相對複雜一些。在Bundle中提供了兩個方法,專門用來傳遞對象的,但是這兩個方法也有相應的限制,需要實作特定的接口,當然,一些Android自帶的類,其實已經實作了這兩個接口中的某一個,可以直接使用。方法如下:
- putParcelable(String key,Parcelable value):需要傳遞的對象類實作Parcelable接口。
- pubSerializable(String key,Serializable value):需要傳遞的對象類實作Serializable接口。
還有另外一種方式在Message中傳遞對象,那就是使用Message自帶的obj屬性傳值,它是一個Object類型,是以可以傳遞任意類型的對象,Message自帶的有如下幾個屬性:
- int arg1:參數一,用于傳遞不複雜的資料,複雜資料使用setData()傳遞。
- int arg2:參數二,用于傳遞不複雜的資料,複雜資料使用setData()傳遞。
- Object obj:傳遞一個任意的對象。
- int what:定義的消息碼,一般用于設定消息的标志。
對于Message對象,一般并不推薦直接使用它的構造方法得到,而是建議通過使用Message.obtain()這個靜态的方法或者Handler.obtainMessage()擷取。Message.obtain()會從消息池中擷取一個Message對象,如果消息池中是空的,才會使用構造方法執行個體化一個新Message,這樣有利于消息資源的利用。并不需要擔心消息池中的消息過多,它是有上限的,上限為10個。Handler.obtainMessage()具有多個重載方法,如果檢視源碼,會發現其實Handler.obtainMessage()在内部也是調用的Message.obtain()。
既然Message是線上程間傳遞消息,那麼先以一個Demo講解一下Message的使用,還是正常的從網際網路上下載下傳一張圖檔的Demo,下載下傳後使用ImageView控件展示:
1 package com.bgxt.datatimepickerdemo;
2
3 import org.apache.http.HttpResponse;
4 import org.apache.http.client.HttpClient;
5 import org.apache.http.client.methods.HttpGet;
6 import org.apache.http.impl.client.DefaultHttpClient;
7 import org.apache.http.util.EntityUtils;
8
9 import android.app.Activity;
10 import android.app.ProgressDialog;
11 import android.graphics.Bitmap;
12 import android.graphics.BitmapFactory;
13 import android.os.Bundle;
14 import android.os.Handler;
15 import android.os.Message;
16 import android.view.View;
17 import android.widget.Button;
18 import android.widget.ImageView;
19
20 public class HandlerMessageActivity1 extends Activity {
21 private Button btnDown;
22 private ImageView ivImage;
23 private static String image_path = "http://ww4.sinaimg.cn/bmiddle/786013a5jw1e7akotp4bcj20c80i3aao.jpg";
24 private ProgressDialog dialog;
25 private static int IS_FINISH = 1;
26
27 @Override
28 protected void onCreate(Bundle savedInstanceState) {
29 super.onCreate(savedInstanceState);
30 setContentView(R.layout.asynctask_activity);
31
32 btnDown = (Button) findViewById(R.id.btnDown);
33 ivImage = (ImageView) findViewById(R.id.ivSinaImage);
34
35 dialog = new ProgressDialog(this);
36 dialog.setTitle("提示資訊");
37 dialog.setMessage("正在下載下傳,請稍後...");
38 dialog.setCancelable(false);
39
40 btnDown.setOnClickListener(new View.OnClickListener() {
41 @Override
42 public void onClick(View v) {
43 new Thread(new MyThread()).start();
44 dialog.show();
45 }
46 });
47 }
48
49 private Handler handler = new Handler() {
50 // 在Handler中擷取消息,重寫handleMessage()方法
51 @Override
52 public void handleMessage(Message msg) {
53 // 判斷消息碼是否為1
54 if(msg.what==IS_FINISH){
55 byte[] data=(byte[])msg.obj;
56 Bitmap bmp=BitmapFactory.decodeByteArray(data, 0, data.length);
57 ivImage.setImageBitmap(bmp);
58 dialog.dismiss();
59 }
60 }
61 };
62
63 public class MyThread implements Runnable {
64
65 @Override
66 public void run() {
67 HttpClient httpClient = new DefaultHttpClient();
68 HttpGet httpGet = new HttpGet(image_path);
69 HttpResponse httpResponse = null;
70 try {
71 httpResponse = httpClient.execute(httpGet);
72 if (httpResponse.getStatusLine().getStatusCode() == 200) {
73 byte[] data = EntityUtils.toByteArray(httpResponse
74 .getEntity());
75 // 擷取一個Message對象,設定what為1
76 Message msg = Message.obtain();
77 msg.obj = data;
78 msg.what = IS_FINISH;
79 // 發送這個消息到消息隊列中
80 handler.sendMessage(msg);
81 }
82 } catch (Exception e) {
83 e.printStackTrace();
84 }
85 }
86 }
87 }
展示效果:
Message.obtain()方法具有多個重載方法,大緻可以分為為兩類,一類是無需傳遞Handler對象,對于這類的方法,當填充好消息後,需要調用Handler.sendMessage()方法來發送消息到消息隊列中。第二類需要傳遞一個Handler對象,這類方法可以直接使用Message.sendToTarget()方法發送消息到消息隊列中,這是因為在Message對象中有一個私有的Handler類型的屬性Target,當時obtain方法傳遞進一個Handler對象的時候,會給Target屬性指派,當調用sendToTarget()方法的時候,實際在它内部還是調用的Target.sendMessage()方法。
在Handler中,也定義了一些發送空消息的方法,如:sendEmptyMessage(int what)、sendEmptyMessageDelayed(int what,long delayMillis),看似這些方法沒有使用Message就可以發送一個消息,但是如果檢視源碼就會發現,其實内部也是從Message.obtain()方法中擷取一個Message對象,然後給屬性指派,最後使用sendMessage()發送消息到消息隊列中。
Handler中,與Message發送消息相關的方法有:
- Message obtainMessage():擷取一個Message對象。
- boolean sendMessage():發送一個Message對象到消息隊列中,并在UI線程取到消息後,立即執行。
- boolean sendMessageDelayed():發送一個Message對象到消息隊列中,在UI線程取到消息後,延遲執行。
- boolean sendEmptyMessage(int what):發送一個空的Message對象到隊列中,并在UI線程取到消息後,立即執行。
- boolean sendEmptyMessageDelayed(int what,long delayMillis):發送一個空Message對象到消息隊列中,在UI線程取到消息後,延遲執行。
- void removeMessage():從消息隊列中移除一個未響應的消息。
下面通過一個小Demo示範一下各種發送Message的方式:
1 package com.bgxt.datatimepickerdemo;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.os.Message;
7 import android.view.View;
8 import android.widget.Button;
9 import android.widget.TextView;
10
11 public class HandlerMessageActivity2 extends Activity {
12 private Button btn1, btn2, btn3, btn4,btn5;
13 private static TextView tvMes;
14 private static Handler handler = new Handler() {
15 @Override
16 public void handleMessage(android.os.Message msg) {
17 if (msg.what == 3||msg.what==5) {
18 tvMes.setText("what=" + msg.what + ",這是一個空消息");
19 } else {
20 tvMes.setText("what=" + msg.what + "," + msg.obj.toString());
21 }
22
23 };
24 };
25
26 @Override
27 protected void onCreate(Bundle savedInstanceState) {
28 // TODO Auto-generated method stub
29 super.onCreate(savedInstanceState);
30 setContentView(R.layout.message_activity2);
31 tvMes = (TextView) findViewById(R.id.tvMes);
32 btn1 = (Button) findViewById(R.id.btnMessage1);
33 btn2 = (Button) findViewById(R.id.btnMessage2);
34 btn3 = (Button) findViewById(R.id.btnMessage3);
35 btn4 = (Button) findViewById(R.id.btnMessage4);
36 btn5 = (Button) findViewById(R.id.btnMessage5);
37
38 btn1.setOnClickListener(new View.OnClickListener() {
39 @Override
40 public void onClick(View v) {
41 // 使用Message.Obtain+Hander.sendMessage()發送消息
42 new Thread(new Runnable() {
43 @Override
44 public void run() {
45 Message msg = Message.obtain();
46 msg.what = 1;
47 msg.obj = "使用Message.Obtain+Hander.sendMessage()發送消息";
48 handler.sendMessage(msg);
49 }
50 }).start();
51 }
52 });
53
54 btn2.setOnClickListener(new View.OnClickListener() {
55
56 @Override
57 public void onClick(View v) {
58 // 使用Message.sendToTarget發送消息
59 new Thread(new Runnable() {
60 @Override
61 public void run() {
62 Message msg = Message.obtain(handler);
63 msg.what = 2;
64 msg.obj = "使用Message.sendToTarget發送消息";
65 msg.sendToTarget();
66 }
67 }).start();
68 }
69 });
70
71 btn3.setOnClickListener(new View.OnClickListener() {
72 // 發送一個延遲消息
73 @Override
74 public void onClick(View v) {
75 new Thread(new Runnable() {
76 @Override
77 public void run() {
78 handler.sendEmptyMessage(3);
79 }
80 }).start();
81 }
82 });
83
84 btn4.setOnClickListener(new View.OnClickListener() {
85
86 @Override
87 public void onClick(View v) {
88 new Thread(new Runnable() {
89 @Override
90 public void run() {
91 Message msg = Message.obtain();
92 msg.what =4;
93 msg.obj = "使用Message.Obtain+Hander.sendMessage()發送延遲消息";
94 handler.sendMessageDelayed(msg, 3000);
95 }
96 }).start();
97 }
98 });
99
100 btn5.setOnClickListener(new View.OnClickListener() {
101 // 發送一個延遲的空消息
102 @Override
103 public void onClick(View v) {
104 new Thread(new Runnable() {
105 @Override
106 public void run() {
107 handler.sendEmptyMessageDelayed(5, 3000);
108 }
109 }).start();
110 }
111 });
112 }
113 }
效果展示: