前言
下面的內(nèi)容基于由淺入深全面分析Handler機(jī)制原理之源碼的理解,擴(kuò)展的Handler機(jī)制的難點(diǎn)分析僚害。
目錄
- 內(nèi)存共享(如何切換線程的)
- prepare()函數(shù)中萨蚕,使用ThreadLocal存放Looper對象岳遥,ThreadLocal的作用浩蓉。
- Message使用對象復(fù)用池(享元設(shè)計(jì)模式)
- Handler的阻塞/喚醒機(jī)制
- Handler的同步屏障
來妻往,上一張大綱圖试和,醒一下神
Handler如何切換線程
當(dāng)我們都了解了Handler的原理,再結(jié)合上述的使用例子节视,我們也對Handler是如何切換的寻行,其實(shí)情況也很明朗了匾荆。
- 創(chuàng)建Message對象,我們都是知道創(chuàng)建一個對象兔魂,對象一般都是存放在
堆內(nèi)存
析校,在JVM中堆是在線程共享區(qū)域智玻,并不是存放在線程私有數(shù)據(jù)區(qū)吊奢,所以說所有線程都可以訪問得到這個Message對象事甜。 - 在子線程中逻谦,Handler發(fā)送一條Message邦马,Message會插入到MessageQueue中滋将。有人會問MessageQueue是屬于那個線程症昏,其實(shí)MessageQueue并不是屬于那個線程肝谭,只僅僅是存放Message的容器而已攘烛,就相當(dāng)于List一樣坟漱,只不過MessageQueue比較特殊,是一個隊(duì)列的形式而已成翩。
- 當(dāng)Looper.loop()函數(shù)捕传,不斷的從MessageQueue取出Message庸论,當(dāng)獲取到Message后,通過Handler.dispatchMessage(msg)函數(shù)去分發(fā)消費(fèi)掉這條Message而已否副。
Handler的線程如何切換是不是很簡單艘儒,當(dāng)然這都是你對Handler的原理熟悉掌握僧凤,并不難理解掐场。下面通過一張圖加深到Handler線程切換熊户。
用ThreadLocal保存Looper對象嚷堡,目的是什么麦到?
ThreadLocal是線程本地變量,它是一種特殊的變量刺桃,ThreadLocal為每個線程提供一個變量的副本瑟慈,使得每一個線程在同一時(shí)間內(nèi)訪問到的不同對象葛碧,這樣就隔離了線程之間對數(shù)據(jù)的一個共享訪問进泼,那么在內(nèi)部實(shí)現(xiàn)上乳绕,ThreadLocal內(nèi)部都有一個ThreadLocalMap济蝉,用來保存每個線程所擁有的變量的副本王滤。
子線程中創(chuàng)建Handler體現(xiàn)ThreadLocal的使用
錯誤例子:直接在子線程創(chuàng)建一個Handler雁乡。
new Thread(new Runnable() {
@Override
public void run() {
new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
}).start();
直接拋出異常:java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
直接看源碼:
public Handler(Callback callback, boolean async) {
//...省略部分代碼
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//...省略部分代碼
}
//Looper類
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
使用ThreadLocal 保存當(dāng)前Looper對象蔗怠,ThreadLocal 類可以對數(shù)據(jù)進(jìn)行線程隔離寞射,保證了在當(dāng)前線程只能獲取當(dāng)前線程的Looper對象桥温,同時(shí)prepare()函數(shù)保證當(dāng)前線程有且只有一個Looper對象。所以在子線程中創(chuàng)建Handler對象掏觉,需要添加Looper.prepare()澳腹,如果只單單添加Looper.prepare()函數(shù)還是不行酱塔,缺少動力沥邻,也需要添加Looper.loop()函數(shù)。
正確代碼如下:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//注意添加
new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();//注意添加
}
}).start();
為什么可以直接new Message羊娃,還提供obtain()函數(shù)唐全,為什么這樣設(shè)計(jì)?
如果不斷的new出Meaage對象并插入到MessageQueue中蕊玷,在jvm的堆中是不是會不斷有新Message對象創(chuàng)建以及銷毀邮利,導(dǎo)致內(nèi)存抖動集畅,而GC線程雖然作為優(yōu)先級最低的線程近弟,此時(shí)因?yàn)楸仨欸C,導(dǎo)致GC線程搶占CPU時(shí)間片挺智,主線程拿不到CPU而卡頓調(diào)幀祷愉。Google在設(shè)計(jì)消息機(jī)制的時(shí)候就想到了消息復(fù)用機(jī)制,幾乎所有Framework中發(fā)送消息都是通過Message.obtain()函數(shù)來進(jìn)行消息復(fù)用赦颇。這種消息復(fù)用機(jī)制其實(shí)就是一種享元設(shè)計(jì)模式二鳄。享元設(shè)計(jì)模式就不展開了,感興趣的可以自行查閱資料
下面我們看看Message是如何復(fù)用的媒怯,首先看看Mesage里的幾個成員變量:
Message next;//形成一個鏈表订讼,指向下一個Message
private static final Object sPoolSync = new Object();//對象鎖
private static Message sPool;//頭節(jié)點(diǎn)的消息
private static int sPoolSize = 0;//當(dāng)前鏈表的個數(shù)
private static final int MAX_POOL_SIZE = 50;//鏈表最多存放的個數(shù)
Looper的loop()函數(shù)中不斷的從消息隊(duì)列中取消息diapatch,分發(fā)之后會調(diào)用Message的回收操作扇苞。
public static void loop() {
//獲取Looper對應(yīng)的消息隊(duì)列MessageQueue
final MessageQueue queue = me.mQueue;
//...省略部分代碼
for (;;) {//不斷循環(huán)從消息隊(duì)列中取出消息
Message msg = queue.next(); //有可能阻塞
if (msg == null) {//沒有消息欺殿,則退出消息隊(duì)列
return;
}
//...省略部分代碼
//msg.target就是Handler,把獲取到的消息分發(fā)出去
msg.target.dispatchMessage(msg);
//...省略部分代碼
msg.recycleUnchecked();//回收Message
}
}
當(dāng)Message分發(fā)完之后鳖敷,就調(diào)用recycleUnchecked()函數(shù)對Message進(jìn)行回收脖苏。
void recycleUnchecked() {
flags = FLAG_IN_USE; // 標(biāo)記改Message將加入消息池
// 重置所有消息屬性
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) { // 線程安全鎖
if (sPoolSize < MAX_POOL_SIZE) { // MAX_POOL_SIZE = 50 ,表明消息池最多50個
//頭節(jié)點(diǎn)設(shè)置給Next 將當(dāng)前對象最為最新的頭節(jié)點(diǎn)sPool
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
所以獲取一個對象池中的Message可以直接調(diào)用Message.obtain()函數(shù)即可定踱。
public static Message obtain() {
synchronized (sPoolSync) {
//sPool就是Looper.loop()棍潘,調(diào)用dispatchMessage函數(shù)后,調(diào)用recycleUnchecked()函數(shù)回收的Message
if (sPool != null) {
Message m = sPool; //取出頭節(jié)點(diǎn)
sPool = m.next; // 將頭節(jié)點(diǎn)的下一個作為最新的頭節(jié)點(diǎn)
m.next = null; // 設(shè)置需要返回的消息的next為空
m.flags = 0; // 清除是否還在鏈表中
sPoolSize--;
return m;
}
}
//如果對象池中沒有消息崖媚,就創(chuàng)建一個消息
return new Message();
}
Handler的阻塞/喚醒機(jī)制
首先我們要了解Handler的阻塞或喚醒是在那里發(fā)起的亦歉,經(jīng)過上面的源碼分析及Handler的整個調(diào)用過程,我們都知道先是創(chuàng)建一個Looper對象畅哑,然后創(chuàng)建一個Handler對象肴楷,最后調(diào)用Looper.loop()函數(shù),在loop()函數(shù)中不斷從MessageQueue.next()函數(shù)中的輪詢獲取Message荠呐。
public static void loop() {
//...省略部分代碼
for (;;) {//不斷循環(huán)從消息隊(duì)列中取出消息
Message msg = queue.next(); //有可能阻塞
//...省略部分代碼
try {
//msg.target就是Handler阶祭,把獲取到的消息分發(fā)出去
msg.target.dispatchMessage(msg);
} finally {
}
//...省略部分代碼
}
}
Message next() {
//...省略部分代碼
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);//根據(jù)nextPollTimeoutMillis是阻塞還喚醒
synchronized (this) {
Message msg = mMessages;
if (msg != null && msg.target == null) {
//...省略部分代碼
}
if (msg != null) {
if (now < msg.when) {
// 當(dāng)頭消息延遲時(shí)間大于當(dāng)前時(shí)間绷杜,阻塞消息要到延遲時(shí)間和當(dāng)前時(shí)間的差值
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {}
//...省略部分代碼
} else {
nextPollTimeoutMillis = -1;//隊(duì)列已無消息,一直阻塞
}
}
//...省略部分代碼
}
}
Looper輪詢器調(diào)用loop()函數(shù)開始輪詢濒募,真正干活的是MessageQueue.next()函數(shù),而next()函數(shù)中獲取消息前首先調(diào)用了nativePollOnce(ptr, nextPollTimeoutMillis)函數(shù)圾结,其意思是根據(jù)nextPollTimeoutMillis判斷是否進(jìn)行阻塞瑰剃,nextPollTimeoutMillis初始化時(shí)默認(rèn)為0表表示不阻塞。
nextPollTimeoutMillis有三種對應(yīng)的狀態(tài):
- nextPollTimeoutMillis=0 筝野,不阻塞
- nextPollTimeoutMillis<0 晌姚,一直阻塞
- nextPollTimeoutMillis>0 ,阻塞對應(yīng)時(shí)長歇竟,可被新消息喚醒
MessageQueue輪詢獲取Message時(shí)有兩種阻塞情況:
- 當(dāng)輪詢MessageQueue時(shí)獲取獲取不到Message挥唠,nextPollTimeoutMillis賦值為-1進(jìn)行阻塞。
- 當(dāng)輪詢MessageQueue時(shí)獲取獲取到Message焕议,msg.when大于當(dāng)前時(shí)間宝磨,時(shí)間差值就是阻塞的時(shí)長。
Handler的阻塞
在MessageQueue類中盅安,有幾個Native層的函數(shù)
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
在MessageQueue的構(gòu)造函數(shù)中唤锉,就調(diào)用nativeInit()函數(shù)進(jìn)行初始化。
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
也就是說在創(chuàng)建MessageQueue對象時(shí)别瞭,通過JNI調(diào)用native層的android_os_MessageQueue_nativeInit()函數(shù)進(jìn)行初始化窿祥。進(jìn)入源碼看看里面做了那些事情。
//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
// 創(chuàng)建一個與java對應(yīng)的MessageQueue
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);//返回到Java層
}
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();//獲取該線程關(guān)聯(lián)的Looper
if (mLooper == NULL) {
mLooper = new Looper(false);// 創(chuàng)建一個Looper對象
Looper::setForThread(mLooper);// 將Looper保存到線程里蝙寨,也相當(dāng)于Java中用ThreadLocal保存Looper一樣
}
}
//Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK);// 創(chuàng)建一個喚醒事件fd
AutoMutex _l(mLock);
rebuildEpollLocked();// 重構(gòu)epoll事件
}
#include<sys/eventfd.h>
int eventfd(unsigned int initval,int flags);
void Looper::rebuildEpollLocked() {
if (mEpollFd >= 0) {
close(mEpollFd);//關(guān)閉epoll
}
// 創(chuàng)建一個epoll實(shí)例
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
//重新設(shè)置
memset(& eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN;// 監(jiān)聽可讀事件
eventItem.data.fd = mWakeEventFd;//喚醒事件fd
// 注冊喚醒事件mWakeEventFd到epoll實(shí)例中
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
// 將請求中的事件注冊到epoll實(shí)例中
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
// 初始化請求事件
request.initEventItem(&eventItem);
// 注冊請求中的事件到epoll實(shí)例中
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d while rebuilding epoll set, errno=%d",
request.fd, errno);
}
}
}
在調(diào)用android_os_MessageQueue_nativeInit()函數(shù)做了些什么事情:
- 創(chuàng)建一個NativeMessageQueue對象與Jave層相對應(yīng)的MessageQueue對象晒衩。
- 獲取一個MessageQueue相對應(yīng)Looper對象,沒有獲取則創(chuàng)建一個Looper 對象墙歪。
- 如果創(chuàng)建Looper對象听系,則保存Looper對應(yīng)的線程。
了解了nativeInit()層初始化時(shí)后箱亿,其實(shí)也不難明白跛锌,說穿了就是構(gòu)建一個Java層一樣的處理方式而已。
值得注意的是 eventfd(0, EFD_NONBLOCK)
這句代碼届惋,它是什么意思呢髓帽。eventfd函數(shù)會創(chuàng)建一個事件描述符對象,通過IO多路復(fù)用機(jī)制epoll可以監(jiān)聽事件描述符脑豹,實(shí)現(xiàn)進(jìn)程(線程)間的等待(wait)/通知(notify)郑藏,對就這么簡單。
eventfd()函數(shù)的標(biāo)志參數(shù)有兩種:
- EFD_NONBLOCK:設(shè)置對象為非阻塞狀態(tài)瘩欺。
- EFD_CLOEXEC:調(diào)用exec后會自動關(guān)閉文件描述符必盖,防止泄漏拌牲。
值得注意的是 rebuildEpollLocked()函數(shù)
,在函數(shù)里歌粥,創(chuàng)建了epoll實(shí)例塌忽,向epoll中注冊喚醒監(jiān)聽事件和請求監(jiān)聽事件。其實(shí)通過Linux系統(tǒng)的epoll機(jī)制失驶,來實(shí)現(xiàn)線程間的等待與喚醒操作土居。好可以簡單理解為epoll就是監(jiān)聽內(nèi)容的讀寫變化,來實(shí)現(xiàn)阻塞或喚醒操作嬉探。
nativePollOnce()函數(shù)
MassgeQueue#nativePollOnce()函數(shù)是Native層調(diào)用的擦耀,那么我們進(jìn)行Native層,通過源碼看看他的調(diào)用流程涩堤。
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
//獲取NativeMessageQueue
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);// 調(diào)用pollOnce()函數(shù)
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);// 調(diào)用Looper#pollOnce()函數(shù)
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {//異步處理
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
//Looper.cpp
inline int pollOnce(int timeoutMillis) {
return pollOnce(timeoutMillis, NULL, NULL, NULL);
}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
// 一個循環(huán)不斷處理響應(yīng)列表中的事件眷蜓。
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
}
if (result != 0) {
if (outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = 0;
if (outData != NULL) *outData = NULL;
return result;
}
result = pollInner(timeoutMillis);// 內(nèi)部輪詢
}
}
//循環(huán)處理內(nèi)部事件
int Looper::pollInner(int timeoutMillis) {
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
//記錄當(dāng)前時(shí)間
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
if (messageTimeoutMillis >= 0
&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
timeoutMillis = messageTimeoutMillis;
}
}
// 事件處理類型為POLL_WAKE 喚醒
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
mPolling = true;//正在輪詢
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//等待事件
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
//不要輪詢啦
mPolling = false;
//獲取鎖
mLock.lock();
//如果需要,重新生成epoll集胎围。
if (mEpollRebuildRequired) {
mEpollRebuildRequired = false;
rebuildEpollLocked();
goto Done;
}
//POLL_ERROR Poll錯誤
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
ALOGW("Poll failed with an unexpected error, errno=%d", errno);
result = POLL_ERROR;
goto Done;
}
//POLL_TIMEOUT Poll超時(shí)
if (eventCount == 0) {
result = POLL_TIMEOUT;
goto Done;
}
//循環(huán)處理所有事件
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;//獲取文件描述符
uint32_t epollEvents = eventItems[i].events;//獲取事件
if (fd == mWakeEventFd) {//如果是喚醒事件
if (epollEvents & EPOLLIN) {
awoken();//喚醒
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {//處理請求隊(duì)列中的事件
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
//請求事件添加到Response數(shù)組中
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ;
//處理MessageEnvelopes的事件
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
// 如果消息的處理時(shí)間小于當(dāng)前時(shí)間吁系,則將從列表中移除
if (messageEnvelope.uptime <= now) {
{ // obtain handler
sp<MessageHandler> handler = messageEnvelope.handler;//處理消息的handler
Message message = messageEnvelope.message;//獲取消息
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock();//釋放鎖
handler->handleMessage(message);//處理消息
} // release handler
mLock.lock();//加上鎖
mSendingMessage = false;
result = POLL_CALLBACK;//事件處理類型為POLL_CALLBACK
} else {
mNextMessageUptime = messageEnvelope.uptime;//更新下一個消息的處理時(shí)間
break;
}
}
//釋放鎖
mLock.unlock();
//處理所有Responses事件
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
// 如果響應(yīng)類型為POLL_CALLBACK
if (response.request.ident == POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
//調(diào)用callback類型的handleEvent方法
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd, response.request.seq);
}
response.request.callback.clear();
result = POLL_CALLBACK;//事件處理類型為POLL_CALLBACK
}
}
return result;
}
void Looper::awoken() {
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}
從java層調(diào)用Native層的nativePollOnce()函數(shù),通過分析整理得出調(diào)用的主路線:nativePollOnce() -> NativeMessageQueue.pollOnce() -> Looper.pollOnce() -> Looper.pollInner()
注意Java傳入的mPtr變量痊远,mPtr變量保存了一個NativeMessageQueue對象垮抗,從而使得MessageQueue成為Java層和Native層的連接橋梁,使得Java層與native層就可以相互處理消息了碧聪。
從源碼的過程得知冒版,消息的處理都放在Looper.pollInner()函數(shù)中處理了,我們重點(diǎn)分析pollInner()函數(shù)逞姿。
1辞嗡、首先是參數(shù)timeoutMillis(從Java傳入的nextPollTimeoutMillis)做相應(yīng)的處理,而timeoutMillis有三狀態(tài):
- 等于0滞造,馬上返回续室,也就是沒有阻塞。
- 小于0谒养,一直阻塞挺狰,直接向消息隊(duì)列中添加消息,才處理事件买窟。
- 大于0丰泊,時(shí)間差值是多少,那么就等待多久始绍,時(shí)間到瞳购,才處理事件。
2亏推、調(diào)用epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)獲取到等待事件eventCount 学赛。
eventCount 也有三種狀態(tài):
- 等于0年堆,返回result =POLL_TIMEOUT,表示在超時(shí)之前盏浇,要準(zhǔn)備的數(shù)據(jù)沒有準(zhǔn)備好变丧,即為等待超時(shí)。
- 小于0绢掰,如果errno == EINTR锄贷,返回result =POLL_WAKE,否則返回result = POLL_ERROR曼月,發(fā)生了錯誤。
- 大于0柔昼,所有數(shù)據(jù)都準(zhǔn)備好了哑芹。如果返回result =POLL_WAKE,通過wake()方法喚醒的捕透。如果返回result = POLL_CALLBACK聪姿,在一個或多個文件描述符被觸發(fā)了。
從eventCount 這三種情況乙嘀,pollInner()函數(shù)返回的值就是pollOnce()函數(shù)的返回值末购,就是POLL_WAKE、POLL_TIMEOUT虎谢、POLL_ERROR盟榴、POLL_CALLBACK四種狀態(tài)。
3婴噩、獲取到等待事件擎场,首先處理eventCount 的事件,也就是處理傳入epoll_wait()函數(shù)的等待事件几莽。如果fd == mWakeEventFd迅办,并且 epollEvents & EPOLLIN,說明有消息隊(duì)列中有新的消息要處理章蚣,調(diào)用awoken()函數(shù)站欺,它只把管道中內(nèi)容讀取出來,清空管道纤垂,方便下一次調(diào)用epoll_wait()函數(shù)時(shí)矾策,再次阻塞等待。
4洒忧、接著處理MessageEnvelopes事件蝴韭,如果消息的處理時(shí)間小于當(dāng)前時(shí)間,則將從消息列表中移除此消息熙侍,并且消費(fèi)掉這條消息榄鉴,最后更新下一個消息的處理時(shí)間履磨。
5、最后處理Responses事件庆尘,如果響應(yīng)類型為POLL_CALLBACK剃诅,然后處理這個事件,如果callbackResult等于0驶忌,則移除文件描述符矛辕,最后也callback.clear()掉。
Handler的喚醒
有Handler阻塞付魔,那么肯定有Handler喚醒聊品,對吧,什么時(shí)候喚醒呢几苍,由于一開始時(shí)翻屈,MessageQueue是沒有Message的,從而進(jìn)入了阻塞等待妻坝,如果向MessageQueue插入一條消息呢伸眶,是不是會喚醒啊。其實(shí)消息從消息隊(duì)列中全部移除quit()時(shí)刽宪,可能需要調(diào)用nativeWake方法厘贼。那么我們從MessageQueue.enqueueMessage()函數(shù)入手吧。
//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
//...省略部分代碼
if (needWake) {//調(diào)用Native的喚醒機(jī)制
nativeWake(mPtr);
}
//...省略部分代碼
return true;
}
喚醒是有兩種情況:
- 當(dāng)消息隊(duì)列中沒有消息時(shí)圣拄,Handler發(fā)送一條新消息到消息隊(duì)列中嘴秸,那么就會調(diào)用Native層的nativeWake(mPtr)函數(shù)。
- 當(dāng)消息隊(duì)列的消息的時(shí)間售担,與當(dāng)前時(shí)間的比值赁遗,就是需要等待的時(shí)間,這個會傳入Native層族铆,時(shí)間到則自動喚醒岩四。
nativeWake()函數(shù)
我們從調(diào)用Native層的nativeWake(mPtr)函數(shù),通過源碼去分析它們的調(diào)用過程吧哥攘。
//android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
//獲取到當(dāng)前的消息隊(duì)列
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();//調(diào)用NatvieMessageQueue的wake()函數(shù)
}
void NativeMessageQueue::wake() {
mLooper->wake();//調(diào)用Looper的wake()函數(shù)
}
//Looper.cpp
void Looper::wake() {
uint64_t inc = 1;
//向mWakeEventFd(喚醒文件描述符)寫入字符
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
看到了Nativen層的喚醒調(diào)用過程是不是很簡單剖煌。最后直接調(diào)用了TEMP_FAILURE_RETRY函數(shù),其他就是向管道中寫入數(shù)據(jù)逝淹,管道監(jiān)聽到有數(shù)據(jù)寫入就是喚醒Android應(yīng)用程序主線程處理事件耕姊。關(guān)于Linux系統(tǒng)的epoll機(jī)制,管道栅葡,涉及到了Binder相關(guān)知識茉兰,就不展開討論了。
Handler的同步屏障
什么是同步屏障
同步屏障:阻礙同步消息欣簇,優(yōu)先執(zhí)行異步消息规脸。
為什么要設(shè)計(jì)同步屏障
從上述代碼的Handler構(gòu)造函數(shù)可知坯约,一般情況下,默認(rèn)調(diào)用Handler(callback, false)構(gòu)造函數(shù)莫鸭,也是就是mAsynchronous = false闹丐,同時(shí)在Handler.enqueueMessage函數(shù)中msg.target已經(jīng)賦值了當(dāng)前的Handler對象,在MessageQueue.enqueueMessage中按執(zhí)行時(shí)間順序把Message插入到Message鏈表合適的位置被因。在調(diào)用MessageQueue.next函數(shù)獲取Message時(shí)添加了synchronied鎖卿拴,所以取消息的時(shí)候是互斥取消息,只能從頭部取消息梨与,也因?yàn)榧酉⑹前凑障⒌膱?zhí)行的先后順序進(jìn)行堕花。如果要優(yōu)先立即執(zhí)行某條Message時(shí),按正常情況是要排隊(duì)的粥鞋,是沒法做到立即執(zhí)行航徙,所以就引入了同步屏障。
如何開啟同步屏障
我們通過MessageQueue.postSyncBarrier() 函數(shù)陷虎,是如何引入同步屏障的。源碼如下:
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();//從消息對象池中獲取一條消息
msg.markInUse();
//將消息信息初始化賦值杠袱,注意這里并沒有給target賦值尚猿,這是關(guān)鍵
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
//如果開啟同步屏障的時(shí)間(假設(shè)記為T)T不為0,且當(dāng)前的同步消息里有時(shí)間小于T楣富,則prev也不為null
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
//將 msg 按照時(shí)間順序插入到 消息隊(duì)列(鏈表)的合適位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;//返回一個序號凿掂,通過這個序號可以撤銷屏障
}
}
從上述開啟同步屏障的源碼中可以看出,Message 對象初始化時(shí)并沒有給 target 賦值纹蝴,也就是說向MessageQueue中插入一條target 為null標(biāo)記的Message庄萎,相對于正常的enqueue操作,在Handler.enqueueMessage函數(shù)中Handler與msg.target綁定了 塘安。同步屏障的Message特殊在于 target 為 null糠涛,并不會被消費(fèi),因?yàn)椴粫拘严㈥?duì)列兼犯。
在那里消費(fèi)掉同步屏障的Message忍捡,上述代碼:Looper.loop()函數(shù)中調(diào)用了MessageQueue.next()函數(shù),那么我們再看一次next函數(shù)源碼:
Message next() {
//...省略部分代碼
for (;;) {
//...省略部分代碼
nativePollOnce(ptr, nextPollTimeoutMillis);//根據(jù)nextPollTimeoutMillis是阻塞還喚醒
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//target為null 說明這是一條同步屏障消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//如果是異步切黔,則獲取并消費(fèi)這條消息
}
}
//...省略部分代碼
}
}
從next函數(shù)中可以看出砸脊,MessageQueue中的msg.target為null說明開啟了同步屏障,同時(shí)是異步纬霞,那么Message則會優(yōu)先處理凌埂,這就是同步屏障的作用(過濾和優(yōu)先作用)。
我們來一形象圖诗芜,加深印象:
發(fā)送異步Message
發(fā)送同步和異步Message都是在Handler的幾個構(gòu)造函數(shù)瞳抓,可以傳入async標(biāo)志為true埃疫,這樣構(gòu)造的Handler發(fā)送的消息就是異步消息。
public Handler(boolean async) {
this(null, async);
}
public Handler(Callback callback, boolean async) {
//...省略代碼
}
public Handler(Looper looper, Callback callback, boolean async) {
//...省略代碼
}
//最終調(diào)用enqueueMessage函數(shù)挨下,把消息插入到消息隊(duì)列中
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//this就是Handler Message中持有一個Handler
//為發(fā)送消息出隊(duì)列交給handler處理埋下伏筆熔恢。
msg.target = this;
if (mAsynchronous) {//是否是異步信息
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//調(diào)用消息隊(duì)列的入隊(duì)函數(shù)
}
當(dāng)然我們也可以在創(chuàng)建Message時(shí),調(diào)用Message.setAsynchronous(true)將消息設(shè)為異步臭笆。
發(fā)送異步消息和發(fā)送同步消息一樣叙淌,唯一區(qū)別就在于Asynchronous的設(shè)置即可。
移除同步屏障
有啟動同步屏障愁铺,那么就有移除同步屏障鹰霍,我們看MessageQueue.removeSyncBarrier()函數(shù)源碼是怎么移除同步屏障的:
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
//找到token對應(yīng)的屏障
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
final boolean needWake;
//從消息鏈表中移除
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
//回收Message到對象池中。
p.recycleUnchecked();
if (needWake && !mQuitting) {
nativeWake(mPtr);//喚醒消息隊(duì)列
}
}
在啟動同步屏障時(shí)茵乱,已經(jīng)記錄了token茂洒,然后通過token對應(yīng)的屏障,從消息鏈表中移除瓶竭,回收Message到對象池中督勺。
同步消息屏障的應(yīng)用場景
同步屏障在系統(tǒng)源碼中有哪些使用場景呢?我們?nèi)粘i_發(fā)中也很少用到同步消息屏障斤贰,涉及到同步消息屏障一般都是Android系統(tǒng)中使用得最多了智哀,比如我們的UI更新相關(guān)的消息,就是利用同步屏障發(fā)送異步消息荧恍,則會優(yōu)先處理瓷叫,從而達(dá)到馬上刷新UI。既然知道了Android系統(tǒng)中刷新UI使用了異步消息送巡,那么我們看看View的更新摹菠,draw()、requestLayout()骗爆、invalidate() 等函數(shù)都調(diào)用了次氨。
我們從View的繪制流程起始,View通過ViewRootImpl來繪制摘投,ViewRootImpl調(diào)用到requestLayout()來完成View的繪制操作:知道這個流程糟需,我們看ViewRootImpl.requestLayout()函數(shù)源碼:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//檢查是否在主線程
mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。
//重要函數(shù)
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//設(shè)置同步障礙谷朝,確保mTraversalRunnable優(yōu)先被執(zhí)行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//內(nèi)部通過Handler發(fā)送了一個異步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
//移除同步屏障
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
調(diào)用 Handler.getLooper().getQueue().postSyncBarrier() 并設(shè)置同步屏障消息洲押。
最終調(diào)用Choreographer.postCallbackDelayedInternal()函數(shù),在其函數(shù)中設(shè)置Message.setAsynchronous(true) 時(shí) 圆凰,也就發(fā)送異步消息杈帐。
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);//獲取消息對象
msg.arg1 = callbackType;
msg.setAsynchronous(true);//設(shè)置為異步
mHandler.sendMessageAtTime(msg, dueTime);//發(fā)送異步消息
}
}
}
調(diào)用Handler.getLooper().getQueue().removeSyncBarrier()函數(shù)移除同步屏障。最終調(diào)用Choreographer.removeCallbacksInternal()函數(shù)移除消息。
private void removeCallbacksInternal(int callbackType, Object action, Object token) {
synchronized (mLock) {
mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
if (action != null && token == null) {
//移除消息
mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
}
}
}
總結(jié)
- 線程切換挑童,在子線程發(fā)送Message累铅,Message對象是存放在堆內(nèi)存,獲取到Message時(shí)站叼,主線程dispatchMessage分發(fā)處理這條消息娃兽。
- ThreadLocal的作用就是線程隔離,每個線程都提供一個變量的副本尽楔,使得每一個線程在同一時(shí)間內(nèi)訪問到的不同對象投储。
- 當(dāng)MessageQueue中沒有Message時(shí),觸發(fā)Native層進(jìn)入阻塞狀態(tài)阔馋;或者M(jìn)essageQueue中的Message更新時(shí)間沒有到時(shí)玛荞,也進(jìn)入了阻塞狀態(tài),阻塞時(shí)長就是它的更新時(shí)間呕寝。
- MessageQueue中沒有Message時(shí)勋眯,向MessageQueue中添加Message時(shí),觸發(fā)Native層的喚醒機(jī)制下梢;MessageQueue中的Message更新時(shí)間到時(shí)客蹋,也觸發(fā)喚醒。
- Handler的同步屏障孽江,就是優(yōu)化處理系統(tǒng)的Message嚼酝,并且Message是異步的。比如:Android應(yīng)用程序出現(xiàn)ANR竟坛,系統(tǒng)發(fā)起同步屏障,發(fā)送一條異步消息钧舌,界面上優(yōu)先處理這條消息了担汤,然后系統(tǒng)彈出一個ANR消息對話框。