Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android消息處理機制源碼分析(一):整體過程

Android消息處理機制源碼分析(一):整體過程

編輯:關於Android編程

什麼是Android消息機制?從開發者使用的角度,消息機制只有一個上層接口,就是我們經常使用的Handler,通過Handler可以輕松的將任務切換到Handler所在的線程去執行,而最常見的做法相信每個開發者都用過,就是更新UI。Android的UI控件的操作只能在主線程(也叫UI線程)中執行,但我們經常在子線程或者線程池做一些耗時或者聯網的工作,當工作完成我們希望在UI中做一些更新,比如關閉對話框、改變按鈕顯示等等。那麼最常見也是比較容易的方法就是創建一個Handler對象,將操作在Handler的handleMessage函數中執行(也就是在主線程中),只需要在子線程發送消息,就可以輕松切換到主線程。當然我們也可以看出Handler不是專門來做此操作才出現的,只是非常適合這個情況,那麼Handler是如何輕松切換到主線程的呢?為什麼Handler就可以在主線程中創建呢?我們從源碼的角度來做一次全面的分析。

一、消息機制的角色分析
首先我們介紹一下消息機制的幾位主人公,正是有它們的通力合作,消息機制才能正常的運行:
1、Handler:處理器,負責的內容是消息的發送和具體處理流程,一般使用時由開發者重寫handleMessage函數,根據自定義的不同message做不同的UI更新操作;
2、Message:消息對象,代表一個消息實體,存放消息的基本內容;
3、MessageQueue:消息隊列,按順序存放消息實體,由單鏈表實現,被Looper(4)所使用(一個Looper具有一個消息隊列);
4、Looper:循環器,也是我們的消息機制的主角,一個線程至多具有一個並且與線程一一對應(如何理解等會來說),負責的內容是不斷從消息隊列取出放入的消息並且交由消息對應的Handler進行處理(Handler的具體handleMessage操作);
5、ThreadLocal:線程本地數據,是一個線程內部的數據存儲類,為每個線程存儲屬於自己的獨立的數據。

二、消息機制總覽
我們來用最簡短的語言說明一下消息循環的整個過程,有個整體性的認識,之後再進行逐一的進行源碼分析。
1、首先,我們知道ThreadLocal是線程內部的數據存儲類,一個線程對應一個自己獨一無二的數據,而我們的主角Looper就是這樣一個對象,每個線程都可以有自己獨一無二的Looper,而Looper自己具有一個屬於自己的MessageQueue,它不斷地從MessageQueue中取Message(注意,Looper的代碼是運行在自己對應的線程中的),如果MessageQueue中沒有消息,Looper只會不斷地循環嘗試取消息(阻塞)。
2、這時,我們在主線程創建了Handler對象,它需要它所在的線程(這裡是主線程)所擁有的Looper對象(也就是說,沒有Looper的線程創建不了Handler,後面我們也會看到具體代碼),一旦我們用Handler發送了消息(可以在別的線程中,這裡假設在某個子線程中),Handler就會把這個消息放入自己擁有的Looper對象的屬於這個Looper對象的MessageQueue中(這句有點拗口,就是放入Looper的MessageQueue中)。
3、我們已經知道Looper會不斷地嘗試從自己的MessageQueue中取出消息,我們剛剛放入的消息立刻被Looper取出來了,它得到了消息就執行了發出消息的Handler(也就是2過程我們所創建的)的消息處理函數handleMessage,我們編寫的UI更新操作就在Looper對象的代碼中執行了,而我們剛才也說了這個Looper運行在我們創建Handler的線程中的,也就是主線程(UI線程),那這樣一來就達到了我們的目標,成功的把代碼執行從子線程切換到了主線程中,這個過程我們也就有個總覽了,下面我們進行源碼的一步一步分析。

三、MessageQueue與Looper
1、MessageQueue
MessageQueue的作用我們已經大致了解了,就是一個可以存放消息和可以取消息的消息隊列,那麼它主要的兩個操作就是插入和讀取,分別對應的函數就是enqueueMessage函數和next函數,分別是用作插入消息和讀取並刪除消息,我們來看一下具體的實現源碼:

(1)enqueueMessage方法

