Android多執行緒(二)
在上一篇中,我簡單說了用AsyncTask來完成簡單非同步任務,但AsyncTask是把所有的非同步任務放到一個佇列中依次在同一個執行緒中執行。這樣就帶來一個問題,它無法處理那些耗時長、需要並行的的任務。如何處理這個難題呢?一是自己開啟執行緒然後處理執行緒通訊問題,二是使用HandlerThread這一便捷類來處理。萬變不離其宗,先來說明Android執行緒、及執行緒通訊的原理,然後對於那些便捷的API自然就懂了。
二、Thread 與 Handler
本節涉及的概念較多,有Thread,Handler,Looper,Message,MessageQuene,對於Looper和MessageQueue只是簡單的談及它們線上程通訊中的作用,Thread,Handler及Message我會盡力講清楚些。
1.Thread基礎:
1)參考文件:http://developer.android.com/reference/java/lang/Thread.html
我包括我的好些朋友學東西時都會忽略官方文件的重要性,其中很大的原因是因為英文的緣故吧,但其實看懂文件要求英語能力並不高。看別人寫的文章、部落格、書籍,聽別人講技術,那始終是經過他人過濾後的知識,而這一切未必是你需要的。前車之鑑固然重要,但並不是每一個老人的話都得聽。歪果仁寫東西有個特點就是他不僅把原理給你講清楚了,還愛舉列子,該怎麼做不建議怎麼做都會涉及。說了這麼多,就是建議自己去看看Android的開發文件,從Guide到Reference內化出自己的知識體系。
2)簡單介紹:
看Android的Thread繼承樹,我們就知道它完全繼承了Java的執行緒體系,在這就不贅言過多的Thread細節了,我就如何開啟一個執行緒講一講。開啟一個新執行緒有兩種方法,第一種是拓展Thread類,在子類中重寫run()方法;第二種是宣告Thread物件時傳入一個Runnable物件,因為只涉及到一個抽象方法,Runnable物件可以用jdk8的Lambda表示式來代替,這樣使得執行緒的建立變得更加簡單。建立好了執行緒後,如何讓執行緒開始執行呢?呼叫Thread.run()即可讓執行緒進入待執行佇列,當獲得CPU時間時它就執行起來了。具體的用法例子會在後面的示例中展現。
2.Handler基礎:
1)參考文件:http://developer.android.com/reference/android/os/Handler.html
2)概述:
之前看過一部美劇《天蠍計劃》,在介紹團隊負責人時,這樣說道”He is our goverment handler“。Android中的Handler就是這樣一個概念,它是執行緒通訊的傳送者和處理者,執行緒要進行通訊時會讓handler發出相應的訊息,通過Looper傳遞,Handler發出的訊息會在目的執行緒中得以執行。再舉個栗子吧,這是我在《Android程式設計權威指南》中看到的,它是這麼描述執行緒通訊的:兩個執行緒的通訊就好像現實中的兩個人通過信件來通訊,訊息佇列(MessageQueue)相對於通訊時候的信箱;Message(訊息)相當於信件;Looper相當於郵遞員,它是MessageQueue的操作者;Handler時執行緒通訊的發出者和處理者;每當Thread想要進行通訊,它會讓Handler投遞一個Message給相應的MessageQueue,Looper會一直迴圈將MessageQueue裡的Message發向它的目的地,到達目的地後Looper通知相應的Handler來處理訊息。
每一個Handler都是和唯一的Looper物件繫結的,也就是說一個Handler既僅可以個一個Looper繫結,但一個Looper可以有好幾個Handler與之關聯。Looper操作的MessageQueue是Handler取得訊息進行處理和發出訊息的地方。
3)使用介紹:
前面說過每一個Handler都是和特別的Looper繫結好的,同時Handler又是處理訊息的地方,所以Handler中既要說明和哪個Looper繫結,又要告知怎麼處理訊息。所以Handler有4個構造方法,下面我來一一介紹:
- Handler()這是無引數構造方法,它預設和當前執行緒的Looper繫結,未指定訊息的處理方式。
- Handler(Looper looper)它需要一個Looper物件作為引數傳入,構成出來的Handler物件將和給定的Looper物件繫結。
- Handler(Handler.Callback callback)它需要一個Handler.Callback物件作為引數傳入,構造處理的物件和當前執行緒的Looper繫結並用傳入的Handler.Callback物件來處理訊息。
- Handler(Looper looper,(Handler.Callback callback)這是第二種和第三種的結合,構成出一個和指定Looper繫結並用指定Callback的回撥方法處理訊息的Handler對像
- 還有一種使用Handler的方法就是自己拓展Handler類,在子類中實現handlerMessage(Message msg)(這就是介面Callback中的抽象方法),這也是我用的比較多的方式。
Handler的使用就是傳送和處理訊息,處理訊息是在Callback介面中定義好的,當Looper操作的MessageQueue中有訊息時,Looper對通知所有與它繫結的Handler呼叫handlerMessage(Message msg)去處理訊息。那怎麼傳送訊息呢?Hander中有一個方法叫sendMessage(Message msg),當呼叫這個方法後Handler就往Looper操作的MessageQueue中投遞一個Message物件,傳送訊息就算是完成了。簡單吧?so easy!
關於Handler的其他方法還請檢視文件,將主幹的部分弄清楚了,指端末節隨著使用慢慢就熟絡了。一口氣吃不成胖子。對了,下面的Message部分還會提及Handler的一些內容。
3.Message基礎:
1)參考文件:http://developer.android.com/reference/android/os/Message.html
2)使用介紹:
執行緒通訊既然叫通訊肯定有一個訊息的載體,Message就是這個訊息的載體,它包含了執行緒想要通訊的全部內容,比如執行緒執行後得到的結果就和可以包含在Message中。關於Message的構造本來可以按正常的方式構造(就是new一個Message物件,詞窮不知道怎麼說,就叫它”正常的方式“O(∩_∩)O~),但官方推薦的做法是通過Handler.obtainMessage()獲得Message物件,因為這樣的Message是從即將被回收的Message中取得的,會避免GC的反覆執行和減少執行時的記憶體消耗。
Message是訊息的攜帶者,它有許多的攜帶訊息的方法,比如setData(Bundle),Message.obj,Message.arg1,Message.arg2等等,需要特別說明的是Message裡有一個公開的整形的全域性變數what,即Message.what,它一般用來闡述訊息的型別,Handler的handlerMessage(Message msg)通常會先檢驗Message的what變數,然後在決定如何處理。畢竟一個應用中和主執行緒通訊的不可能只用一個執行緒,一種訊息。
4.Looper與MessageQueue簡單介紹:
1)參考文件:
Looper:http://developer.android.com/reference/android/os/Looper.html
MessageQueue:http://developer.android.com/reference/android/os/MessageQueue.html
2)簡單介紹:
需要注意的地方就是,一個普通Thread建立時是沒有Looper物件和它關聯的,我們必須線上程的建立中進行關聯,具體做法就是在Thread的建立時呼叫Looper.prepare()進行繫結,呼叫Looper.loop()使得與執行緒繫結的Looper物件開始工作。Looper中有一個巨好用的方法,Looper.getMainLooper(),這個方法直接返回當前應用的UI執行緒的Looper物件,有了Looper物件就可以往主執行緒發訊息了,一會我在示例中會用到這樣方法。
關於MessageQueue,其實線上程通訊中我們並不直接使用它,只需知道我們通過Handler傳送出去的訊息都是放在它裡的就行了,它是一個”第層次“的物件,我們也不能直接往它裡新增訊息。但對於理解整個執行緒通訊過程還是很重要的。
5.實踐——示例
說了這麼多,是時候用實踐檢驗了!先說說我打算怎麼做吧!我打算從UI執行緒向非UI執行緒(是不是叫主執行緒和子執行緒更好?)發一個訊息,然後在非UI執行緒中處理這個訊息(這裡我只是列印一下日誌),然後從非UI執行緒向主執行緒傳送一個訊息,然後在UI執行緒中處理這個訊息(也是簡單的列印一下)。好像有些偷懶,但真正使用也大致是這樣的,就是把列印換成具體的任務。好了,開始吧!
先給出佈局吧!
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:paddingBottom="@dimen/activity_vertical_margin" 8 android:paddingLeft="@dimen/activity_horizontal_margin" 9 android:paddingRight="@dimen/activity_horizontal_margin" 10 android:paddingTop="@dimen/activity_vertical_margin" 11 tools:context=".MainActivity"> 12 13 <TextView 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:layout_alignParentTop="true" 17 android:layout_centerHorizontal="true" 18 android:text="Thread和Handler"/> 19 20 <Button 21 android:id="@+id/send_message" 22 android:layout_width="match_parent" 23 android:layout_height="wrap_content" 24 android:layout_alignParentBottom="true" 25 android:text="傳送訊息"/> 26 27 <Button 28 android:id="@+id/startThread" 29 android:layout_width="match_parent" 30 android:layout_height="wrap_content" 31 android:layout_above="@id/send_message" 32 android:text="開啟子執行緒"/> 33 34 </RelativeLayout>
佈局很簡單,就是一個textview和兩個button,一個button開啟執行緒,並接受來自子執行緒的資訊,另一個從主執行緒發出訊息給子執行緒。
下面給出程式碼邏輯:
第一部分是子執行緒的程式碼:
1 package comfallblank.github.threadandhandler; 2 3 import android.os.Handler; 4 import android.os.Looper; 5 import android.os.Message; 6 import android.util.Log; 7 8 /** 9 * Created by fallb on 2015/10/7. 10 */ 11 public class MyThread extends Thread { 12 public static final int MSG_WORKER_THREAD = 100; 13 private static final String TAG = "MyThread"; 14 15 private Handler mWorkerHandler; 16 private Handler mMainHandler; 17 18 public MyThread(Handler handler) { 19 mMainHandler = handler; 20 mWorkerHandler = new Handler(){ 21 @Override 22 public void handleMessage(Message msg) { 23 if(msg.what == MainActivity.MSG_MAIN){ 24 Log.d(TAG,"Message:"+msg.obj); 25 } 26 } 27 }; 28 } 29 30 @Override 31 public void run() { 32 Looper.prepare(); 33 Message msg = mMainHandler.obtainMessage(); 34 msg.what = MyThread.MSG_WORKER_THREAD; 35 msg.obj="子執行緒發出的訊息"; 36 mMainHandler.sendMessage(msg); 37 38 Looper.loop(); 39 } 40 41 public Handler getWorkerHandler() { 42 return mWorkerHandler; 43 } 44 }
第二份是主執行緒的:
1 package comfallblank.github.threadandhandler; 2 3 import android.os.Bundle; 4 import android.os.Handler; 5 import android.os.Message; 6 import android.support.v7.app.AppCompatActivity; 7 import android.util.Log; 8 import android.view.View; 9 import android.widget.Button; 10 11 public class MainActivity extends AppCompatActivity { 12 public static final int MSG_MAIN = 100; 13 private static final String TAG = "MainActivity"; 14 15 private Button mStartThread; 16 private Button mSendMessage; 17 private Handler mHandler = new MyHandler(); 18 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_main); 23 24 final MyThread thread = new MyThread(mHandler); 25 mStartThread = (Button) findViewById(R.id.startThread); 26 mStartThread.setOnClickListener(new View.OnClickListener() { 27 @Override 28 public void onClick(View view) { 29 Log.d(TAG, "開啟執行緒"); 30 thread.start(); 31 } 32 }); 33 mSendMessage = (Button) findViewById(R.id.send_message); 34 mSendMessage.setOnClickListener(new View.OnClickListener() { 35 @Override 36 public void onClick(View view) { 37 Handler workerHandler = thread.getWorkerHandler(); 38 Message msg = workerHandler.obtainMessage(); 39 msg.what = MSG_MAIN; 40 msg.obj = "來自主執行緒的訊息"; 41 workerHandler.sendMessage(msg); 42 } 43 }); 44 45 } 46 47 class MyHandler extends Handler { 48 @Override 49 public void handleMessage(Message msg) { 50 if (msg.what == MyThread.MSG_WORKER_THREAD) { 51 Log.d(TAG,"Message:"+msg.obj); 52 } 53 } 54 } 55 }
Logcat列印日誌如下:
10-07 19:34:34.424 19671-19671/comfallblank.github.threadandhandler D/MainActivity: 開啟執行緒
10-07 19:34:34.454 19671-19671/comfallblank.github.threadandhandler D/MainActivity: Message:子執行緒發出的訊息
10-07 19:34:37.267 19671-19671/comfallblank.github.threadandhandler D/MyThread: Message:來自主執行緒的訊息
6.關於Handler再說兩句:
Handler物件除了發出訊息外,還有一族方法,來發出一個Runnable物件,Handler.post(Runnable runnable)及時其中的一個,它向MessageQueue中新增這個Runnable物件,然後目的執行緒的MessageQueue會執行該方法。
關於Thread、Handler的內容就說到這裡了,還是如前上一篇所說,本文只起到拋磚引玉的作用,多有偏頗,往交流指正。