Android線程間通信機制(Handler Looper )


Android線程間通信機制

當android應用程序運行時,一個主線程被創建(也稱作UI線程),此線程主要負責處理UI相關的事件,由於Android采用UI單線程模型,所以只能在主線程中對UI元素進行操作,如果在非UI線程直接對UI進行了操作,則會報錯,另外,對於運算量較大的操作和IO操作,我們需要新開線程來處理這些工作,以免阻塞UI線程,子線程與主線程之間是怎樣進行通信的呢?此時就要采用消息循環機制(Looper)與Handler進行處理。

一、基本概念

Looper:每一個線程都可以產生一個Looper,用來管理線程的Message,Looper對象會建立一個MessgaeQueue數據結構來存放message。

Handler:與Looper溝通的對象,可以push消息或者runnable對象到MessgaeQueue,也可以從MessageQueue得到消息。

查看其構造函數:

Handler()

Default constructor associates this handler with the queue for the current thread.//如不指定Looper參數則默認利用當前線程的Looper創建

Handler(Looper looper)

Use the provided queue instead of the default one.//使用指定的Looper對象創建Handler

 

線程A的Handler對象引用可以傳遞給別的線程,讓別的線程B或C等能送消息來給線程A。

線程A的Message Queue里的消息,只有線程A所屬的對象可以處理。

注意:Android里沒有global的MessageQueue,不同進程(或APK之間)不能通過MessageQueue交換消息。

 

二、Handler通過Message通信的基本方式

使用Looper.myLooper可以取得當前線程的Looper對象。

使用mHandler = new Handler(Looper.myLooper()); 可產生用來處理當前線程的Handler對象。

使用mHandler = new Handler(Looper.getMainLooper()); 可誕生用來處理main線程的Handler對象。

使用Handler傳遞消息對象時將消息封裝到一個Message對象中,Message對象中主要字段如下:

public int

arg1

當需要傳遞的消息是整形時arg1 和 arg2 是一種低成本的可選方案,他使用 setData()/getData()訪問或修改字段。 

public int

arg2

同上

public Object

obj

可傳送的任意object類型.

public int

what

Int類型用戶自定義的消息類型碼

Message對象可以通過Message類的構造函數獲得,但Google推薦使用Message.obtain()方法獲得,該方法會從全局的對象池里返回一個可復用的Messgae實例,API中解釋如下:

Message()

Constructor (but the preferred way to get a Message is to call Message.obtain()).

Handler發出消息時,既可以指定消息被接受后馬上處理,也可以指定經過一定時間間隔之后被處理,如sendMessageDelayed(Message msg, long delayMillis),具體請參考API。

Handler消息被發送出去之后,將由handleMessage(Message msg)方法處理。

 

注意:在Android里,新誕生一個線程,並不會自動建立其Message Loop

可以通過調用Looper.prepare()為該線程建立一個MessageQueue,再調用Looper.loop()進行消息循環

下面舉例說明:

在main.xml中定義兩個button及一個textview

<Button

android:id="@+id/a"

android:layout_width
="80dp"

android:layout_height
="60dp"

android:padding
="6dp"

android:layout_marginTop
="10dp"

android:text
="Test looper"

android:hint
="Test looper" />

<Button

android:id="@+id/b"

android:layout_width
="80dp"

android:layout_height
="60dp"

android:padding
="6dp"

android:layout_marginTop
="10dp"

android:text
="Exit" />

<TextView

android:id="@+id/tv"

android:layout_width
="fill_parent"

android:layout_height
="wrap_content"

android:layout_marginTop
="10dp"/>



 

Activity源代碼如下:

  1 public class HandlerTestActivity extends Activity implements Button.OnClickListener{
2
3 public TextView tv;
4
5 private myThread myT;
6
7 Button bt1, bt2;
8
9 /** Called when the activity is first created. */
10
11 @Override
12
13 public void onCreate(Bundle savedInstanceState) {
14
15 super.onCreate(savedInstanceState);
16
17 setContentView(R.layout.main);
18
19 bt1 = (Button)findViewById(R.id.a);
20
21 bt2 = (Button)findViewById(R.id.b);
22
23 tv = (TextView)findViewById(R.id.tv);
24
25 bt1.setId(1);//為兩個button設置ID,此ID用於后面判斷是哪個button被按下
26
27 bt2.setId(2);
28
29 bt1.setOnClickListener(this);//增加監聽器
30
31 bt2.setOnClickListener(this);
32
33 }
34
35
36
37 @Override
38
39 public void onClick(View v) {
40
41 // TODO Auto-generated method stub
42
43 switch(v.getId()){//按鍵事件響應,如果是第一個按鍵將啟動一個新線程
44
45 case 1:
46
47 myT = new myThread();
48
49 myT.start();
50
51 break;
52
53 case 2:
54
55 finish();
56
57 break;
58
59 default:
60
61 break;
62
63 }
64
65 }
66
67
68
69 class myThread extends Thread
70
71 {
72
73 private EHandler mHandler;
74
75 public void run()
76
77 {
78
79 Looper myLooper, mainLooper;
80
81 myLooper = Looper.myLooper();//得到當前線程的Looper
82
83 mainLooper = Looper.getMainLooper();//得到UI線程的Looper
84
85 String obj;
86
87 if(myLooper == null)//判斷當前線程是否有消息循環Looper
88
89 {
90
91 mHandler = new EHandler(mainLooper);
92
93 obj = "current thread has no looper!";//當前Looper為空,EHandler用mainLooper對象構造
94
95 }
96
97 else
98
99 {
100
101 mHandler = new EHandler(myLooper);//當前Looper不為空,EHandler用當前線程的Looper對象構造
102
103 obj = "This is from current thread.";
104
105 }
106
107 mHandler.removeMessages(0);//清空消息隊列里的內容
108
109 Message m = mHandler.obtainMessage(1, 1, 1, obj);
110
111 mHandler.sendMessage(m);//發送消息
112
113 }
114
115 }
116
117
118
119 class EHandler extends Handler
120
121 {
122
123 public EHandler(Looper looper)
124
125 {
126
127 super(looper);
128
129 }
130
131 @Override
132
133 public void handleMessage(Message msg) //消息處理函數
134
135 {
136
137 tv.setText((String)msg.obj);//設置TextView內容
138
139 }
140
141 }
142
143
144
145 }