final boolean enqueueMessage(Message msg, long when) {  
    if (msg.isInUse()) {  
        throw new AndroidRuntimeException(msg + " This message is already in use.");  
    }  
    if (msg.target == null) {  
        throw new AndroidRuntimeException("Message must have a target.");  
    }  

    boolean needWake;  
    synchronized (this) {  
        if (mQuiting) {  
            return false;  
        }  

        msg.when = when;  
        Message p = mMessages;  
        if (p == null || when == 0 || when < p.when) {  
            // New head, wake up the event queue if blocked.  
            msg.next = p;  
            mMessages = msg;  
            needWake = mBlocked;  
        } else {  
            // Inserted within the middle of the queue.  Usually we don't have to wake  
            // up the event queue unless there is a barrier at the head of the queue  
            // and the message is the earliest asynchronous message in the queue.  
            needWake = mBlocked && p.target == null && msg.isAsynchronous();  
            Message prev;  
            for (;;) {  
                prev = p;  
                p = p.next;  
                if (p == null || when < p.when) {  
                    break;  
                }  
                if (needWake && p.isAsynchronous()) {  
                    needWake = false;  
                }  
            }  
            msg.next = p; // invariant: p == prev.next  
            prev.next = msg;  
        }  
    }  
    if (needWake) {  
        nativeWake(mPtr);  
    }  
    return true;  
}  

首先,進行消息的合理性的檢查,防止兩個非法情況:消息已經在使用中和消息沒有target(也就是我們發出消息的Handler,它是作為target最終要來真正做相應處理);之後,進行單鏈表的插入操作。插入完成之後,我們會看到這樣一個操作:

if (needWake) {  
    nativeWake(mPtr);  
}  

這個needWake和本地方法nativeWake(mPtr)有一些關系,同樣有關的還有插入過程的一些if語句,但這裡對我們了解整體機制沒有非常重要的作用,本地方法也非常復雜,要與next方法中的本地方法一起講解,在講完整體機制後我們再詳細介紹。

(2)next方法
省略了部分非關鍵代碼(用於一些優化操作)

