其實對于初學(xué)者來說Handler的使用很容易會很容易上手,但是對于其中的機制的理解,卻不會那么簡單,甚至1,2年經(jīng)驗的android開發(fā)也可能對其內(nèi)部實現(xiàn)原理不是很了解。
其中我經(jīng)過的認知的過程
初識Handler,認為沒必要,直接執(zhí)行方法不就好了么,為什么還要post出去,然后再在handleMessage中作處理,多此一舉。
異步可使用Handler,在主線程更新ui
主線程中有個Looper不斷循環(huán),從MessageQueue中獲取到Message,而Handler只是往MessageQueue中塞入Message
非主線程中也可以創(chuàng)建自己的Looper來處理消息,不過要自己調(diào)用Looper.prepare(),Looper.loop(),初始化和開始輪詢。
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。
上面代碼主要做了
- 創(chuàng)建Looper,MessageQueue
- 把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 },
...
};
...
簡單地介紹下上面代碼
- 注冊對應(yīng)的jni方法,如nativeInit.
- java層調(diào)用nativeInit后創(chuàng)建,NativeMessageQueue
- NativeMessageQueue中創(chuàng)建了Looper,如java層也是跟線程綁定,但是這個jni層的Looper,跟java層的 Looper沒有關(guān)系。
- 利用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)鍵的幾行,但是涉及到的知識點卻很多。
- pipe,創(chuàng)建讀寫管道
http://www.cnblogs.com/kunhu/p/3608109.html - epoll_create,管理io
http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
其實我對這2者也并不是很熟悉。但是我知道以下幾點,我認為就足夠了。
- pipe創(chuàng)建讀寫2個通道
- epoll監(jiān)聽io
- 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ù),這里介紹下。
mPtr,是MessageQueue的一個變量,在前面其實已經(jīng)介紹過了,其實是指向jni層的NativeMessageQueue的地址。
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));
}
...
- epoll_wait阻塞,等待
timeoutMillis毫秒或者pipe中有輸入 - 如果有消息過來了,其實會先往pipe中輸入,解除epoll_wait阻塞
- for循環(huán)eventCount,判斷是否是當(dāng)前線程的管道的fd,如果是,就調(diào)用awoken方法,read一下把東西讀出來
其實到這里了,已經(jīng)把大部分重要的代碼貼出,下面我們來過一遍,走下整個流程,來梳理一下。
- Looper初始化,這里就不過多介紹了。
- 調(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種方式不再阻塞
- 等待傳入的timeout參數(shù)時間到
- 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點。
- 在添加Message的時候進行時間排序了,把Message插入到對應(yīng)的位置。
- 主要還是利用nextPollTimeoutMillis,在epoll_wait的時候阻塞,等待。
這里我就不再詳細贅述了。
問題
在分析源碼的時候,我遇到了一個問題,整個消息機制,其實就是一個死循環(huán),一直在執(zhí)行,當(dāng)沒有消息的時候epoll_wait阻塞。
為什么epoll_wait在主線程阻塞不會造成卡頓,或者ANR?

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