android中的消息循環(huán)&鍵盤事件

消息循環(huán)

public final class ActivityThread {  
    ......  
  
    public static final void main(String[] args) {  

        Looper.prepareMainLooper();  //sThreadLocal.set(new Looper());   創(chuàng)建looper對象
        ActivityThread thread = new ActivityThread(); //H mH = new H();   
        thread.attach(false);  
        Looper.loop();  
        thread.detach();  

    }  
}  

//sThreadLocal.set(new Looper());
每個線程的threadLocal中存儲自己的Looper,每個Looper有自己的消息隊列MessageQueue

以上是主線程消息循環(huán)的創(chuàng)建過程慎璧。對于子線程來說趁矾,創(chuàng)建Handler時使用new Handler(thread.getLooper())

Looper.cpp

每個Java層的Looper都會在native層對應一個Looper,(MessageQueue)類似艳狐,下面來看看native層的Looper的初始過程芹关。

int result = pipe(wakeFds);  //創(chuàng)建管道
mWakeReadPipeFd = wakeFds[0];  
mWakeWritePipeFd = wakeFds[1];  
mEpollFd = epoll_create(EPOLL_SIZE_HINT);  //創(chuàng)建epoll
struct epoll_event eventItem;  
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union  
eventItem.events = EPOLLIN;  
eventItem.data.fd = mWakeReadPipeFd;  
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);  //向epoll注冊讀管道

通過管道+epoll,主線程在消息隊列中沒有消息時要進入等待狀態(tài)以及當消息隊列有消息時要把應用程序主線程喚醒

監(jiān)控mWakeReadPipeFd文件描述符的EPOLLIN事件行瑞,即當管道中有內(nèi)容可讀時奸腺,就喚醒當前正在等待管道中的內(nèi)容的線程。

