Android Handler中的休眠喚醒實(shí)現(xiàn)詳解
Handler中的奇奇怪怪
了解Handler原理時(shí),有一個(gè)疑問(wèn)handler中的休眠/喚醒不用Java中wait和notify呢,而是調(diào)用native方法(nativePollOnce/nativeWake)呢,奇奇怪怪 的又得需要學(xué)起來(lái),沒(méi)事找事的一天嘛。
這樣不行嗎?wait/notify 偽代碼
MessageQueue.java
//調(diào)用MessageQueue的next方法獲取消息
Message next() {
、、、
synchronized (this) {
//這時(shí)候檢查隊(duì)列有沒(méi)有消息,沒(méi)有消息調(diào)用this.wait()等待
if (message == null) {
this.wait();
}
if(有消息但消息未到期){
this.wait(time);
}
}
、、、
}
//調(diào)用MessageQueue.enqueueMessage()添加消息
enqueueMessage(Message message) {
、、、
synchronized (this) {
//消息加入隊(duì)列后會(huì)調(diào)用this.notity()喚醒next()方法
if (message != null) {
this.notify();
}
}
、、、
}
在學(xué)習(xí)nativePollOnce/nativeWake前,還需要對(duì)Linux相關(guān)的知識(shí)熟悉一下。
Linux相關(guān)
eventfd
eventfd 是從內(nèi)核2.6.22開(kāi)始支持的一種新的事件等待/通知機(jī)制。用來(lái)通知事件的文件描述符,它不僅可以用于進(jìn)程間的通信,還可以用戶內(nèi)核發(fā)信號(hào)給用戶層的進(jìn)程。簡(jiǎn)而言之:eventfd 就是用來(lái)觸發(fā)事件通知,它只有一個(gè)創(chuàng)建方法:
int eventfd(unsigned int initval, int flags); 表示創(chuàng)建一個(gè) eventfd 文件并返回文件描述符
參數(shù):initval, 初始值
參數(shù):flags
- EFD_CLOEXEC 會(huì)自動(dòng)關(guān)閉這個(gè)文件描述符。
- EFD_NONBLOCK 執(zhí)行 read / write 操作時(shí),不會(huì)阻塞。
- EFD_SEMAPHORE count 遞減 1。
相關(guān)操作
- write(): 其實(shí)是執(zhí)行 add 操作,累加 count值。
- read(): 根據(jù)設(shè)置不同的flags標(biāo)記,讀取到不同的值
EFD_SEMAPHORE:讀到的值為 1,同時(shí) count 值遞減 1。
其他的都是:讀取 count 值后置 0
阿西吧什么亂七八糟的,別急看看這個(gè)下面這個(gè)Demo;
eventfd demo
#include <cstdlib>
#include <inttypes.h>
#include <iostream>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/eventfd.h>
#include <unistd.h>
using namespace std;
int main(int argc, char* argv[]) {
int event_fd;
if (argc < 2) {
std::cout << "please input llegal argv " << endl;
exit(EXIT_FAILURE);
}
event_fd = eventfd(0, EFD_NONBLOCK);
if (event_fd == -1) {
std::cout << "create evebtFd fail" << endl;
exit(EXIT_FAILURE);
}
switch (fork()) {
case 0:
for (int j = 1; j < argc; j++) {
long u = atoi(argv[j]);
printf("Child writing %lu to efd\n", u);
write(event_fd, &u, sizeof(long));
}
printf("Child completed write loop\n");
exit(EXIT_SUCCESS);
default:
sleep(2);
long u;
printf("Parent about to read\n");
read(event_fd, &u, sizeof(long));
printf("Parents first read %lu from efd\n", u);
long u2;
read(event_fd, &u2, sizeof(long));
printf("Parents second read %lu from efd\n", u2);
exit(EXIT_SUCCESS);
}
}
?? #include <sys/eventfd.h> 是在Linux操作系統(tǒng)中的,在Mac電腦中是找不到包的,需要裝虛擬機(jī)或者其他的C++開(kāi)發(fā)軟件包,這里推薦一個(gè)在線免費(fèi)的編譯C++的軟件Lightly