final Message next() {  
     int pendingIdleHandlerCount = -1; //空閒處理器的個數 
     int nextPollTimeoutMillis = 0;  //等待時間

     for (;;) {  
         if (nextPollTimeoutMillis != 0) {  
             Binder.flushPendingCommands();  
         }  
         nativePollOnce(mPtr, nextPollTimeoutMillis); //本地方法調用 

         synchronized (this) {  
             if (mQuiting) {  
                 return null;  
             }  

             // Try to retrieve the next message.  Return if found.  
             final long now = SystemClock.uptimeMillis();  
             Message prevMsg = null;  
             Message msg = mMessages; 
             if (msg != null) {  
                 if (now < msg.when) {  
                     // Next message is not ready.  Set a timeout to wake up when it is ready.  
                     nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);  
                 } else {  
                     // Got a message.  
                     mBlocked = false;  
                     if (prevMsg != null) {  
                         prevMsg.next = msg.next;  
                     } else {  
                         mMessages = msg.next;  
                     }  
                     msg.next = null;  
                     if (false) Log.v("MessageQueue", "Returning message: " + msg);  
                     msg.markInUse();  
                     return msg;  
                 }  
             } else {  
                 // No more messages.  
                 nextPollTimeoutMillis = -1;  
             }  
             ...
 }  

next的作用就是為了獲取隊列中的被放入的消息,我們先介紹一下變量mMessages,這個其實就是MessageQueue的鏈表頭,也就是最先需要處理的消息;接著我們可以看出next是一個阻塞方法,如果沒有尋找到需要處理的消息,即msg == null的情況,那麼next方法一直在無限循環中;如果尋找到了位於鏈表頭的消息,還要判斷是否處於需要執行的時間,如果這些都滿足了要求,就把message取走(移除)並返回,關鍵代碼如下:

// Got a message.  
mBlocked = false;  
if (prevMsg != null) {  
    prevMsg.next = msg.next;  
} else {  
    mMessages = msg.next;  
}  
msg.next = null;  
if (false) Log.v("MessageQueue", "Returning message: " + msg);  
msg.markInUse();  
return msg;  

這裡有本地方法上面我們也說到了,其實涉及到了底層的一些處理,其中最重要的就是這句函數:

nativePollOnce(mPtr, nextPollTimeoutMillis);  

並且我們還有兩個變量的作用沒有講解,就是next開頭創建的兩個變量:

int pendingIdleHandlerCount = -1; //空閒處理器的個數 
int nextPollTimeoutMillis = 0;  //等待時間

這兩個變量的作用我們也沒有講解,他們與這個本地方法有著非常重要的聯系,我們會在底層源碼分析中進行介紹,在分析完整體機制之後會著重分析MessageQueue的底層部分,先不著急,我們現在只需要確定MessageQueue是作為一個存放消息的消息隊列就行了,但實際上它在消息循環中的地位非常的特殊,講完整體框架我們再細看這個類的實現,從整體來看我們只需要大致了解它添加、讀取的作用即可。

2、Looper
Looper作為我們消息處理的主人公,在消息處理中起了非常大的作用,我們來好好看一下Looper的具體實現。Looper擁有一個屬於自己的MessageQueue,並且一直查看其中是否有新的消息,如果有就會立刻進行處理,否則就會一直阻塞在循環中。

(1)首先看一看Looper擁有的MessageQueue

//成員變量之一
final MessageQueue mQueue;
//私有構造函數
private Looper() {
    mQueue = new MessageQueue();
    mRun = true;
    mThread = Thread.currentThread();
}

這裡可以看出,每個Looper對象都有自己的MessageQueue,在構造函數中初始化。

(2)Looper的初始化過程
既然Looper是一個與線程有關的變量,它在哪裡初始化呢?前面我也說過,一個線程沒有Looper就不能創建Handler,那我們平時怎麼沒接觸到Looper的初始化呢?因為我們那時在主線程中創建的Handler,而主線程本身就有一個主消息循環(MainLooper),就不用我們自己去創建了。我們來看一看如何在子線程中初始化Looper,開啟屬於自己的消息循環:

class LooperThread extends Thread {
public Handler mHandler;

public void run() {
  Looper.prepare();//給線程創建一個消息循環
  mHandler = new Handler() {
    public void handleMessage(Message msg) {
     // process incoming messages here
    }
  };
  Looper.loop();//開啟消息循環
  }
}

這裡用到了兩個頗為關鍵的方法,prepare和loop,我們來詳細的看一下

2.1 prepare方法

//靜態成員變量之一
private static final ThreadLocal sThreadLocal = new ThreadLocal();

//靜態方法prepare
public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper()); //放入新的Looper對象(對應當前線程)
}  

首先我們要明確一下剛才說的另一個小主角:ThreadLocal,它的作用就是為每個線程存儲屬於每個線程自己的數據,這裡的sThreadLocal就是為每個線程存儲屬於自己的Looper變量,prepare方法做了什麼呢?首先判斷當前線程是否已經有Looper變量,如果有就報異常,因為每個線程不能有多個Looper對象;然後為當前線程創建屬於自己的Looper對象,並保存到sThreadLocal變量中,這樣當前線程就具有了屬於自己的Looper。

2.2 loop方法

public static void loop() {  
      final Looper me = myLooper();  
      if (me == null) {  
          throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
      }  
      final MessageQueue queue = me.mQueue;  
      ...  
      for (;;) {  
          Message msg = queue.next(); // might block  
          if (msg == null) {  
              // No message indicates that the message queue is quitting.  
              return;  
          }  

          msg.target.dispatchMessage(msg);  

          msg.recycle();  
      }  
}  

終於看到loop方法的真面目,首先我們要明確一個重要的觀點,loop函數是運行在Looper所在的線程中的,比如我們的loop就是運行在我們的LooperThread中,而主線程得loop方法就是運行在主線程中。我們來慢慢看loop的操作:

步驟1、得到當前所在線程的Looper對象,調用了函數myLooper

final Looper me = myLooper();  

這個myLooper的作用就是獲取當前線程的Looper對象,相信大家估計都知道是怎麼實現的了:

//靜態成員變量之一
private static final ThreadLocal sThreadLocal = new ThreadLocal();

//靜態方法
public static final Looper myLooper() {
    return (Looper)sThreadLocal.get();
}

其作用就是獲取當前線程的Looper對象,從代碼中我們可以很清楚的看到,從靜態成員變量ThreadLocal中獲取當前線程的Looper對象,我們接著往下看。