這里解釋一下血久,管道和epoll的作用突照。管道是負責讀和取。作為Linux弱智的我一開始總是不明白為什么有了管道還要有epoll,其實這兩個是并列的關(guān)系氧吐,epoll/poll/select都是會阻塞進程的讹蘑,在有多個Fd的時候末盔,使用它們不需要使用多個程序來一一控制,多路復用最大的意義在于可以一個socket控制多個Fd座慰。一個程序可以程序監(jiān)視多個文件句柄(file descriptor)的狀態(tài)變化陨舱。在后面,我們會看到Looper有一個addFd的接口版仔,通過epoll,mLooper中的一個`mEpollFd可以控制監(jiān)視多個文件句柄游盲。

可以簡單介紹一下epollselect最大的區(qū)別,epoll中會維持一個隊列蛮粮,記錄發(fā)生事件的Fd,而select不會維護導致上層需要遍歷找到發(fā)生事件的Fd益缎。epoll注冊的時候可以有callback,當Fd發(fā)生事件時,會去回調(diào)這個callback蝉揍。

也就是說epoll機制接管了管道讀寫,它站在了更上層链峭。

Looper.loop

初始化Ok后,進入loop循環(huán)畦娄。從此就兢兢業(yè)業(yè)從message.next()讀取消息又沾。

Message.next

主要執(zhí)行 nativePollOnce(mPtr, nextPollTimeoutMillis);
它背后實際用的就是mLooper->pollOnce(timeoutMillis);-> pollInner

pollInner

int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));  

使用epoll_wait等待事件發(fā)生或是超時,然后從readPipeFd中讀出數(shù)據(jù)熙卡。

消息發(fā)送

queue.enqueueMessage(msg, uptimeMillis);

將消息按消息的when插入到mMessage隊列中,如果當前消息隊列沒有消息杖刷,nativeWake喚醒主線程。最后也就是通過Looper喚醒:

nWrite = write(mWakeWritePipeFd, "W", 1);向管道寫入一個1

消息接受

通過write喚醒驳癌。

這里特別要注意,pipe生成的read/write Fd只是用來通知喚醒滑燃,write只會往里寫一個簡單的1,read也只會去讀出內(nèi)容是否為1颓鲜。真正的消息還是存放在下圖中的mMessage隊列中表窘。這一點還是很有意思的。(管道不能傳輸太多數(shù)據(jù)甜滨?

image.png

handler.java

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        handleCallback(msg);  
    } else {  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        handleMessage(msg);  
    }  
}  

這個也值得記錄乐严。

HandlerThread

Handler sWorker = new Handler(sWorkerThread.getLooper()); //handlerThread

初始化的時候會Looper.prepare,loop

神秘的Toast報錯 todo

來自知乎

漲姿勢,Toast在創(chuàng)建的時候會進行Handler myHandler = new Handler()
所以衣摩,如果不在主線程明顯這句代碼是會拋出錯誤的

new Thread(){
    public void run(){
      Looper.prepare();//給當前線程初始化Looper
      Toast.makeText(getApplicationContext(),"你猜我能不能彈出來~~",0).show();//Toast初始化的時候會new Handler();無參構(gòu)造默認獲取當前線程的Looper昂验,如果沒有prepare過,則拋出題主描述的異常艾扮。上一句代碼初始化過了既琴,就不會出錯。
      Looper.loop();//這句執(zhí)行泡嘴,Toast排隊show所依賴的Handler發(fā)出的消息就有人處理了甫恩,Toast就可以吐出來了。但是酌予,這個Thread也阻塞這里了磺箕,因為loop()是個for (;;) ...
    }
  }.start();

InputManager

最近分析軟鍵盤纹腌。。順便寫一起了

image.png

這圖滞磺。升薯。放大看吧

WindowManagerService在啟動的時候就會通過系統(tǒng)輸入管理器InputManager來總負責監(jiān)控鍵盤消息。這些鍵盤消息一般都是分發(fā)給當前激活的Activity窗口來處理的击困,因此涎劈,當前激活的Activity窗口在創(chuàng)建的時候,會到WindowManagerService中去注冊一個接收鍵盤消息的通道阅茶,表明它要處理鍵盤消息蛛枚,而當InputManager監(jiān)控到有鍵盤消息時,就會分給給它處理脸哀。當當前激活的Activity窗口不再處于激活狀態(tài)時蹦浦,它也會到WindowManagerService中去反注冊之前的鍵盤消息接收通道

5.png

EventHub

具體與鍵盤設(shè)備交互的類。太底層了懶得分析了,反正InputReader線程就是通過它去讀具體的鍵盤事件的撞蜂。

ViewRoot.setView

sWindowSession.add
if (outInputChannel != null) {  
     String name = win.makeInputChannelName();  
     InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);  //創(chuàng)建一對輸入通道
     win.mInputChannel = inputChannels[0];  
     inputChannels[1].transferToBinderOutParameter(outInputChannel);  //通過outInputChannel參數(shù)返回到應用程序中
     mInputManager.registerInputChannel(win.mInputChannel);  
}  

首先會創(chuàng)建一堆輸入通道,一個供server InputManager使用,一個供應用層client使用盲镶。那么這個輸入通道具體是什么,這個就比較復雜了,會創(chuàng)建一個匿名共享內(nèi)存文件和兩個管道,看大圖也能知道兩個管道一個負責client->server的讀寫,一個負責server->client的讀寫

具體來說,Server端和Client端的InputChannel分別是這樣構(gòu)成的:
Server Input Channel: ashmem - reverse(read) - forward(write)
Client Input Channel: ashmem - forward(read) - reverse(write)

最后會把兩個inputChannel分別注冊到服務層和應用層蝌诡。注冊的時候又要搞事,包了一層Connection:

int32_t receiveFd = inputChannel->getReceivePipeFd();  //反向管道的讀
mConnectionsByReceiveFd.add(receiveFd, connection);  
if (monitor) {  
      mMonitoringChannels.push(inputChannel);  
}    
mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);  

又見mLooper->addFd,前面說過,epoll可以同時監(jiān)控多個Fd,所以這里發(fā)現(xiàn)可以有讀的內(nèi)容的時候就會調(diào)用handleReceiveCallback

poll

inputReadThread會通過EventHub看是否有鍵盤事件發(fā)生溉贿,如果沒有,通過poll來睡眠等待

int pollResult = poll(mFDs, mFDCount, -1);

這是一個Linux系統(tǒng)的文件操作系統(tǒng)調(diào)用浦旱,它用來查詢指定的文件列表是否有有可讀寫的宇色,如果有,就馬上返回颁湖,否則的話宣蠕,就阻塞線程,并等待驅(qū)動程序喚醒甥捺,重新調(diào)用poll函數(shù)抢蚀,或超時返回。在我們的這個場景中涎永,就是要查詢是否有鍵盤事件發(fā)生思币,如果有的話,就返回羡微,否則的話谷饿,當前線程就睡眠等待鍵盤事件的發(fā)生了。

當鍵盤事件發(fā)生后,就通過mLooper->wake();喚醒睡眠著的InputDispatcherThread線程

然后inputDispatcherThread找到之前存儲的被激活的窗口后妈倔,把之前的Connection拿出來,并把要發(fā)送的鍵盤事件封裝后塞入ConnectionoutboundQueue事件隊列,最后放到通過前面創(chuàng)建的匿名共享內(nèi)存里(忘了嗎博投!),并通過管道通知應用層盯蝴。毅哗。所以跟handler的消息機制一樣听怕,這里的管道只起一個通知的作用,真正是從匿名共享內(nèi)存進行讀取的虑绵。

應用層處理

應用層獲取到鍵盤消息后尿瞭,通知InputMethodManager .dispatchKeyEvent處理,如果該manager沒有處理就通過View .dispatchKeyEvent處理。

總結(jié)

我們可以總結(jié)一下翅睛,

A. 鍵盤事件發(fā)生声搁,InputManager中的InputReader被喚醒,此前InputReader睡眠在/dev/input/event0這個設(shè)備文件上捕发;
B. InputReader被喚醒后疏旨,它接著喚醒InputManager中的InputDispatcher,此前InputDispatcher睡眠在InputManager所運行的線程中的Looper對象里面的管道的讀端上扎酷;這是looper內(nèi)部的管道
C. InputDispatcher被喚醒后檐涝,它接著喚醒應用程序的主線程來處理這個鍵盤事件,此前應用程序的主線程睡眠在Client端InputChannel中的前向管道的讀端上法挨;
D. 應用程序處理處理鍵盤事件之后谁榜,它接著喚醒InputDispatcher來執(zhí)行善后工作,此前InputDispatcher睡眠在Server端InputChannel的反向管道的讀端上坷剧,注意這里與第二個線索處的區(qū)別惰爬。
C/D是Looper來自外部的管道

最后再次敬佩老羅!

handler內(nèi)存泄漏 todo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惫企,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陵叽,更是在濱河造成了極大的恐慌狞尔,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巩掺,死亡現(xiàn)場離奇詭異偏序,居然都是意外死亡,警方通過查閱死者的電腦和手機胖替,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門研儒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人独令,你說我怎么就攤上這事端朵。” “怎么了燃箭?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵冲呢,是天一觀的道長。 經(jīng)常有香客問我招狸,道長敬拓,這世上最難降的妖魔是什么邻薯? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮乘凸,結(jié)果婚禮上厕诡,老公的妹妹穿的比我還像新娘。我一直安慰自己营勤,他們只是感情好木人,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冀偶,像睡著了一般醒第。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上进鸠,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天稠曼,我揣著相機與錄音,去河邊找鬼客年。 笑死霞幅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的量瓜。 我是一名探鬼主播司恳,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绍傲!你這毒婦竟也來了扔傅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烫饼,失蹤者是張志新(化名)和其女友劉穎猎塞,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杠纵,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡荠耽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了比藻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铝量。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖银亲,靈堂內(nèi)的尸體忽然破棺而出慢叨,到底是詐尸還是另有隱情,我是刑警寧澤群凶,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布插爹,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赠尾。R本人自食惡果不足惜力穗,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望气嫁。 院中可真熱鬧当窗,春花似錦、人聲如沸寸宵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梯影。三九已至巫员,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甲棍,已是汗流浹背简识。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留感猛,地道東北人七扰。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像陪白,于是被迫代替她去往敵國和親颈走。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容