Q eventfd和socket、pipe、fd_set、有什么區(qū)別和聯(lián)系?
Epoll
epoll是Linux內(nèi)核為處理大批量文件描述符而改進(jìn)的poll,是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本,它能顯著提高程序在大量并發(fā)連接中,只有少量活躍的情況下的系統(tǒng)CPU利用率。另一點(diǎn)原因就是獲取事件的時(shí)候,它無(wú)須遍歷整個(gè)被偵聽(tīng)的描述符集,只要遍歷那些被內(nèi)核IO事件異步喚醒而加入Ready隊(duì)列的描述符集合就行了。提高應(yīng)用程序效率。 來(lái)源自百度百科
epoll API
int epoll_create(int size)
創(chuàng)建 eventpoll 對(duì)象,返回一個(gè) epfd,即 eventpoll 句柄。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
對(duì)eventpoll執(zhí)行的操作,返回值:成功 0;失敗 -1
epfd 對(duì)一個(gè) eventPoll 進(jìn)行操作
op 表示要執(zhí)行的操作,包括 EPOLL_CTL_ADD (添加)、EPOLL_CTL_DEL (刪除)、EPOLL_CTL_MOD (修改);
fd 表示被監(jiān)聽(tīng)的文件描述符;
event 表示要被監(jiān)聽(tīng)的事件,包括:
- EPOLLIN(表示被監(jiān)聽(tīng)的fd有可以讀的數(shù)據(jù))
- EPOLLOUT(表示被監(jiān)聽(tīng)的fd有可以寫的數(shù)據(jù))
- EPOLLPRI(表示有可讀的緊急數(shù)據(jù))
- EPOLLERR(對(duì)應(yīng)的fd發(fā)生異常)
- EPOLLHUP(對(duì)應(yīng)的fd被掛斷)
- EPOLLET(設(shè)置EPOLL為邊緣觸發(fā))
- EPOLLONESHOT(只監(jiān)聽(tīng)一次)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
返回值:監(jiān)聽(tīng)到的產(chǎn)生的事件數(shù) 等待 epfd 監(jiān)聽(tīng)的 fd 所產(chǎn)生對(duì)應(yīng)的事件。
- epfd 表示 eventpoll句柄;
- events 表示回傳處理事件的數(shù)組;
- maxevents 表示每次能處理的最大事件數(shù);
- timeout:等待 IO 的超時(shí)時(shí)間,-1 表示一直阻塞直到來(lái) IO 被喚醒,大于 0 表示阻塞指定的時(shí)間后被喚醒
epoll 使用示例
創(chuàng)建一個(gè)管道,使用 epoll 監(jiān)聽(tīng)管道讀端,然后進(jìn)入阻塞:
#include <iostream>
#include <stdio.h>
#include <string>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <unistd.h>
using namespace std;
int main(int argc, char* argv[]) {
if (argc < 2) {
exit(EXIT_FAILURE);
}
int event_fd;
int epoll_fd;
event_fd = eventfd(0, EFD_NONBLOCK);
if (event_fd == -1) {
std::cout << "create evebtFd fail";
exit(EXIT_FAILURE);
}
epoll_fd = epoll_create(8);
if (epoll_fd < 0) {
std::cout << "create epollFd fail";
}
struct epoll_event read_event;
read_event.events = EPOLLIN;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &read_event);
switch (fork()) {
case 0:
for (int j = 1; j < argc; j++) {
long u = atoi(argv[j]);
sleep(1);
printf("Child writing %lu to efd\n", u);
write(event_fd, &u, sizeof(long));
}
printf("Child completed write loop\n");
exit(EXIT_SUCCESS);
default:
printf("Parent about to read\n");
struct epoll_event events[16];
int ret;
while (1) {
ret = epoll_wait(epoll_fd, events, 1, -1);
printf("Parent epoll_wait return ret : %d\n", ret);
if (ret > 0) {
long u;
read(event_fd, &u, sizeof(long));
printf("Parents read %lu from efd\n", u);
}
}
exit(EXIT_SUCCESS);
}
}
結(jié)果

Handler 中的 epoll 源碼分析
主要分析 MessageQueue.java 中的三個(gè) native 函數(shù):
private native static long nativeInit(); //初始化
private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞
private native static void nativeWake(long ptr); //喚醒
「nativeInit 返回long,這是為什么?」 預(yù)知一下,或許這個(gè)可以解答我們的問(wèn)題
nativeInit
首先來(lái)看 nativeInit 方法,nativeInit 在 MessageQueue 構(gòu)造函數(shù)中被調(diào)用,其返回了一個(gè)底層對(duì)象的指針:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit(); //保存NativeMessageQueue
}
//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
...
return reinterpret_cast<jlong>(nativeMessageQueue);
}
einterpret_cast<type-id> (expression)
type-id 必須是一個(gè)指針、引用、算術(shù)類型、函數(shù)指針或者成員指針。它可以把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),也可以把一個(gè)整數(shù)轉(zhuǎn)換成一個(gè)指針(先把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),再把該整數(shù)轉(zhuǎn)換成原類型的指針,還可以得到原先的指針值
返回值是NativeMessageQueue,而 NativeMessageQueue 初始化時(shí)會(huì)創(chuàng)建一個(gè)底層的 Looper 對(duì)象:
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
Looper 的構(gòu)造函數(shù)如下:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), ...{
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
...
rebuildEpollLocked();
}
有沒(méi)有熟悉的感覺(jué),這和我們的Epoll的demo很相似,首先通過(guò)創(chuàng)建eventFd, ,專門用于事件通知。接著來(lái)看 rebuildEpollLocked 方法:
void Looper::rebuildEpollLocked() {
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
...
}
可以看到我們已經(jīng)熟悉的 epoll 操作了:通過(guò) epoll_create 創(chuàng)建 epoll 對(duì)象,然后調(diào)用 epoll_ctl 添加 mWakeEventFd 為要監(jiān)聽(tīng)的文件描述符。
nativePollOnce
之前學(xué)習(xí) Handler 機(jī)制時(shí)多次看到過(guò) nativePollOnce 方法,也知道它會(huì)進(jìn)入休眠,下面就具體看看它的原理。對(duì)應(yīng)的底層調(diào)用同樣是在 android_os_MessageQueue.cpp 中:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mLooper->pollOnce(timeoutMillis);
...
}
可以看到實(shí)現(xiàn)同樣是在 Looper.cpp 中,接著來(lái)看 Looper 的 pollOnce 方法:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
for (;;) {
...
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
至此通過(guò)調(diào)用 epoll_wait 方法,當(dāng)前線程進(jìn)入休眠,等待被喚醒。
nativeWake
最后來(lái)看如何通過(guò) nativeWake 喚醒線程,首先是 android_os_MessageQueue.cpp 中:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
mLooper->wake();
}
與 nativeInit、nativePollOnce 一樣,最終實(shí)現(xiàn)都是在 Looper.cpp 中,Looper 的 wake 方法如下:
void Looper::wake() {
uint64_t inc = 1;
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));
}
}
}
其中關(guān)鍵邏輯是對(duì) mWakeEventFd 發(fā)起寫入操作,從而喚醒 nativePollOnce 中通過(guò) epoll_wait 進(jìn)入休眠的線程。
結(jié)束
回答剛開(kāi)始的疑問(wèn)handler中的休眠/喚醒不用Java中wait和notify呢,而是調(diào)用nativePollOnce/nativeWake呢?
MessageQueue.java
//調(diào)用MessageQueue的next方法獲取消息
Message next() {
、、、
synchronized (this) {
//這時(shí)候檢查隊(duì)列有沒(méi)有消息,沒(méi)有消息調(diào)用this.wait()等待
if (message == null) {
this.wait();
}
if(有消息但消息未到期){
this.wait(time);
}
}
、、、
}
//調(diào)用MessageQueue.enqueueMessage()添加消息
enqueueMessage(Message message) {
、、、
synchronized (this) {
//消息加入隊(duì)列后會(huì)調(diào)用this.notity()喚醒next()方法
if (message != null) {
this.notify();
}
}
、、、
}
private native static long nativeInit(); 返回值是nativeMessage對(duì)象,阻塞時(shí)會(huì)將mPtr當(dāng)成參數(shù)nativePollOnce();
如果單純用object.wait,那對(duì)于native層的消息是處理不到的,隊(duì)列空閑時(shí)不能只判斷Java層的MessageQueue,nativePollOnce去判斷Native層,若大家都空閑,方法會(huì)阻塞到native的epoll_wait()方法中,等待喚醒。 單純用wait和notify,只能處理java層的消息,對(duì)于系統(tǒng)的消息不能處理。
以上就是Android Handler中的休眠喚醒實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Android Handler休眠喚醒的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android設(shè)備間實(shí)現(xiàn)無(wú)線投屏的示例代碼
Android提供了MediaProjection來(lái)實(shí)現(xiàn)錄屏,通過(guò)MediaProjection可以獲取當(dāng)前屏幕的視頻流,而視頻流需要通過(guò)編解碼來(lái)壓縮進(jìn)行傳輸,通過(guò)MediaCodec可實(shí)現(xiàn)視頻的編碼和解碼,這篇文章主要介紹了android設(shè)備間實(shí)現(xiàn)無(wú)線投屏,需要的朋友可以參考下2022-06-06
Android實(shí)現(xiàn)WebView點(diǎn)擊攔截跳轉(zhuǎn)原生
這篇文章主要介紹了Android實(shí)現(xiàn)WebView點(diǎn)擊攔截跳轉(zhuǎn)原生,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Flutter彈性布局Flex水平排列Row垂直排列Column使用示例
這篇文章主要為大家介紹了Flutter彈性布局Flex水平排列Row垂直排列Column使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Android中MPAndroidChart自定義繪制最高點(diǎn)標(biāo)識(shí)的方法
目前在做一款軟件,要求在展示走勢(shì)圖的時(shí)候?qū)ψ罡唿c(diǎn)進(jìn)行自定義繪制,下面這篇文章主要給大家介紹了關(guān)于Android中MPAndroidChart自定義繪制最高點(diǎn)標(biāo)識(shí)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
android自定義View實(shí)現(xiàn)圓環(huán)顏色選擇器
這篇文章主要介紹了android自定義View實(shí)現(xiàn)圓環(huán)顏色選擇器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
Android學(xué)習(xí)之AppWidget筆記分享
這篇文章主要為大家詳細(xì)介紹了Android學(xué)習(xí)筆記之AppWidget的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-08-08
Android 自定義TextView實(shí)現(xiàn)滑動(dòng)解鎖高亮文字
這篇文章主要介紹了Android 自定義TextView實(shí)現(xiàn)滑動(dòng)解鎖高亮文字的相關(guān)資料,需要的朋友可以參考下2018-03-03
Flutter?文字中劃線動(dòng)畫StrikeThroughTextAnimation
這篇文章主要為大家介紹了Flutter?文字中劃線動(dòng)畫StrikeThroughTextAnimation示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
android仿知乎標(biāo)題欄隨ScrollView滾動(dòng)變色
這篇文章主要為大家詳細(xì)介紹了android仿知乎標(biāo)題欄隨ScrollView滾動(dòng)變色,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06

