Android消息機制之Handler


Android為什么要提供Handler

Android建議我們不要在UI線程中執行耗時操作,因為這很容易導致ANR異常(在Android源碼中我們可以看到,UI如果對用戶的操作超過5秒無響應,就會報ANR異常)。因此,一些耗時操作都會在子線程中完成。當我們在子線程中獲取了數據,要將其顯示到UI中,如果沒有Handler,這將很難完成。因此,Android之所以提供Handler,就是為了解決子線程訪問UI的問題。
為什么Android不允許在子線程中訪問UI呢?顯然這樣做不安全,多線程訪問UI是不安全的(學過操作系統的盆友應該都了解線程互斥,這里我就不詳細介紹了)。有人就會說了,可以通過設置信號量來解決啊。這中方法不是不可以,因為這種方法會使訪問UI的邏輯變得復雜;其次這會降低UI的訪問效率。而使用Handler就比較簡單高效。Handler是同個Message來通訊的。

 

Handler的用法

使用Handler時,需要重寫handleMessage方法,在handleMessage中接受新線程發來的Message,並做相應的處理。在新線程中則是通過Message來傳遞消息,Message中往往也攜帶着需要傳遞的數據以及消息的類型。還要強調一點,如果當前線程有Looper就不需要執行Looper.prepare(),如果沒有,就需要在新線程內執行Looper.prepare(),否則會報錯。具體使用代碼如下:

 1 public class MainActivity extends AppCompatActivity {
2 private Handler mHandler=new Handler(){
3 @Override
4 public void handleMessage(Message msg) {
5 switch (msg.what)
6 {
7 case 1:
8 //執行需要修改的UI操作
9 break;
10 default:
11 break;
12 }
13 }
14 };
15
16 @Override
17 protected void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 setContentView(R.layout.activity_main);
20
21 new Thread(new Runnable() {
22 @Override
23 public void run() {//在新線程中執行耗時操作
24
25 //如果當前線程有Looper就不需要執行Looper.prepare();
26 Looper.prepare();
27 try {
28 Thread.sleep(1000);//睡眠1秒
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32
33 //操作完成之后通過發送Message,來通知Handler進行UI操作
34
35 Message msg=new Message();
36 msg.what=1;
37
38 /*這部分是偽代碼,value 是想通過Message傳遞的值
39 Bundle data=new Bundle();
40 data.putSerializable("key",value);
41 msg.setData(data);
42
43 */
44
45 //設置好數據后,發送消息
46 mHandler.sendMessage(msg);
47 }
48 }).start();
49 }
50
51 }

當然,handler也可以在子線程中創建,代碼如下:

 1   private TextView tv_test;
2
3 @Override
4 protected void onCreate(Bundle savedInstanceState) {
5 super.onCreate(savedInstanceState);
6 setContentView(R.layout.handler_test_layout);
7
8 tv_test= (TextView) findViewById(R.id.tv_test);
9
10
11 }
12
13 //button點擊的函數
14 public void click(View v)
15 {
16 new Thread(new Runnable() {
17 @Override
18 public void run() {
19 Looper.prepare();
20 Handler handler=new Handler(Looper.getMainLooper()){
21 @Override
22 public void handleMessage(Message msg) {
23 switch (msg.what)
24 {
25 case 1:
26 tv_test.setText("receive msg");
27 }
28 }
29 };
30 Message msg=new Message();
31 msg.what=1;
32 handler.sendMessage(msg);
33 }
34 }).start();
35 }

上面的代碼是,當點擊按鈕后,就會創建一個新的線程,在新線程中創建handler,並發送消息、接受消息。這里需要注意的是,在新線程中創建handler需要使用Handler handler=new Handler(Looper.getMainLooper())這樣的寫法,Looper.getMainLooper()將主線程中的Looper傳過去,這樣handler才能訪問UI。運行效果如下:

 

 

Handler的內部機制

Handler創建時會采用Looper來建立消息循環。所以,當前線程必須要有Looper。當Handler創建完成后,其內部的Looper以及MessageQueue既可以和Handler一起協同工作了。Handler通過sendMessage將消息發送給內部的MessageQueue,而MessageQueue會調用queue.enqueueMessage(msg, uptimeMillis)方法,它的源碼如下:

 1 boolean enqueueMessage(Message msg, long when) {
2 if (msg.target == null) {
3 throw new IllegalArgumentException("Message must have a target.");
4 }
5 if (msg.isInUse()) {
6 throw new IllegalStateException(msg + " This message is already in use.");
7 }
8
9 synchronized (this) {
10 if (mQuitting) {
11 IllegalStateException e = new IllegalStateException(
12 msg.target + " sending message to a Handler on a dead thread");
13 Log.w(TAG, e.getMessage(), e);
14 msg.recycle();
15 return false;
16 }
17
18 msg.markInUse();
19 msg.when = when;
20 Message p = mMessages;
21 boolean needWake;
22 if (p == null || when == 0 || when < p.when) {
23 // New head, wake up the event queue if blocked.
24 msg.next = p;
25 mMessages = msg;
26 needWake = mBlocked;
27 } else {
28 // Inserted within the middle of the queue. Usually we don't have to wake
29 // up the event queue unless there is a barrier at the head of the queue
30 // and the message is the earliest asynchronous message in the queue.
31 needWake = mBlocked && p.target == null && msg.isAsynchronous();
32 Message prev;
33 for (;;) {
34 prev = p;
35 p = p.next;
36 if (p == null || when < p.when) {
37 break;
38 }
39 if (needWake && p.isAsynchronous()) {
40 needWake = false;
41 }
42 }
43 msg.next = p; // invariant: p == prev.next
44 prev.next = msg;
45 }
46
47 // We can assume mPtr != 0 because mQuitting is false.
48 if (needWake) {
49 nativeWake(mPtr);
50 }
51 }
52 return true;
53 }

通過源碼,我們發現,queue.enqueueMessage(msg, uptimeMillis)將消息放入了MessageQueue里。Looper則會一直處理MessageQueue中的消息。



                        
                        
                 

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com