在平時寫程序時,我們一般都是在子線程中向主線程發送消息,從而完成請求的處理,這個很常見,不用多說了。那么有時候,我們也可能碰到這樣子的一種需求:需要主線程來向子線程發送消息,希望子線程來完成什么任務。如果這樣子應該怎么做呢?這就是這篇文章將要討論的內容。
一、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 }
注釋很詳細,不解釋 了。運行程序,結果如下:
這樣子,就會一直循環下去,輪流打印出主線程和子線程。