Android 消息處理

其實對于初學(xué)者來說Handler的使用很容易會很容易上手,但是對于其中的機制的理解,卻不會那么簡單,甚至1,2年經(jīng)驗的android開發(fā)也可能對其內(nèi)部實現(xiàn)原理不是很了解。

其中我經(jīng)過的認知的過程

  1. 初識Handler,認為沒必要,直接執(zhí)行方法不就好了么,為什么還要post出去,然后再在handleMessage中作處理,多此一舉。

  2. 異步可使用Handler,在主線程更新ui

  3. 主線程中有個Looper不斷循環(huán),從MessageQueue中獲取到Message,而Handler只是往MessageQueue中塞入Message

  4. 非主線程中也可以創(chuàng)建自己的Looper來處理消息,不過要自己調(diào)用Looper.prepare(),Looper.loop(),初始化和開始輪詢。

  5. MessageQueue調(diào)用了jni層的方法來實現(xiàn)消息處理機制

筆者對整個android的消息處理機制的認知過程如上,經(jīng)過的時間也很長,不是一蹴而就的。

今天就通過對其底層源碼分析,和大家分享其內(nèi)部實現(xiàn)原理,主要是第5步的認知,涉及到了jni層。這里主要介紹主線程中的Looper。

  注意:android源碼2.3.1

我們從頭開始
frameworks/base/core/java/android/app/ActivityThread

...
...
public static final void main(String[] args) {
    ...
     Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }
      ...
      ...
        Looper.loop();
    ...
}

我們這里以ActivityThread為入口來分析消息處理機制,每個app啟動的時候,都會創(chuàng)建自己的ActivityThread,并且在main方法中也會創(chuàng)建主線程中的Looper。

這里如果對app整個啟動流程不是特別清楚的話,建議先看下
http://www.itdecent.cn/p/9da5bb46835c

frameworks/base/core/java/android/os/Looper

...
private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }
...
 public static final void prepare() {
        ...
        sThreadLocal.set(new Looper());
    }
    public static final void prepareMainLooper() {
        prepare();
        setMainLooper(myLooper());
       ...
    }
  ...
  ...
   public static final Looper myLooper() {
        return (Looper)sThreadLocal.get();
    }
  ...

首先我們要理解ThreadLocal這個類的作用
http://www.cnblogs.com/alphablox/archive/2013/01/20/2869061.html

簡單來說,就是為了實現(xiàn)每個線程中有對應(yīng)一個Looper。

上面代碼主要做了

  1. 創(chuàng)建Looper,MessageQueue
  2. 把Looper與當(dāng)前主線程關(guān)聯(lián)

mQueue其實就是MessageQueue,在Looper的構(gòu)造方法中創(chuàng)建。
下面分析下MessageQueue。

frameworks/base/core/java/android/os/MessageQueue

...
private native void nativeInit();
...
MessageQueue() {
        nativeInit();
    }
...

創(chuàng)建MessageQueue,其實主要調(diào)用了native方法。

其實在我們分析android源碼的時候,要找到的對應(yīng)的native方法,其實是一件很麻煩的事情,因為我們只知道方法名,并不知道對應(yīng)的.c文件或者.cpp文件是哪個,這樣就造成了我們閱讀的障礙,這里有個比較笨的方法就是使用grep '**' -R .來查詢文件內(nèi)容。但是畢竟android源碼比較龐大,這樣做其實還是很麻煩的,我這里暫時也沒有更好的辦法,主要還是參考了別人的文章,找到了對應(yīng)的文件。

frameworks/base/core/jni/android_os_MessageQueue

...
NativeMessageQueue::NativeMessageQueue() {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}
...
static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
        NativeMessageQueue* nativeMessageQueue) {
    env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
             reinterpret_cast<jint>(nativeMessageQueue));
}
...
static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
  ...
    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}
...
static JNINativeMethod gMessageQueueMethods[] = {
    { "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit },
    ...
};
...

簡單地介紹下上面代碼

  1. 注冊對應(yīng)的jni方法,如nativeInit.
  2. java層調(diào)用nativeInit后創(chuàng)建,NativeMessageQueue
  3. NativeMessageQueue中創(chuàng)建了Looper,如java層也是跟線程綁定,但是這個jni層的Looper,跟java層的 Looper沒有關(guān)系。
  4. 利用JNIEnv方法給java 層中的MessageQueue對象的mPtr變量設(shè)置為NativeMessageQueue的地址。

下面是對jni層Looper的解析
frameworks/base/libs/utils/Looper

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks),
        mResponseIndex(0) {
      ...
      int result = pipe(wakeFds);
      ...
      mWakeReadPipeFd = wakeFds[0];
      mWakeWritePipeFd = wakeFds[1];
      ...
      mEpollFd = epoll_create(EPOLL_SIZE_HINT);
      ...
}

其實Looper的代碼很多,這里我就貼出了關(guān)鍵的幾行,但是涉及到的知識點卻很多。

  1. pipe,創(chuàng)建讀寫管道
    http://www.cnblogs.com/kunhu/p/3608109.html
  2. epoll_create,管理io
    http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