步驟2:

if (me == null) {  
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
}  

這裡做一個簡單的判斷,如果沒有調用之前的Looper.prepare()進行Looper對象的創建,這裡就會觸發異常,下面進入關鍵的loop階段:

步驟3:
首先獲取了當前線程Looper對象的MessageQueue,緊接著開始循環,在循環中不斷進行獲取message的嘗試:

final MessageQueue queue = me.mQueue;  
...  
for (;;) {  
    Message msg = queue.next(); //阻塞方法,可能阻塞
    if (msg == null) {  
        // No message indicates that the message queue is quitting.  
        return;  
    }  

    msg.target.dispatchMessage(msg);  //Handler的消息處理函數執行

    msg.recycle();  
}  

MessageQueue的next方法我們剛才已經分析過了,這是一個阻塞方法,如果沒有Message會一直阻塞;一旦獲取到了Message,首先判斷是否為null,這個的作用就是用來判斷Looper的退出,當Looper調用quit方法,就會調用MessageQueue的quit方法,它的next就會返回null,然後loop方法就return,即退出了循環,達到了退出的作用。一旦獲取到了不為null的Message,就會在loop函數中調用msg的target的dispatchMessage函數,Message我們知道,那這個target是什麼呢?我們來看一看Message的成員變量中的target:

Handler target;    // 發送和處理消息的 Handler  

原來target就是我們的Handler對象,也就是我們下一個要介紹的主人公,現在我們只要知道一點,這個Handler的dispatchMessage函數是在loop中執行的,也就是Looper所在的線程中執行的(也可以說是Looper對應的線程),其實這裡已經完成了我們所熟知的線程切換,Handler發送消息的線程已經切換到執行消息處理的Looper所在線程(熟知的主線程)了。

四、Handler
終於要說到我們經常使用的Handler類了,說了前面這麼多,相信大家對Handler在消息循環中的地位也有了一定的認識。我們都知道真正消息處理的代碼是寫在Handler中的,但Looper還是真正的主角,畢竟Handler中的處理函數還是Looper調用的,可以說一切的獲取Message、執行處理都是Looper調用,Handler只是在別的線程中“發出”一個Message而已,之後的執行通過對Looper的分析我們都知道是Looper的活。這裡為什麼說“發出”要加雙引號更多的是因為不是真正意義的發出,通過下面的分析我們可以知道,其實真正的操作只是在Looper的MessageQueue中加入新的Message對象而已。
首先來看一看Handler的構造函數,也是我們創建Handler的調用之一:

(1)Handler構造函數

public Handler(){
    ...
    mLooper = Looper.myLooper(); //獲取Looper對象
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; //獲取消息隊列
    mCallback = null;
}

這裡我們清晰的看到Handler的默認構造函數做的第一件事就是獲取當前線程的Looper對象,如果沒有Looper對象自然也創建不了默認的Handler對象(前面的結論在這裡得到了印證);第二件事就是獲取當前線程的Looper的MessageQueue,我們記住這個mQueue,實際上大家肯定明白為什麼要這個mQueue,其實就是為了插入消息才獲取的;第三個這個mCallback是干嘛的呢,我們往下看就知道了。

(2)sendMessage的系列函數

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis){
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this; 
        //把target設置為自己,這樣我們就知道Message的target就是發送它的Handler
        sent = queue.enqueueMessage(msg, uptimeMillis);
        //隊列的添加消息操作
    }
    else {
        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}

無論是哪種sendMessage方法,最終都會調用sendMessageAtTime函數,我們來看看它做了哪些操作:

步驟一:獲取消息隊列,其實就是Handler初始化時獲取到的

MessageQueue queue = mQueue;

步驟二:當隊列不為空時(正常情況),做了兩件事,第一件事把消息的target設置為自己,這也印證了我們之前講到的msg.target對象;第二件事就是簡單的隊列添加操作,調用之前分析過的enqueueMessage函數,關鍵代碼如下:

if (queue != null) {
    msg.target = this; 
    //1.把target設置為自己,這樣我們就知道Message的target就是發送它的Handler
    sent = queue.enqueueMessage(msg, uptimeMillis);
    //2.隊列的添加消息操作
}

