Android中的“智慧指標”——RefBase
阿新 • • 發佈:2019-01-26
圖5-1 執行緒和訊息處理的原理圖
從圖中可以看出:
事件源把待處理的訊息加入到訊息佇列中,一般是加至佇列尾,一些優先順序高的訊息也可以加至佇列頭。事件源提交的訊息可以是按鍵、觸控式螢幕等物理事件產生的訊息,也可以是系統或應用程式本身發出的請求訊息。
處理執行緒不斷從訊息佇列頭中取出訊息並處理,事件源可以把優先順序高的訊息放到佇列頭,這樣,優先順序高的訊息就會首先被處理。
在Android系統中,這些工作主要由Looper和Handler來實現:
Looper類,用於封裝訊息迴圈,並且有一個訊息佇列。
Handler類,有點像輔助類,它封裝了訊息投遞、訊息處理等介面。
Looper類是其中的關鍵。先來看看它是怎麼做的。
5.4.1 Looper類分析
我們以Looper使用的一個常見例子來分析這個Looper類。
[-->例子1]
//定義一個LooperThread。
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
//① 呼叫prepare。
Looper.prepare();
......
//② 進入訊息迴圈。
Looper.loop();
}
}
//應用程式使用LooperThread。
{
......
new LooperThread().start();//啟動新執行緒,執行緒函式是run
}
上面的程式碼一共有兩個關鍵呼叫(即①和②),我們對其逐一進行分析。
1. 準備好了嗎
第一個呼叫函式是Looper的prepare函式。它會做什麼工作呢?其程式碼如下所示:
[-->Looper.java]
public static final void prepare() {
//一個Looper只能呼叫一次prepare。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//構造一個Looper物件,設定到呼叫執行緒的區域性變數中。
sThreadLocal.set(new Looper());
}
//sThreadLocal定義
private static final ThreadLocal sThreadLocal = new ThreadLocal();
ThreadLocal是Java中的執行緒區域性變數類,全名應該是Thread Local Variable。我覺得它的實現和作業系統提供的執行緒本地儲存(TLS)有關係。總之,該類有兩個關鍵函式:
set:設定呼叫執行緒的區域性變數。
get:獲取呼叫執行緒的區域性變數。
注意 set/get的結果都和呼叫這個函式的執行緒有關。ThreadLocal類可參考JDK API文件或Android API文件。
根據上面的分析可知,prepare會在呼叫執行緒的區域性變數中設定一個Looper物件。這個呼叫執行緒就是LooperThread的run執行緒。先看看Looper物件的構造,其程式碼如下所示:
[-->Looper.java]
private Looper(){
//構造一個訊息佇列。
mQueue = new MessageQueue();
mRun = true;
//得到當前執行緒的Thread物件。
mThread = Thread.currentThread();
}
prepare函式很簡單,它主要乾了一件事:
在呼叫prepare的執行緒中,設定了一個Looper物件,這個Looper物件就儲存在這個呼叫執行緒的TLV中。而Looper物件內部封裝了一個訊息佇列。
也就是說,prepare函式通過ThreadLocal機制,巧妙地把Looper和呼叫執行緒關聯在一起了。要了解這樣做的目的是什麼,需要再看第二個重要函式。
2.Looper迴圈
程式碼如下所示:
[-->Looper.java]
public static final void loop() {
Looper me = myLooper();//myLooper返回儲存在呼叫執行緒TLV中的Looper物件。
//取出這個Looper的訊息佇列。
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next();
//處理訊息,Message物件中有一個target,它是Handler型別。
//如果target為空,則表示需要退出訊息迴圈。
if (msg != null) {
if (msg.target == null) {
return;
}
//呼叫該訊息的Handler,交給它的dispatchMessage函式處理。
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
//myLooper函式返回呼叫執行緒的執行緒區域性變數,也就是儲存在其中的Looper物件。
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
通過上面的分析會發現,Looper的作用是:
封裝了一個訊息佇列。
Looper的prepare函式把這個Looper和呼叫prepare的執行緒(也就是最終的處理執行緒)繫結在一起了。
處理執行緒呼叫loop函式,處理來自該訊息佇列的訊息。
當事件源向這個Looper傳送訊息的時候,其實是把訊息加到這個Looper的訊息佇列裡了。那麼,該訊息就將由和Looper繫結的處理執行緒來處理。可事件源又是怎麼向Looper訊息佇列新增訊息的呢?來看下一節。
3.Looper、Message和Handler的關係
Looper、Message和Handler之間也存在曖昧關係,不過要比RefBase那三個簡單得多,用兩句話就可以說清楚:
Looper中有一個Message佇列,裡面儲存的是一個個待處理的Message。
Message中有一個Handler,這個Handler是用來處理Message的。
其中,Handler類封裝了很多瑣碎的工作。先來認識一下這個Handler。
5.4.2 Handler分析
1.初識Handler
Handler中所包括的成員:
[-->Handler.java]
final MessageQueue mQueue;//Handler中也有一個訊息佇列。
final Looper mLooper;//也有一個Looper。
final Callback mCallback;//有一個回撥用的類。
這幾個成員變數是怎麼使用的呢?這首先得分析Handler的建構函式。Handler一共有四個建構函式,它們主要的區別是在對上面三個重要成員變數的初始化上。我們試對其進行逐一的分析。
[-->Handler.java]
//建構函式1
public Handler() {
//獲得呼叫執行緒的Looper。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(......);
}
//得到Looper的訊息佇列。
mQueue = mLooper.mQueue;
//無callback設定。
mCallback = null;
}
//建構函式2
public Handler(Callback callback) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(......);
}
//和建構函式1類似,只不過多了一個設定callback。
mQueue = mLooper.mQueue;
mCallback = callback;
}
//建構函式3
public Handler(Looper looper) {
mLooper = looper; //looper由外部傳入,是哪個執行緒的Looper不確定。
mQueue = looper.mQueue;
mCallback = null;
}
//建構函式4,和建構函式3類似,只不過多了callback設定。
public Handler(Looper looper, Callback callback) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
}
在上述建構函式中,Handler中的訊息佇列變數最終都會指向Looper的訊息佇列,Handler為何要如此做?
2. Handler的真面目
根據前面的分析可知,Handler中的訊息佇列實際就是某個Looper的訊息佇列,那麼,Handler如此安排的目的何在?
在回答這個問題之前,我先來問一個問題:
怎麼往Looper的訊息佇列插入訊息?
如果不知道Handler,這裡有一個很原始的方法可解決上面這個問題:
呼叫Looper的myQueue,它將返回訊息佇列物件MessageQueue。
構造一個Message,填充它的成員,尤其是target變數。
呼叫MessageQueue的enqueueMessage,將訊息插入訊息佇列。
這種原始方法的確很麻煩,且極容易出錯。但有了Handler後,我們的工作就變得異常簡單了。Handler更像一個輔助類,幫助我們簡化程式設計的工作。
(1)Handler和Message
Handler提供了一系列函式,幫助我們完成建立訊息和插入訊息佇列的工作。這裡只列舉其中一二。要掌握詳細的API,則需要檢視相關的文件。
//檢視訊息佇列中是否有訊息碼是what的訊息。
final boolean hasMessages(int what)
//從Handler中建立一個訊息碼是what的訊息。
final Message obtainMessage(int what)
//從訊息佇列中移除訊息碼是what的訊息。
final void removeMessages(int what)
//傳送一個只填充了訊息碼的訊息。
final boolean sendEmptyMessage(int what)
//傳送一個訊息,該訊息新增到佇列尾。
final boolean sendMessage(Message msg)
//傳送一個訊息,該訊息新增到佇列頭,所以優先順序很高。
final boolean sendMessageAtFrontOfQueue(Message msg)
只需對上面這些函式稍作分析,就能明白其他的函式。現以sendMessage為例,其程式碼如下所示:
[-->Handler.java]
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0); //呼叫sendMessageDelayed
}
[-->Handler.java]
// delayMillis是以當前呼叫時間為基礎的相對時間
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//呼叫sendMessageAtTime,把當前時間算上
return sendMessageAtTime(msg,SystemClock.uptimeMillis() + delayMillis);
}
[-->Handler.java]
//uptimeMillis 是絕對時間,即sendMessageAtTime函式處理的是絕對時間
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
//把Message的target設定為自己,然後加入到訊息佇列中
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
return sent;
}
看到上面這些函式我們可以預見,如果沒有Handler的輔助,當我們自己操作MessageQueue的enqueueMessage時,得花費多大工夫!
Handler把Message的target設為自己,是因為Handler除了封裝訊息新增等功能外還封裝了訊息處理的介面。
(2)Handler的訊息處理
剛才,我們往Looper的訊息佇列中加入了一個訊息,按照Looper的處理規則,它在獲取訊息後會呼叫target的dispatchMessage函式,再把這個訊息派發給Handler處理。Handler在這塊是如何處理訊息的呢?
[-->Handler.java]
public void dispatchMessage(Message msg) {
//如果Message本身有callback,則直接交給Message的callback處理
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果本Handler設定了mCallback,則交給mCallback處理
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//最後才是交給子類處理
handleMessage(msg);
}
}
dispatchMessage定義了一套訊息處理的優先順序機制,它們分別是:
Message如果自帶了callback處理,則交給callback處理。
Handler如果設定了全域性的mCallback,則交給mCallback處理。
如果上述都沒有,該訊息則會被交給Handler子類實現的handleMessage來處理。當然,這需要從Handler派生並重載handleMessage函式。
在通常情況下,我們一般都是採用第三種方法,即在子類中通過過載handleMessage來完成處理工作的。
至此,Handler知識基本上講解完了,可是在實際編碼過程中還有一個重要問題需要警惕,下一節內容就會談及此問題。
5.4.3 Looper和Handler的同步關係
Looper和Handler會有什麼同步關係呢?它們之間確實有同步關係,而且如果不注意此關係,定會鑄成大錯!
同步關係肯定與多執行緒有關,我們來看下面的一個例子:
[-->例子2]
//先定義一個LooperThread類
class LooperThread extends Thread {
public Looper myLooper = null;//定義一個public的成員myLooper,初值為空。
public void run() { //假設run線上程2中執行
Looper.prepare();
// myLooper必須在這個執行緒中賦值
myLooper = Looper.myLooper();
Looper.loop();
}
}
//下面這段程式碼線上程1中執行,並且會建立執行緒2
{
LooperThread lpThread= new LooperThread;
lpThread.start();//start後會建立執行緒2
Looper looper = lpThread.myLooper;//<======注意
// thread2Handler和執行緒2的Looper掛上鉤
Handler thread2Handler = new Handler(looper);
//sendMessage傳送的訊息將由執行緒2處理
threadHandler.sendMessage(...)
}
上面這段程式碼的目的很簡單:
執行緒1中建立執行緒2,並且執行緒2通過Looper處理訊息。
執行緒1中得到執行緒2的Looper,並且根據這個Looper建立一個Handler,這樣傳送給該Handler的訊息將由執行緒2處理。
但很可惜,上面的程式碼是有問題的。如果我們熟悉多執行緒,就會發現標有“注意”的那行程式碼存在著嚴重問題。myLooper的建立是線上程2中,而looper的賦值線上程1中,很有可能此時執行緒2的run函式還沒來得及給myLooper賦值,這樣執行緒1中的looper將取到myLooper的初值,也就是looper等於null。另外,
Handler thread2Handler = new Handler(looper) 不能替換成
Handler thread2Handler = new Handler(Looper.myLooper())
這是因為,myLooper返回的是呼叫執行緒的Looper,即Thread1的Looper,而不是我們想要的Thread2的Looper。
對這個問題,可以採用同步的方式進行處理。你是不是有點迫不及待地想完善這個例子了?其實Android早就替我們想好了,它提供了一個HandlerThread來解決這個問題。
5.4.4 HandlerThread介紹
HandlerThread完美地解決了myLooper可能為空的問題。下面來看看它是怎麼做的,程式碼如下所示:
[-->HandlerThread]
public class HandlerThread extends Thread{
//執行緒1呼叫getLooper來獲得新執行緒的Looper
public Looper getLooper() {
......
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//如果新執行緒還未建立Looper,則等待
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
//執行緒2執行它的run函式,looper就是在run執行緒裡建立的。
public void run() {
mTid = Process.myTid();
Looper.prepare(); //建立這個執行緒上的Looper
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();//通知取Looper的執行緒1,此時Looper已經建立好了。
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
HandlerThread很簡單,小小的wait/ notifyAll就解決了我們的難題。為了避免重複發明輪子,我們還是多用HandlerThread類吧!
5.5 本章小結
本章主要分析了Android程式碼中最常見的幾個類:其中在Native層包括與物件生命週期相關的RefBase、sp、wp、LightRefBase類,以及Android為多執行緒程式設計提供的Thread類和相關的同步類;Java層則包括使用最為廣泛的Handler類和Looper類。另外,還分析了類HandlerThread,它降低了建立和使用帶有訊息佇列的執行緒的難度。
從圖中可以看出:
事件源把待處理的訊息加入到訊息佇列中,一般是加至佇列尾,一些優先順序高的訊息也可以加至佇列頭。事件源提交的訊息可以是按鍵、觸控式螢幕等物理事件產生的訊息,也可以是系統或應用程式本身發出的請求訊息。
處理執行緒不斷從訊息佇列頭中取出訊息並處理,事件源可以把優先順序高的訊息放到佇列頭,這樣,優先順序高的訊息就會首先被處理。
在Android系統中,這些工作主要由Looper和Handler來實現:
Looper類,用於封裝訊息迴圈,並且有一個訊息佇列。
Handler類,有點像輔助類,它封裝了訊息投遞、訊息處理等介面。
Looper類是其中的關鍵。先來看看它是怎麼做的。
5.4.1 Looper類分析
我們以Looper使用的一個常見例子來分析這個Looper類。
[-->例子1]
//定義一個LooperThread。
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
//① 呼叫prepare。
Looper.prepare();
......
//② 進入訊息迴圈。
Looper.loop();
}
}
//應用程式使用LooperThread。
{
......
new LooperThread().start();//啟動新執行緒,執行緒函式是run
}
上面的程式碼一共有兩個關鍵呼叫(即①和②),我們對其逐一進行分析。
1. 準備好了嗎
第一個呼叫函式是Looper的prepare函式。它會做什麼工作呢?其程式碼如下所示:
[-->Looper.java]
public static final void prepare() {
//一個Looper只能呼叫一次prepare。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//構造一個Looper物件,設定到呼叫執行緒的區域性變數中。
sThreadLocal.set(new Looper());
}
//sThreadLocal定義
private static final ThreadLocal sThreadLocal = new ThreadLocal();
ThreadLocal是Java中的執行緒區域性變數類,全名應該是Thread Local Variable。我覺得它的實現和作業系統提供的執行緒本地儲存(TLS)有關係。總之,該類有兩個關鍵函式:
set:設定呼叫執行緒的區域性變數。
get:獲取呼叫執行緒的區域性變數。
注意 set/get的結果都和呼叫這個函式的執行緒有關。ThreadLocal類可參考JDK API文件或Android API文件。
根據上面的分析可知,prepare會在呼叫執行緒的區域性變數中設定一個Looper物件。這個呼叫執行緒就是LooperThread的run執行緒。先看看Looper物件的構造,其程式碼如下所示:
[-->Looper.java]
private Looper(){
//構造一個訊息佇列。
mQueue = new MessageQueue();
mRun = true;
//得到當前執行緒的Thread物件。
mThread = Thread.currentThread();
}
prepare函式很簡單,它主要乾了一件事:
在呼叫prepare的執行緒中,設定了一個Looper物件,這個Looper物件就儲存在這個呼叫執行緒的TLV中。而Looper物件內部封裝了一個訊息佇列。
也就是說,prepare函式通過ThreadLocal機制,巧妙地把Looper和呼叫執行緒關聯在一起了。要了解這樣做的目的是什麼,需要再看第二個重要函式。
2.Looper迴圈
程式碼如下所示:
[-->Looper.java]
public static final void loop() {
Looper me = myLooper();//myLooper返回儲存在呼叫執行緒TLV中的Looper物件。
//取出這個Looper的訊息佇列。
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next();
//處理訊息,Message物件中有一個target,它是Handler型別。
//如果target為空,則表示需要退出訊息迴圈。
if (msg != null) {
if (msg.target == null) {
return;
}
//呼叫該訊息的Handler,交給它的dispatchMessage函式處理。
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
//myLooper函式返回呼叫執行緒的執行緒區域性變數,也就是儲存在其中的Looper物件。
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
通過上面的分析會發現,Looper的作用是:
封裝了一個訊息佇列。
Looper的prepare函式把這個Looper和呼叫prepare的執行緒(也就是最終的處理執行緒)繫結在一起了。
處理執行緒呼叫loop函式,處理來自該訊息佇列的訊息。
當事件源向這個Looper傳送訊息的時候,其實是把訊息加到這個Looper的訊息佇列裡了。那麼,該訊息就將由和Looper繫結的處理執行緒來處理。可事件源又是怎麼向Looper訊息佇列新增訊息的呢?來看下一節。
3.Looper、Message和Handler的關係
Looper、Message和Handler之間也存在曖昧關係,不過要比RefBase那三個簡單得多,用兩句話就可以說清楚:
Looper中有一個Message佇列,裡面儲存的是一個個待處理的Message。
Message中有一個Handler,這個Handler是用來處理Message的。
其中,Handler類封裝了很多瑣碎的工作。先來認識一下這個Handler。
5.4.2 Handler分析
1.初識Handler
Handler中所包括的成員:
[-->Handler.java]
final MessageQueue mQueue;//Handler中也有一個訊息佇列。
final Looper mLooper;//也有一個Looper。
final Callback mCallback;//有一個回撥用的類。
這幾個成員變數是怎麼使用的呢?這首先得分析Handler的建構函式。Handler一共有四個建構函式,它們主要的區別是在對上面三個重要成員變數的初始化上。我們試對其進行逐一的分析。
[-->Handler.java]
//建構函式1
public Handler() {
//獲得呼叫執行緒的Looper。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(......);
}
//得到Looper的訊息佇列。
mQueue = mLooper.mQueue;
//無callback設定。
mCallback = null;
}
//建構函式2
public Handler(Callback callback) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(......);
}
//和建構函式1類似,只不過多了一個設定callback。
mQueue = mLooper.mQueue;
mCallback = callback;
}
//建構函式3
public Handler(Looper looper) {
mLooper = looper; //looper由外部傳入,是哪個執行緒的Looper不確定。
mQueue = looper.mQueue;
mCallback = null;
}
//建構函式4,和建構函式3類似,只不過多了callback設定。
public Handler(Looper looper, Callback callback) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
}
在上述建構函式中,Handler中的訊息佇列變數最終都會指向Looper的訊息佇列,Handler為何要如此做?
2. Handler的真面目
根據前面的分析可知,Handler中的訊息佇列實際就是某個Looper的訊息佇列,那麼,Handler如此安排的目的何在?
在回答這個問題之前,我先來問一個問題:
怎麼往Looper的訊息佇列插入訊息?
如果不知道Handler,這裡有一個很原始的方法可解決上面這個問題:
呼叫Looper的myQueue,它將返回訊息佇列物件MessageQueue。
構造一個Message,填充它的成員,尤其是target變數。
呼叫MessageQueue的enqueueMessage,將訊息插入訊息佇列。
這種原始方法的確很麻煩,且極容易出錯。但有了Handler後,我們的工作就變得異常簡單了。Handler更像一個輔助類,幫助我們簡化程式設計的工作。
(1)Handler和Message
Handler提供了一系列函式,幫助我們完成建立訊息和插入訊息佇列的工作。這裡只列舉其中一二。要掌握詳細的API,則需要檢視相關的文件。
//檢視訊息佇列中是否有訊息碼是what的訊息。
final boolean hasMessages(int what)
//從Handler中建立一個訊息碼是what的訊息。
final Message obtainMessage(int what)
//從訊息佇列中移除訊息碼是what的訊息。
final void removeMessages(int what)
//傳送一個只填充了訊息碼的訊息。
final boolean sendEmptyMessage(int what)
//傳送一個訊息,該訊息新增到佇列尾。
final boolean sendMessage(Message msg)
//傳送一個訊息,該訊息新增到佇列頭,所以優先順序很高。
final boolean sendMessageAtFrontOfQueue(Message msg)
只需對上面這些函式稍作分析,就能明白其他的函式。現以sendMessage為例,其程式碼如下所示:
[-->Handler.java]
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0); //呼叫sendMessageDelayed
}
[-->Handler.java]
// delayMillis是以當前呼叫時間為基礎的相對時間
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//呼叫sendMessageAtTime,把當前時間算上
return sendMessageAtTime(msg,SystemClock.uptimeMillis() + delayMillis);
}
[-->Handler.java]
//uptimeMillis 是絕對時間,即sendMessageAtTime函式處理的是絕對時間
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
//把Message的target設定為自己,然後加入到訊息佇列中
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
return sent;
}
看到上面這些函式我們可以預見,如果沒有Handler的輔助,當我們自己操作MessageQueue的enqueueMessage時,得花費多大工夫!
Handler把Message的target設為自己,是因為Handler除了封裝訊息新增等功能外還封裝了訊息處理的介面。
(2)Handler的訊息處理
剛才,我們往Looper的訊息佇列中加入了一個訊息,按照Looper的處理規則,它在獲取訊息後會呼叫target的dispatchMessage函式,再把這個訊息派發給Handler處理。Handler在這塊是如何處理訊息的呢?
[-->Handler.java]
public void dispatchMessage(Message msg) {
//如果Message本身有callback,則直接交給Message的callback處理
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果本Handler設定了mCallback,則交給mCallback處理
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//最後才是交給子類處理
handleMessage(msg);
}
}
dispatchMessage定義了一套訊息處理的優先順序機制,它們分別是:
Message如果自帶了callback處理,則交給callback處理。
Handler如果設定了全域性的mCallback,則交給mCallback處理。
如果上述都沒有,該訊息則會被交給Handler子類實現的handleMessage來處理。當然,這需要從Handler派生並重載handleMessage函式。
在通常情況下,我們一般都是採用第三種方法,即在子類中通過過載handleMessage來完成處理工作的。
至此,Handler知識基本上講解完了,可是在實際編碼過程中還有一個重要問題需要警惕,下一節內容就會談及此問題。
5.4.3 Looper和Handler的同步關係
Looper和Handler會有什麼同步關係呢?它們之間確實有同步關係,而且如果不注意此關係,定會鑄成大錯!
同步關係肯定與多執行緒有關,我們來看下面的一個例子:
[-->例子2]
//先定義一個LooperThread類
class LooperThread extends Thread {
public Looper myLooper = null;//定義一個public的成員myLooper,初值為空。
public void run() { //假設run線上程2中執行
Looper.prepare();
// myLooper必須在這個執行緒中賦值
myLooper = Looper.myLooper();
Looper.loop();
}
}
//下面這段程式碼線上程1中執行,並且會建立執行緒2
{
LooperThread lpThread= new LooperThread;
lpThread.start();//start後會建立執行緒2
Looper looper = lpThread.myLooper;//<======注意
// thread2Handler和執行緒2的Looper掛上鉤
Handler thread2Handler = new Handler(looper);
//sendMessage傳送的訊息將由執行緒2處理
threadHandler.sendMessage(...)
}
上面這段程式碼的目的很簡單:
執行緒1中建立執行緒2,並且執行緒2通過Looper處理訊息。
執行緒1中得到執行緒2的Looper,並且根據這個Looper建立一個Handler,這樣傳送給該Handler的訊息將由執行緒2處理。
但很可惜,上面的程式碼是有問題的。如果我們熟悉多執行緒,就會發現標有“注意”的那行程式碼存在著嚴重問題。myLooper的建立是線上程2中,而looper的賦值線上程1中,很有可能此時執行緒2的run函式還沒來得及給myLooper賦值,這樣執行緒1中的looper將取到myLooper的初值,也就是looper等於null。另外,
Handler thread2Handler = new Handler(looper) 不能替換成
Handler thread2Handler = new Handler(Looper.myLooper())
這是因為,myLooper返回的是呼叫執行緒的Looper,即Thread1的Looper,而不是我們想要的Thread2的Looper。
對這個問題,可以採用同步的方式進行處理。你是不是有點迫不及待地想完善這個例子了?其實Android早就替我們想好了,它提供了一個HandlerThread來解決這個問題。
5.4.4 HandlerThread介紹
HandlerThread完美地解決了myLooper可能為空的問題。下面來看看它是怎麼做的,程式碼如下所示:
[-->HandlerThread]
public class HandlerThread extends Thread{
//執行緒1呼叫getLooper來獲得新執行緒的Looper
public Looper getLooper() {
......
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//如果新執行緒還未建立Looper,則等待
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
//執行緒2執行它的run函式,looper就是在run執行緒裡建立的。
public void run() {
mTid = Process.myTid();
Looper.prepare(); //建立這個執行緒上的Looper
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();//通知取Looper的執行緒1,此時Looper已經建立好了。
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
HandlerThread很簡單,小小的wait/ notifyAll就解決了我們的難題。為了避免重複發明輪子,我們還是多用HandlerThread類吧!
5.5 本章小結
本章主要分析了Android程式碼中最常見的幾個類:其中在Native層包括與物件生命週期相關的RefBase、sp、wp、LightRefBase類,以及Android為多執行緒程式設計提供的Thread類和相關的同步類;Java層則包括使用最為廣泛的Handler類和Looper類。另外,還分析了類HandlerThread,它降低了建立和使用帶有訊息佇列的執行緒的難度。