其實我對這2者也并不是很熟悉。但是我知道以下幾點,我認為就足夠了。

  1. pipe創(chuàng)建讀寫2個通道
  2. epoll監(jiān)聽io
  3. epoll_wait會io阻塞,等到pipe管道中寫入內(nèi)容后,阻塞結(jié)束。

以上其實都是ActivityThread中調(diào)用Looper.prepareMainLooper之后的流程。主要是進行一些初始化,創(chuàng)建一些基礎(chǔ)的對象。

下面我們來分析Looper.loop(),消息輪詢。

frameworks/base/core/java/android/os/Looper

...
 public static final void loop() {
        Looper me = myLooper();
        MessageQueue queue = me.mQueue;
        while (true) {
            Message msg = queue.next(); // might block
             ...
                msg.target.dispatchMessage(msg);
             ...
                msg.recycle();
            }
        }
    }
...

這段代碼就是開啟while死循環(huán),不斷從MessageQueue中獲取Message,一看最重要的肯定是queue.next()獲取Message這個方法。
繼續(xù)跟入。
frameworks/base/core/java/android/os/MessageQueue

  ...
  final Message next() {
      ...
       for (;;) {
            ...
            nativePollOnce(mPtr, nextPollTimeoutMillis);
            ...
        }
      ...
  }
  ...

next方法去獲取Message,最終還是調(diào)用nativePollOnce方法,傳入的2個參數(shù),這里介紹下。

  1. mPtr,是MessageQueue的一個變量,在前面其實已經(jīng)介紹過了,其實是指向jni層的NativeMessageQueue的地址。

  2. nextPollTimeoutMillis其實是epoll_wait時,如果pipe中沒有內(nèi)容寫入的等待時間,舉個例子,如果nextPollTimeoutMillis為1000,那么這1秒中如果pipe中一直沒有東西寫入,那么,epoll_wait那么會阻塞1秒,然后繼續(xù)運行,如果為0的話,epoll_wait就不會阻塞,如果為-1的話,如果pipe中沒有東西寫入,就會一直阻塞。

下面我們來看下nativePollOnce的具體實現(xiàn)。

frameworks/base/core/jni/android_os_MessageQueue

...
void NativeMessageQueue::pollOnce(int timeoutMillis) {
    mLooper->pollOnce(timeoutMillis);
}
...

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jint ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(timeoutMillis);
}
...

其實在前面那一步就已經(jīng)介紹了,ptr是NativeMessageQueue指針,在方法中強轉(zhuǎn)為NativeMessageQueue對象后,調(diào)用他的pollOnce,
然后再調(diào)用NativeMessageQueue中的Looper的pollOnce方法

frameworks/base/libs/utils/Looper

...
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
         ...
        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
    ...
    ...
     int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ...
     for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            }
            ...
         }
        ...
      }
      ..
}
...
void Looper::awoken() {
...
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}
...
  1. epoll_wait阻塞,等待timeoutMillis毫秒或者pipe中有輸入
  2. 如果有消息過來了,其實會先往pipe中輸入,解除epoll_wait阻塞
  3. for循環(huán)eventCount,判斷是否是當(dāng)前線程的管道的fd,如果是,就調(diào)用awoken方法,read一下把東西讀出來

其實到這里了,已經(jīng)把大部分重要的代碼貼出,下面我們來過一遍,走下整個流程,來梳理一下。

  1. Looper初始化,這里就不過多介紹了。
  2. 調(diào)用loop,不斷從MessageQueue取數(shù)據(jù)。
    第一次調(diào)用時,nextPollTimeoutMillis=0,epoll_wait沒有造成阻塞,馬上運行到下面,由于剛開始,還沒有消息過來eventCount=0,所以
    java層中。
    frameworks/base/core/java/android/os/MessageQueue