程序運行后點擊TestLooper按鍵,TextView輸出如下,說明新創建的線程里Looper為空,也就說明了新創建的線程並不會自己建立Message Looper。

 

 

 

修改myThread類:

  

 1 class myThread extends Thread
2
3 {
4
5 private EHandler mHandler;
6
7 public void run()
8
9 {
10
11 Looper myLooper, mainLooper;
12
13 Looper.prepare();
14
15 myLooper = Looper.myLooper();
16
17 mainLooper = Looper.getMainLooper();
18
19 ......
20
21 ......
22
23 mHandler.sendMessage(m);
24
25 Looper.loop();
26
27 }
28
29 }

 

    Looper.prepare為當前線程創建一個Message Looper,Looper.loop()開啟消息循環。這樣修改是OK呢?

    答案是否定的!運行時Logcat將拋出CalledFromWrongThreadException異常錯誤,提示如下:

   

    意思就是說“只有原始創建這個視圖層次的線程才能修改它的視圖”,本例中的TextView是在UI線程(主線程)中創建,因此,myThread線程不能修改其顯示內容!

       一般的做法是在子線程里獲取主線程里的Handler對象,然后通過該對象向主線程的消息隊列里發送消息,進行通信。

       如果子線程想修改主線程的UI,可以通過發送Message給主線程的消息隊列,主線程經行判斷處理再對UI經行操作,具體可以參考之前的代碼。

 

三、Handler通過runnable通信的基本方式

我們可以通過Handler的post方法實現線程間的通信,API中關於post方法說明如下

public final boolean post (Runnable r)

Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.

Post方法將安排runnable對象在主線程的某個位置運行,但是並不會開啟一個新的線程,驗證代碼如下:

 1 public class HandlerTestActivity extends Activity {
2
3
4
5 private Handler handlerTest;
6
7 Runnable runnableTest = new Runnable()
8
9 {
10
11 public void run()
12
13 {
14
15 String runID = String.valueOf(Thread.currentThread().getId());//輸出Runnable 線程的ID號
16
17 Log.v("Debug",runID);
18
19 }
20
21 };
22
23 /** Called when the activity is first created. */
24
25 @Override
26
27 public void onCreate(Bundle savedInstanceState) {
28
29 super.onCreate(savedInstanceState);
30
31 setContentView(R.layout.main);
32
33 handlerTest = new Handler();
34
35 String mainID = String.valueOf(Thread.currentThread().getId());
36
37 Log.v("Debug",mainID);//輸出主線程的ID號
38
39 handlerTest.post(runnableTest);
40
41 }
42
43 }



Logcat里輸出如下:

 

 

說明只是把runnable里的run方法放到UI線程里運行,並不會創建新線程

因此我們可以在子線程中將runnable加入到主線程的MessageQueue,然后主線程將調用runnable的方法,可以在此方法中更新主線程UI。

將之前的代碼修改如下:

 1 public class HandlerTestActivity extends Activity {
2
3 private Handler handlerTest;
4
5 private TextView tv;
6
7 /** Called when the activity is first created. */
8
9 @Override
10
11 public void onCreate(Bundle savedInstanceState) {
12
13 super.onCreate(savedInstanceState);
14
15 setContentView(R.layout.main);
16
17 tv = (TextView)findViewById(R.id.tv);//TextView初始化為空
18
19 handlerTest = new Handler();
20
21 myThread myT = new myThread();
22
23 myT.start();//開啟子線程
24
25 }
26
27
28
29 class myThread extends Thread{
30
31 public void run(){
32
33 handlerTest.post(runnableTest);//子線程將runnable加入消息隊列
34
35 }
36
37 }
38
39
40
41 Runnable runnableTest = new Runnable()
42
43 {
44
45 public void run()
46
47 {
48
49 tv.setText("此信息由子線程輸出!");
50
51 }
52
53 };
54
55 }



相當於在主線程中調用了runnalberun方法,更改了TextViewUI!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM