1. 程式人生 > >Android中的“智慧指標”——RefBase

Android中的“智慧指標”——RefBase

圖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,它降低了建立和使用帶有訊息佇列的執行緒的難度。