...
final Message next() {
    ...
    for (;;) {
    ...
    nativePollOnce(mPtr, nextPollTimeoutMillis);
            synchronized (this) {
                ...
                final Message msg = mMessages;
                if (msg != null) {
                    final long when = msg.when;
                    if (now >= when) {
                        mBlocked = false;
                        mMessages = msg.next;
                        msg.next = null;
                        if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
                        return msg;
          else{
                    nextPollTimeoutMillis = -1;
          }
    ...
    }
}
...

調(diào)用nativePollOnce其實主要作用就是在jni層中epoll_wait等待pipe管道有新的輸入,即有新的 Message。

剛剛開始,沒有阻塞,所以nativePollOnce馬上返回。

Message其實是一個鏈表結(jié)構(gòu)。

mMessages變量始終指向第一個Message.

由于剛開始還沒有Message,所有mMessages = null

進入else,設(shè)置nextPollTimeoutMillis = -1;
由于還處于for之中,所以繼續(xù)調(diào)用native方法nativePollOnce,
前面我已經(jīng)介紹了,當(dāng)nextPollTimeoutMillis=-1時,其實在epoll_wait會一直阻塞。

這時,我又不得不提到這篇優(yōu)秀的文章
http://www.itdecent.cn/p/9da5bb46835c

這里我并不清楚第一個Message消息是哪里傳過來的,但是在App啟動時Activity的生命周期中大部分都是利用handleMessage來處理的,姑且當(dāng)作是第一個Message吧。

我們都知道是通過Handler來發(fā)送Message,由于Handler中發(fā)送 Message方法很多,但是最后都是調(diào)用sendMessageAtTime

frameworks/base/core/java/android/os/Handler

...
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }
...

這里的mQueue其實就是我們在Looper中創(chuàng)建的MessageQueue.
frameworks/base/core/java/android/os/MessageQueue

...
final boolean enqueueMessage(Message msg, long when) {
       ...
        final boolean needWake;
        synchronized (this) {
          ...
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked; // new head, might need to wake up
            } else {
                Message prev = null;
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
                msg.next = prev.next;
                prev.next = msg;
                needWake = false; // still waiting on head, no need to wake up
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }
...

enqueueMessage其實這個方法很重要,它對Message先進行了排序,要早運行的排在了前面,比較晚運行的排在隊列后面。

前面介紹了mMessages其實是指向Message鏈表的第一個Message.
這時候來了個新的Message,由于是剛剛開始mMessage = null;
所以進入了if()中的語句。

這里有個變量需要特別解釋,needWake,顧名思義,是否需要喚醒。
就整篇文章而言,其實阻塞的就一個地方,就是epoll_wait,所以needWake指的需不需要喚醒,也就是指的這里。
mBlocked代表,當(dāng)前的epoll_wait是否正在阻塞,如果是阻塞的,
mBlocked = true,所以needWake = mBlocked = true,也就是需要喚醒。
如果非阻塞那么,needWake = false.
最終是

if (needWake) {
            nativeWake(mPtr);
        }

前面已經(jīng)介紹了,由于nextPollTimeoutMillis=-1,epoll_wait正在阻塞,所以needWake = true;所以這個時候需要喚醒。

這里我就 不介紹跳轉(zhuǎn)過程了,最終調(diào)用jni層的Looper中的wake方法

frameworks/base/libs/utils/Looper

...
void Looper::wake() {
...
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);
  ...
}
...

前面其實我已經(jīng)介紹過了epoll_wait有2種方式不再阻塞

  1. 等待傳入的timeout參數(shù)時間到
  2. pipe管道中有新的輸入

由于這個時候的timeout=-1代表一直阻塞,所以只能通過往pipe管道中寫入東西,才能不阻塞epoll_wait.如上write一個內(nèi)容。其實寫的內(nèi)容是什么并不重要,如上,其實就是寫了一個W字符。

這個時候,epoll_wait不再阻塞,繼續(xù)往下運行。
frameworks/base/libs/utils/Looper

...
int Looper::pollInner(int timeoutMillis) {
    ...
     int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ...
     for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } 
          ...
        } 
    ...
    ...

}
...
void Looper::awoken() {
    ...
     do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}
...

這個時候進入awoken方法,把寫入內(nèi)容讀出。沒有造成阻塞。

也就是java層中的MessageQueue中的next方法中的nativePollOnce沒有造成阻塞,繼續(xù)運行。

frameworks/base/core/java/android/os/MessageQueue

  ...
  final Message next() {
        ...
      
        for (;;) {
            ...
            nativePollOnce(mPtr, nextPollTimeoutMillis);
            ...
             final Message msg = mMessages;
                if (msg != null) {
                    final long when = msg.when;
                    if (now >= when) {
                        mBlocked = false;
                        mMessages = msg.next;
                        msg.next = null;
                        if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
                        return msg;
                    } else {
                        nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
            ...  
      }
        ...
  }
  ...

這時nativePollOnce阻塞結(jié)束,繼續(xù)運行,if (now >= when) 是為了判斷這個消息是否延遲,對應(yīng)我們Handler中延遲發(fā)送的一些方法,如postDelay()等。這里直接認為沒有延時。

在Message鏈表中把第一個Message移除返回。
最后調(diào)用Handler的handleMessage,這不是重點我就直接略過了。

等到Message都處理完了之后,獲取到Message 為null。nextPollTimeoutMillis又被置為-1,然后又在epoll_wait中進行阻塞。

這里對延時發(fā)送Message的沒有詳細講解,主要還是2點。

  1. 在添加Message的時候進行時間排序了,把Message插入到對應(yīng)的位置。
  2. 主要還是利用nextPollTimeoutMillis,在epoll_wait的時候阻塞,等待。

這里我就不再詳細贅述了。

問題

在分析源碼的時候,我遇到了一個問題,整個消息機制,其實就是一個死循環(huán),一直在執(zhí)行,當(dāng)沒有消息的時候epoll_wait阻塞。
為什么epoll_wait在主線程阻塞不會造成卡頓,或者ANR?

29125917-E9AB-4187-AA9C-A685980FE347.png

我這里就不再過多贅述了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容