1. 程式人生 > >Android多執行緒(二)

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的內容就說到這裡了,還是如前上一篇所說,本文只起到拋磚引玉的作用,多有偏頗,往交流指正。