前言
Handler不管是作為一種消息機制,還是作為切換線程的手段关摇,在Android中都有充足的應用場景。在不了解Handler原理的情況下瑞你,僅知道上層API如何發(fā)送消息烛愧,如何處理消息油宜,加之了解一些Handler容易造成的問題以及應對策略,實際上也不會造成什么大問題怜姿。
Handler的戲份比上面所描述的要重慎冤,因為一個APP的運行過程,是不斷接受消息以及處理消息的過程沧卢。比如Activity蚁堤,從啟動、創(chuàng)建但狭、生命周期回調(diào)披诗、銷毀撬即,都是借由Handler發(fā)送消息來驅(qū)動完成。從一個APK的安裝呈队,到一個View的更新剥槐,都離不開Handler的幫助。
如果對于以下問題有疑問宪摧,那這篇文章可能有借鑒價值:
- Handler如何保證運行在目標線程
- Handler容易造成內(nèi)存泄漏的原因
- loop()為什么不會阻塞粒竖,CPU為什么不會忙等
- MessageQueue如何存儲
- Message如何緩存
- 什么是線程空閑消息
- 線程如何使用Handler機制
note: 本文源碼版本為8.0
Handler 如何運行
Handler角色分配:
Handler中存在四種角色
Handler
Handler用來向Looper發(fā)送消息,在Looper處理到對應的消息時绍刮,Handler在對消息進行具體的處理温圆。上層關鍵API為handleMessage(),由子類自行實現(xiàn)處理邏輯孩革。
Looper
Looper運行在目標線程里岁歉,不斷從消息隊列MessageQueue讀取消息,分配給Handler處理膝蜈。Looper起到連接的作用锅移,將來自不同渠道的消息,聚集在目標線程里處理饱搏。也因此Looper需要確保線程唯一非剃。
MessageQueue
存儲消息對象Message,當Looper向MessageQueue獲取消息推沸,或Handler向其插入數(shù)據(jù)時备绽,決定消息如何提取、如何存儲鬓催。不僅如此肺素,MessageQueue還維護與Native端的連接,也是解決Looper.loop() 阻塞問題的 Java 端的控制器宇驾。
Message
Message包含具體的消息數(shù)據(jù)倍靡,在成員變量target中保存了用來發(fā)送此消息的Handler引用。因此在消息獲得這行時機時课舍,能知道具體由哪一個Handler處理塌西。此外靜態(tài)成員變量sPool,則維護了消息緩存池以復用筝尾。
運行過程
首先捡需,需要構建消息對象。獲取消息對象從Handler.obtainMessage()系列方法可以獲取Message忿等,這一系列的函數(shù)提供了相應對應于Message對象關鍵成員變量對應的函數(shù)參數(shù)栖忠,而無論使用哪一個方法獲取,最終通過Message.obtain()獲取具體的Message對象。
// 緩存池
private static Message sPool;
// 緩存池當前容量
private static int sPoolSize = 0;
// 下一節(jié)點
Message next;
public static Message obtain() {
// 確保同步
synchronized (sPoolSync) {
if (sPool != null) {
// 緩存池不為空
Message m = sPool;
// 緩存池指向下一個Message節(jié)點
sPool = m.next;
// 從緩存池拿到的Message對象與緩存斷開連接
m.next = null;
m.flags = 0; // clear in-use flag
// 緩存池大小減一
sPoolSize--;
return m;
}
}
// 緩存吃沒有可用對象庵寞,返回新的Message()
return new Message();
}
Message成員變量中存在類型為Message的next狸相,可以看出Message為鏈表結構,而上面代碼從緩存池里獲取消息對象的過程可以用下圖描述:
創(chuàng)建出消息之后捐川,通過Handler將消息發(fā)送到消息隊列脓鹃,發(fā)送方法有很多,不一一陳列古沥。發(fā)送發(fā)上有兩種:
- 將Message對象發(fā)送到
- 發(fā)送Runnable瘸右,通過getPostMessage()將Runnable包裝在Message里,表現(xiàn)為成員變量callback
private static Message getPostMessage(Runnable r) {
// 獲取Message
Message m = Message.obtain();
// 記住Runnale岩齿,等消息獲得執(zhí)行時回調(diào)
m.callback = r;
return m;
}
不管哪種方式發(fā)送太颤,最終消息隊列MessageQueue只知接受到了消息對象Message。而將消息加入到消息隊列盹沈,最終通過enqueueMessage()加入龄章。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// Message.target 記住 Handler 以明確是由哪一個Handler來處理這個消息的
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 消息入隊
return queue.enqueueMessage(msg, uptimeMillis);
}
在將消息加入消息隊列時,有時需要提供延遲信息delayTime乞封,以期未來多久后執(zhí)行做裙,這個值存于 uptimeMillis。
之后肃晚,等待Looper輪詢從消息隊列中讀取消息進行處理锚贱。見Looper.loop()
public static void loop() {
// 拿到Looper
final Looper me = myLooper();
if (me == null) {
// 沒調(diào)用prepare初始化Looper,報錯
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 拿到消息隊列
final MessageQueue queue = me.mQueue;
......
for (;;) {
// 從消息隊列取出下一個信息
Message msg = queue.next();
if (msg == null) {
// 消息為空关串,返回
return;
}
.......
try {
// 分發(fā)消息到Handler
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
}
// 消息回收拧廊,放入緩存池
msg.recycleUnchecked();
}
Looper從MessageQueue里取出Message,Message.target則是具體的Hander晋修,Handler.dispatchMessage()將觸發(fā)具體分配邏輯卦绣。此后,將Message回收飞蚓,放入緩存池。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 這個情況說明了次消息為Runnable廊蜒,觸發(fā)Runnable.run()
handleCallback(msg);
} else {
if (mCallback != null) {
// 指定了Handler的mCallback
if (mCallback.handleMessage(msg)) {
return;
}
}
// 普通消息處理
handleMessage(msg);
}
}
Handler分配消息分三種情況:
- 可以通過Handler發(fā)送Runnable消息到消息隊列趴拧,因此handleCallback()處理這種情況
- 可以給Handler設置Callback,當分配消息給Handler時山叮,Callback可以優(yōu)先處理此消息著榴,如果Callback.handleMessage()返回了true,不再執(zhí)行Handler.handleMessage()
- Handler.handleMessage()處理具體邏輯
回收則是通過Message.recycleUnchecked()
void recycleUnchecked() {
// 這里是將Message各種屬性重置操作
......
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
// 緩存池還能裝下屁倔,回收到緩存池
// 下面操作將此Message加入到緩存池頭部
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
通過上面的分析脑又,Handler的運行如下圖
- Handler 從緩存池獲取Message,發(fā)送到MessageQueue
- Looper不斷從MessageQueue讀取消息,通過Message.target.dispatchMessage()觸發(fā)Handler處理邏輯
- 回收Message到緩存池
目前來看问麸,可以算是了解了Handler的運行機制往衷,但是對于解答開篇提出的問題,捉襟見肘严卖,需要深入Handler席舍。
Java端與Native端建立連接
實際上,不僅僅是Java端存在Handler機制哮笆,在Native端同樣存在Handler機制来颤。他們通過MessageQueue建立了連接。
一般來說稠肘,Looper通過prepare()進行初始化
private static void prepare(boolean quitAllowed) {
// 保證Looper在線程唯一
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 將Looper放入ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
在實例化Looper時福铅,需要確保Looper在線程里是唯一的。Handler知道自己的具體Looper對象项阴,而Looper運行在具體的線程里并在此線程里處理消息滑黔。這也是為什么Looper能達到切換線程的目的。Looper線程唯一需要ThreadLocal來確保鲁冯,ThreadLocal的原理拷沸,簡單來說Thread里有類型為ThreadLocalMap的成員threadLocals,通過ThreadLocal能將相應對象放入threadLocals里通過K/V存儲薯演,如此能保證變量在線程范圍內(nèi)存儲撞芍,其中Key為ThreadLocal< T > 。
private Looper(boolean quitAllowed) {
// 初始化MessageQueue
mQueue = new MessageQueue(quitAllowed);
// 記住當前線程
mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
// 與Native建立連接
mPtr = nativeInit();
}
在MessageQueue創(chuàng)建時跨扮,通過native方法nativeInit()與Native端建立了連接序无,mPtr為long型變量,存儲一個地址衡创。
方法實現(xiàn)文件位于frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
// 返回給Java層的mPtr, NativeMessageQueue地址值
return reinterpret_cast<jlong>(nativeMessageQueue);
}
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
// 檢查Looper 是否創(chuàng)建
if (mLooper == NULL) {
mLooper = new Looper(false);
// 確保Looper唯一
Looper::setForThread(mLooper);
}
}
在Native端創(chuàng)建了NativeMessageQueue帝嗡,同樣也創(chuàng)建了Native端的Looper。在創(chuàng)建NativeMessageQueue后璃氢,將它的地址值返回給了Java層MessageQueue.mPtr哟玷。實際上,Native端Looper實例化時做了更多事情一也。
Nativ端Looper文件位于system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
// 添加到epoll的文件描述符巢寡,線程喚醒事件的fd
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
strerror(errno));
AutoMutex _l(mLock);
rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
.....
// Allocate the new epoll instance and register the wake pipe.
// 創(chuàng)建epolle實例,并注冊wake管道
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
struct epoll_event eventItem;
// 清空椰苟,把未使用的數(shù)據(jù)區(qū)域進行置0操作
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
// 監(jiān)聽可讀事件
eventItem.events = EPOLLIN;
// 設置作為喚醒評判的fd
eventItem.data.fd = mWakeEventFd;
// 將喚醒事件(mWakeEventFd)添加到epoll實例抑月,意為放置一個喚醒機制
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
strerror(errno));
// 添加各種事件的fd到epoll實例,如鍵盤舆蝴、傳感器輸入等
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
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: %s",
request.fd, strerror(errno));
}
}
}
初次見面谦絮,上面的代碼讓人迷糊题诵,因為缺少了關鍵的知識點,epoll機制层皱。
如何理解epoll機制性锭?
文件、socket奶甘、pipe(管道)等可以進行I/O操作的對象可以視為流篷店。既然是I/O操作,則有read端讀入數(shù)據(jù)臭家,有write端寫入數(shù)據(jù)疲陕。但是兩端并不知道對方進行操作的時機。而epoll則能觀察到哪個流發(fā)生了了I/O事件钉赁,并進行通知蹄殃。
這個過程,就好比你在等快遞你踩,但你不知道快遞什么時候來诅岩,那這時你可以去睡覺,因為你知道快遞送來時一定會打個電話叫醒你带膜,讓你拿快遞吩谦,接著做你想的事情。
epoll有效地降低了CPU的使用膝藕,在線程空間時令其休眠式廷,等有事件到來時再講它喚醒。
epoll機制可以參考這里
在知道了epoll之后芭挽,再來看上面的代碼滑废,就可以理解了。在Native端創(chuàng)建Looper時袜爪,會創(chuàng)建用來喚醒線程的fd —— mWakeEventFd蠕趁,創(chuàng)建epoll實例并注冊管道,清空管道數(shù)據(jù)辛馆,監(jiān)聽可讀事件俺陋。當有數(shù)據(jù)寫入mWakeEventFd描述的文件時,epoll能監(jiān)聽到此事件昙篙,并通知將目標線程喚醒倔韭。
在Java端MessageQueue.mPrt存儲了Native端NativeMassageQueue的地址,可以利用NativeMassageQueue享用此機制瓢对。
發(fā)送數(shù)據(jù)的具體過程
之前說過,Handler發(fā)送消息時胰苏,最終通過MessageQueue.enqueueMessage向消息隊列中插入消息硕蛹,下面為具體代碼
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
// 記錄消息處理的時間
msg.when = when;
Message p = mMessages;
// 喚醒線程的標志位
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 這里三種情況:
// 1醇疼、目標消息隊列是空隊列
// 2、插入的消息處理時間等于0
// 3法焰、插入的消息處理時間小于保存在消息隊列頭的消息處理時間
// 這三種情況都插入列表頭
msg.next = p;
mMessages = msg;
// mBlocked 表示當前線程是否睡眠
needWake = mBlocked;
} else {
// 這里則說明消息處理時間大于消息列表頭的處理時間秧荆,因此需要找到合適的插入位置
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 這里的循環(huán)是找到消息的插入位置
for (;;) {
prev = p;
p = p.next;
// 到鏈表尾,或處理時間早于p的時間
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
// 如果插入的消息在目標隊列中間埃仪,是不需要檢查改變線程喚醒狀態(tài)的
needWake = false;
}
}
// 插入到消息隊列
msg.next = p;
prev.next = msg;
}
if (needWake) {
// 喚醒線程
nativeWake(mPtr);
}
}
return true;
}
消息隊列里的消息也是以鏈表形式存儲乙濒,存儲順序則按照處理的時間順序。那么在向消息隊列里插入數(shù)據(jù)時卵蛉,存在四種情況:
- 目標消息隊列是空隊列
- 插入的消息處理時間等于0
- 插入的消息處理時間小于保存在消息隊列頭的消息處理時間
- 插入的消息處理時間大于消息隊列頭的消息處理時間
前三種情況颁股,將消息插入消息隊列頭的位置,在這種情況下傻丝,因為不能保證當前消息是否達到了可以處理的狀態(tài)甘有,且如果此時線程是睡眠的,則需要調(diào)用nativeWake()將其線程喚醒葡缰。后一種情況亏掀,則需要找到消息的插入位置,因不影響線程狀態(tài)而需要改變線程狀態(tài)泛释。
插入消息如圖
mPtr保存了NativeMessageQueue的地址滤愕,所以Native可以知道具體操作的NativeMessageQueue,當前用它來喚醒線程怜校,實際調(diào)用鏈為
MessageQueue.cpp.nativeWake()
-> MessageQueue.cpp.wake()
-> Looper.cpp.wake()
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
uint64_t inc = 1;
// 向管道寫入一個新數(shù)據(jù)间影,這樣管道因為發(fā)生了IO事件被喚醒
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
mWakeEventFd, strerror(errno));
}
}
}
實現(xiàn)也簡單,向mWakeEventFd文件里寫入一個數(shù)據(jù)韭畸,根據(jù)epoll機制監(jiān)聽到此次I/O事件宇智,將線程喚醒。
消息讀取
Looper不斷從MessageQueue讀取消息進行處理胰丁,通過MessageQueue.next()進行讀取随橘。
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
// 獲取NativeMessageQueue地址失敗,無法正常使用epoll機制
return null;
}
// 用來保存注冊到消息隊列中的空閑消息處理器(IdleHandler)的個數(shù)
int pendingIdleHandlerCount = -1;
// 如果這個變量等于0锦庸,表示即便消息隊列中沒有新的消息需要處理机蔗,當前
// 線程也不要進入睡眠等待狀態(tài)。如果值等于-1甘萧,那么就表示當消息隊列中沒有新的消息
// 需要處理時萝嘁,當前線程需要無限地處于休眠等待狀態(tài),直到它被其它線程喚醒為止
int nextPollTimeoutMillis = 0;
for (;;) {
......
// 檢查當前線程的消息隊列中是否有新的消息需要處理扬卷,嘗試進入休眠
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 當前時間
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// mMessages 表示當前線程需要處理的消息
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 找到有效的Message
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
/**
* 檢查當前時間和消息要被處理的時間牙言,如果小于當前時間,說明馬上要進行處理
*/
if (now < msg.when) {
// 還沒達到下一個消息需要被處理的時間怪得,計算需要休眠的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 有消息需要處理
// 不要進入休眠
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
// 指向下一個需要處理的消息
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 沒有更多消息咱枉,休眠時間無限
nextPollTimeoutMillis = -1;
}
......
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// 獲取IdleHandler數(shù)
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 沒有IdleHandler需要處理卑硫,可直接進入休眠
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 如果沒有更多要進行處理的消息,在休眠之前蚕断,發(fā)送線程空閑消息給已注冊到消息隊列中的IdleHandler對象來處理
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
// 處理對應邏輯欢伏,并由自己決定是否保持激活狀態(tài)
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
// 不需要存活,移除
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置IdleHandler數(shù)量
pendingIdleHandlerCount = 0;
/**
* 這里置0亿乳,表示下一次循環(huán)不能馬上進入休眠狀態(tài)硝拧,因為IdleHandler在處理事件的時間里,
* 有可能有新的消息發(fā)送來過來葛假,需要重新檢查障陶。
*/
nextPollTimeoutMillis = 0;
}
}
分為兩種情況處理:
取到消息Message時
需要查看當前時間是否達到了Message處理的時間,如果達到了則返回桐款,改變mMessages指向下一消息咸这。如果沒達到,則計算要達到處理的時間魔眨,還需要休眠多久媳维,并進行休眠
沒有更多Message時
當消息隊列里沒有消息時,則會檢查是否有IdleHandler需要處理遏暴。在Handler機制里侄刽,允許添加一些事件,在線程空閑時進行處理朋凉,表現(xiàn)為IdleHandler州丹,可以通過MessageQueue.addIdleHandler添加。當有IdleHandler需要處理杂彭,則在IdleHandler處理完后墓毒,線程不能馬上進入休眠狀態(tài),在此期間可能已有新消息加入消息隊列亲怠,需要重新做檢查所计。如果沒有IdleHandler,則可以進入休眠团秽。
線程休眠調(diào)用鏈為
NativeMessageQueue.nativePollOnce()
-> NativeMessageQueue.pollOnce()
-> Looper.pollOnce()
-> Looper.pollInner()
int Looper::pollInner(int timeoutMillis) {
......
// 這個是用來監(jiān)聽實例化時創(chuàng)建的epoll實例的文件描述符的IO讀寫事件
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 如果沒有事件主胧,進入休眠,timeoutMillis為休眠事件
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
......
/**
* 檢測是哪一個文件描述符發(fā)生了IO讀寫事件
*/
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) {
// 如果文件描述符為mWakeEventFd习勤,并且讀寫事件類型為EPOLLIN踪栋,說明
// 當前線程所關聯(lián)的一個管道被寫入了一個新的數(shù)據(jù)
// 喚醒
awoken();
}
}
......
}
}
Java層提供了線程休眠時間timeoutMillis,通過epoll_wait()讓線程進行休眠图毕。當線程被喚醒后夷都,查看文件描述符,如果為mWakeEventFd并且為I/O事件予颤,則說明當當前線程所關聯(lián)的一個管道被寫入了一個新的數(shù)據(jù)囤官,通過awoken()處理厢破。當前線程已是喚醒狀態(tài),awoken()則是將管道中的數(shù)據(jù)讀出達到清理目的治拿,但并不關心數(shù)據(jù)什么。核心目的是喚醒線程笆焰。
總結
Handler機制更具體的原理如圖:
- Looper通過prepare()創(chuàng)建劫谅,借助ThreadLocal保證線程唯一,如果沒有進行prepare()嚷掠,調(diào)用Loop()會拋出異常
- Looper在實例化時創(chuàng)建MessageQueue捏检,MessageQueue與NativeMessageQueue建立連接,NativeMessageQueue存儲地址存于MessageQueue.mPtr不皆。Native端也建立了Handler機制贯城,使用epoll機制。Java端借由NativeMessageQueue能達到使用epoll機制的目的
- 從Message緩存里獲取Message霹娄,緩存為鏈表存儲能犯,從頭出取出,并且Message在回收時也是插入頭部犬耻。如果存緩存里取不到踩晶,則新建
- Handler向MessageQueue插入消息,如果消息插入消息隊列頭部枕磁,需要喚醒線程渡蜻;如果插入消息隊列中,無需改變線程狀態(tài)
- Looper.loop() 不斷從消息隊列獲取消息计济,消息隊列獲取消息時會出現(xiàn)兩種情況茸苇。如果取到消息,但沒達到處理時間沦寂,則讓線程休眠学密;如果沒有更多消息,則在處理IdleHandler事后凑队,在考慮讓線程進入休眠
- Message達到了可處理狀態(tài)则果,則有Handler處理,處理時考慮三種情況漩氨,消息內(nèi)容為Runnable時西壮、設置了Handle.Callback時、普通消息時叫惊,對應調(diào)用為Message.callback.run() 款青、 Callback.handleMessage()、Handler.handleMessage()
- 從Handler機制里霍狰,epoll可以簡單理解為抡草,當Handler機制沒有消息要處理時饰及,讓線程進入休眠,當Handler機制有消息要處理時康震,將線程喚起燎含。通過Native端監(jiān)聽mWakeEventFd的I/O事件實現(xiàn)
答疑
這里對文章一開始的問題進行回答
Handler如何保證運行在目標線程
Looper在實例化時通過ThreadLocal保證線程唯一。Looper運行在目標線程中腿短,接收Handler發(fā)送的消息并進行處理屏箍。Message創(chuàng)建時與具體的Handler進行了關聯(lián),因此能知道由哪一個Handler進行相應橘忱。
Handler容易造成內(nèi)存泄漏的原因
Message.target存有Handler的引用赴魁,以知道自身由哪一個Handler來處理。因此钝诚,當Handler為非靜態(tài)內(nèi)部類颖御、或持有關鍵對象的其它表現(xiàn)形式時(如Activity常表現(xiàn)為Context),就引用了其它外部對象凝颇。當Message得不到處理時潘拱,被Handler持有的外部對象會一直處于內(nèi)存泄漏狀態(tài)。
loop()為什么不會阻塞祈噪,CPU為什么不會忙等
通過epoll機制監(jiān)聽文件I/O時間泽铛,在有Message需要處理時,寫入數(shù)據(jù)以喚醒線程辑鲤;在沒有Message要處理時盔腔,讓線程進入休眠狀態(tài)。
MessageQueue如何存儲
以鏈表存儲月褥,MessageQueue.mMessages指向頭節(jié)點弛随。
Message如何緩存
以鏈表緩存,取出時從頭部取出宁赤,回收時插入頭部舀透。
什么是線程空閑消息
Handler提供的一種機制,允許添加IdleHandler事件决左。并在沒有更多Message要處理愕够,要進入休眠前,讓IdleHandler做具體事情佛猛,也就是線程空間時處理的事件惑芭。
子線程如何使用Handler機制
只要保證在子線程先執(zhí)行Looper.prepare()再使用Looper.Loop()即可,但實際應用場景不多继找。 順便提一句遂跟,主線程初始化Looper操作在ActivityTread.main()里觸發(fā),簡單了解即可。
參考
《Android 系統(tǒng)源代碼情景分析》 —— 第十三章
Android Handler機制10之Native的實現(xiàn)
我讀過的最好的epoll講解--轉(zhuǎn)自”知乎“