(3)dispatchMessage函數
分析了前面那麼多,終於要說到我們分析loop函數時調用的那句msg.target.dispatchMessage(msg);中的關鍵函數dispatchMessage,讓我們來看看代碼,但大家一定要清楚,這句話是運行在Looper所屬於的線程中的喲。

public void dispatchMessage(Message msg) {
    //1.如果msg本身設置了callback,則直接交給這個msg的callback處理了
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
    //2.如果該handler的callback有的話,則交給這個callback處理了
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
            return;
        }
    }
    //3.在msg和自身都沒有callback 的情況下,運行自己的handleMessage(是由我們重寫的handleMessage,Handler本身的handleMessage什麼也不會干)
    handleMessage(msg);
}

其實我的注釋已經把能說的都說了,我簡單的解釋一下:

步驟1:首先就是msg是否有callback,而這個callback我們可以看Message的源碼中:

Runnable callback;   // 處理消息的回調

可以看到這是一個Runnable對象,實際就是Handler的post函數所傳遞的參數,它的調用也非常簡單,handleCallback函數只做了一件事:

private static void handleCallback(Message message) {
    message.callback.run();
}

實際上就是針對post方法來進行執行而已,這裡延伸一下post方法,我們知道他也是Handler提交任務的一種方式,具體過程如下:

handler.post(new Runnable(){
@Override
public void run() {
    //do something
}});

那麼這個post如何實現呢?

public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}

又回到了我們剛才的sendMessageDelayed函數,相信大家對於getPostMessage是干嘛的也應該有一定想法了,不就是創建Runnable對應的Message嘛,對,我們來看一下實現:

private final Message getPostMessage(Runnable r) {
    Message m = Message.obtain(); //新建一個Message
    m.callback = r; //設置Runnable對象
    return m;
}

這下知道了這個post是干嘛的了吧,我們也可以知道上面的msg的callback是怎麼來的了。

步驟2:檢查Handler本身是否有callback,也就是我們之前在初始化函數中說到的mCallback對象,它是一個回調接口,定義如下:

public interface Callback {
    public boolean handleMessage(Message msg);
}

那他是干嘛的呢?實際上他就是Handler使用的另外一種方式,我們平時都是使用派生Handler的子類,重寫handleMessage函數來實現自己的Handler;而另外一種方式就是這個Callback接口,通過傳入實現的Callback接口就可以實現Handler處理的自定義,它的另一個構造函數如下所示:

public Handler(Callback callback) {
    ...
    mLooper = Looper.myLooper(); //獲取Looper對象
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
}

這就是通過實現Callback來進行創建Handler的過程,只不過多了一個mCallback的設置而已,如果你設置了callback,那麼就可以直接進行消息的處理。

步驟3:在上面兩步都沒有執行的情況下,進行消息處理,這裡就會調用我們熟悉的handleMessage函數了,你的重寫方法真正的得到了執行:

handleMessage(msg);

這樣我們就清楚了整個任務調用流程。

五、主線程消息循環的開啟
上面我們也說了,之所以你可以不用Looper就創建Handler的原因就是因為你在主線程中進行的操作,而主線程會自己創建Looper循環,那具體在哪裡呢,就在主線程的入口函數main中:

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();
    ...//省略中間代碼
    Looper.loop();
}

那這個prepareMainLooper函數是怎麼實現的呢?我們來看一看:

public static final void prepareMainLooper() {
    prepare();
    setMainLooper(myLooper());
    ...
}
private synchronized static void setMainLooper(Looper looper) {
    mMainLooper = looper;
}

我們可以看出主要工作就是進行Looper的准備並設置MainLooper對象。

總結:通過整體的流程分析和各個類的關鍵函數分析,我們已經知道了Handler線程切換的秘密了,其實Handler並不是真正的主角,真正重要的是Looper和MessageQueue,它們才是線程切換的真正主角,但我前面提到的MessageQueue我也說了,它有著非常重要的作用,具體的作用就離不開它的兩個本地方法,其實在C++層,MessageQueue做了更多的操作來進行處理,這個底層部分我們在下一篇分析中來慢慢看。不過通過這第一篇的分析,我們已經對消息處理機制有了總體的了解,起碼知道Handler的實現比使用要復雜的多。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved