主線程與子線程之間相互通信


      在平時寫程序時,我們一般都是在子線程中向主線程發送消息,從而完成請求的處理,這個很常見,不用多說了。那么有時候,我們也可能碰到這樣子的一種需求:需要主線程來向子線程發送消息,希望子線程來完成什么任務。如果這樣子應該怎么做呢?這就是這篇文章將要討論的內容。

一、HandlerThread類

      主線程發送消息給子線程,通常思維邏輯就是:其實很簡單,在主線程中實例化一個Handler,然后讓他與子線程相關聯(只要它與子線程的Looper相關聯即可),這樣子它處理的消息就是該子線程中的消息隊列,而處理的邏輯都是在該子線程中執行的,不會占用主線程的時間。那么我們就來實現一下,看看這樣子到底行得通還是行不通。新建項目,修改它的MainActivity的代碼,如下即可:

 1 package com.example.handldertest;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.os.Handler;
 6 import android.os.Looper;
 7 import android.util.Log;
 8 import android.widget.TextView;
 9 
10 public class ThreadHandlerActivity extends Activity{
11     
12 
13     //創建子線程
14     class MyThread extends Thread{
15         private Looper looper;//取出該子線程的Looper
16         public void run() {
17          
18             Looper.prepare();//創建該子線程的Looper
19             looper = Looper.myLooper();//取出該子線程的Looper
20             Looper.loop();//只要調用了該方法才能不斷循環取出消息
21         }
22     }
23     
24     private TextView tv;
25     private MyThread thread;
26     
27     
28     private Handler mHandler;//將mHandler指定輪詢的Looper
29     
30     protected void onCreate(Bundle savedInstanceState) {
31             super.onCreate(savedInstanceState);
32             tv = new TextView(this);
33             tv.setText("Handler實驗");
34             setContentView(tv);
35             thread = new MyThread();
36             thread.start();//千萬別忘記開啟這個線程
37             //下面是主線程發送消息
38             mHandler = new Handler(thread.looper){
39                 public void handleMessage(android.os.Message msg) {
40                     Log.d("當前子線程是----->", Thread.currentThread()+"");
41                 };
42             };
43             mHandler.sendEmptyMessage(1);
44     }
45 
46 }

      好了,現在運行該程序。有沒有得到預期的結果呢?顯然沒有,因為報錯誤了,如下:

      這是一個空指針錯誤。這是為什么呢?仔細思考,也不難發現原因。因為當主線程走到第38行時,此時子線程的Looper對象還沒有被創建出來,那么此時thread.looper肯定為空了。其實這個時間是很不好控制的,當然了,你可以讓主線程休眠2秒后再執行第38行以后的代碼。但是如果有很多個子線程都需要主線程類給其分配任務怎么辦??那簡直要亂套了。所以我們就更好的解決方式。就是android顯然也考慮到了這個問題,於是它我們提供了一個HandlerThread類。這個類是專門處理這個問題的。

      當主線程中有耗時的操作時,需要在子線程中完成,通常我們就把這個邏輯放在HandlerThread的對象中執行(該對象就是一個子線程),然后在需要開始執行邏輯的地方發送一個Message來通知一下就可以了。下面我們就修改上面的代碼,看一看如何使用HandlerThread這個類。修改MainActivity中的代碼如下:

 1 package com.example.handldertest;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.os.Handler;
 6 import android.os.HandlerThread;
 7 import android.util.Log;
 8 import android.widget.TextView;
 9 
10 public class ThreadHandlerActivity extends Activity{
11     
12 
13     
14     private TextView tv;
15 
16     private Handler mHandler;//將mHandler指定輪詢的Looper
17     
18     protected void onCreate(Bundle savedInstanceState) {
19             super.onCreate(savedInstanceState);
20             tv = new TextView(this);
21             tv.setText("Handler實驗");
22             setContentView(tv);
23         
24             //實例化一個特殊的線程HandlerThread,必須給其指定一個名字
25             HandlerThread thread = new HandlerThread("handler thread");
26             thread.start();//千萬不要忘記開啟這個線程
27             //將mHandler與thread相關聯
28             mHandler = new Handler(thread.getLooper()){
29                 public void handleMessage(android.os.Message msg) {
30                     Log.d("當前子線程是----->", Thread.currentThread()+"");
31                 };
32             };
33             mHandler.sendEmptyMessage(1);//發送消息
34     }
35 
36 }

      運行程序,打印的結果如下:

     從打印結果來看,當前子線程的名字正是我們所起的那個名字“handler thread"。

     你會有疑問,表面上看HandlerThread並沒有創建自己的Looper啊?而且既然是一個線程,那么我們肯定也能重寫它的run方法吧。在解答你的疑問之前,我們不妨重寫它的run方法來看一看會有什么結果。將代碼修改如下:

 1 package com.example.handldertest;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.os.Handler;
 6 import android.os.HandlerThread;
 7 import android.util.Log;
 8 import android.widget.TextView;
 9 
10 public class ThreadHandlerActivity extends Activity{
11     
12 
13     
14     private TextView tv;
15 
16     private Handler mHandler;//將mHandler指定輪詢的Looper
17     
18     protected void onCreate(Bundle savedInstanceState) {
19             super.onCreate(savedInstanceState);
20             tv = new TextView(this);
21             tv.setText("Handler實驗");
22             setContentView(tv);
23         
24             //實例化一個特殊的線程HandlerThread,必須給其指定一個名字
25             HandlerThread thread = new HandlerThread("handler thread"){
26                 @Override
27                 public void run() {
28                     for(int i=0;i<3;i++){
29                         Log.d("handler thread run ",i+"");
30                     }
31                 }
32             };
33 //            HandlerThread thread = new HandlerThread("handler thread");
34             thread.start();//千萬不要忘記開啟這個線程
35             //將mHandler與thread相關聯
36             mHandler = new Handler(thread.getLooper()){
37                 public void handleMessage(android.os.Message msg) {
38                     Log.d("當前子線程是----->", Thread.currentThread()+"");
39                 };
40             };
41             mHandler.sendEmptyMessage(1);//發送消息
42     }
43 
44 }

      紅色部分就是我們重寫了它的run方法。再雲運行程序,打印的結果如下:

     for循環的打印結果正常,但是為什么沒有打印出”當前子線程“呢。其實這正是我們要解釋的地方。還記得上一篇文章中實現與子線程相關聯的的Handler,我們是怎么做的嗎?沒讀過的朋友看以點擊鏈接(http://www.cnblogs.com/fuly550871915/p/4889838.html)。其實我們實現Handlei與線程的關聯正是寫在run方法中的。而對於HandlerThread這樣的線程,也是如此。我們翻看這個類的源代碼,找到它的run方法,如下:

 1  @Override
 2     public void run() {
 3         mTid = Process.myTid();
 4         Looper.prepare();
 5         synchronized (this) {
 6             mLooper = Looper.myLooper();
 7             notifyAll();
 8         }
 9         Process.setThreadPriority(mPriority);
10         onLooperPrepared();
11         Looper.loop();
12         mTid = -1;
13     }

      在源代碼的第4行,進行了實例化自己的Looper,如果繼續追蹤源代碼翻看其getLooper方法你會發現,如果一個Handler在與HandlerThread進行綁定時,發現Looper為空,Handler則會一直等待直到Looper被創建出來為止,然后才繼續執行后續的代碼。所以我們重寫了HandlerThread的run方法,肯定就不會去創建Looper對象,那么綁定的Handler就會永遠處於等待狀態,自然而然就不會打印出”當前子線程“信息了。這也是為什么我們要使用HandlerThread這個特殊的線程,因為使用這個,我們不必關心多線程會混亂,Looper會為空等一系列問題,只要去關心我們要實現的邏輯就行了。

好了,現在做一下簡單的總結吧。

 

小結:
1. Handler與哪個線程的Looper相關聯,那么它的消息處理邏輯就在與之相關的線程中執行,相應的消息的走向也就在相關聯的MessageQueue中。(最常見的就是Handler與主線程關聯,那么接收Looper回傳的消息后的邏輯就會在主線程中執行)
2. 當主線程中需要與子線程進行通信時(比如將耗時操作放在子線程中),建議使用HandlerThread。同時要注意,千萬不要去重寫它的run方法。

二、一個主線程與子線程互相通信的例子

     知識點都說完了。下面我們來寫一個具體的例子實踐一下吧。新建一個項目,修改它的MainActivity代碼,如下:

 1 package com.example.handldertest;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.os.Handler;
 6 import android.os.HandlerThread;
 7 import android.util.Log;
 8 import android.widget.TextView;
 9 
10 public class ThreadHandlerActivity extends Activity{
11     
12 
13     
14     private TextView tv;
15 
16     private Handler mHandler;//與子線程關聯的Handler
17     private Handler handler;//與主線程關聯的Handler
18     
19     protected void onCreate(Bundle savedInstanceState) {
20             super.onCreate(savedInstanceState);
21             tv = new TextView(this);
22             tv.setText("Handler實驗");
23             setContentView(tv);
24         
25             //實例化一個特殊的線程HandlerThread,必須給其指定一個名字
26             HandlerThread thread = new HandlerThread("handler thread");
27             thread.start();//千萬不要忘記開啟這個線程
28             //將mHandler與thread相關聯
29             mHandler = new Handler(thread.getLooper()){
30                 public void handleMessage(android.os.Message msg) {
31                     Log.d("我是子線程----->", Thread.currentThread()+"");
32                     handler.sendEmptyMessage(1);//發送消息給主線程
33                 };
34             };
35             
36             handler = new Handler(){
37                 public void handleMessage(android.os.Message msg) {
38                     Log.d("我是主線程----->", Thread.currentThread()+"");
39                     mHandler.sendEmptyMessage(1);//發送消息給子線程
40                 };
41             };
42             mHandler.sendEmptyMessage(1);//發送消息
43             handler.sendEmptyMessage(1);//發送消息
44     }
45 
46 }

          注釋很詳細,不解釋 了。運行程序,結果如下:

         這樣子,就會一直循環下去,輪流打印出主線程和子線程。

 


免責聲明!

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



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