[Input] App端消費(fèi)事件流程

這是Android Input系列的第三篇文章,前面兩篇的地址如下:

今天主要講講App端在收到事件之后,是如何消費(fèi)這些事件的座咆。

首先惩坑,我們看一個事件分發(fā)的典型Java堆棧:


image.png

可以看到扛稽,事件是從nativePollOnce分發(fā)出來的,調(diào)到了InputDispatcherReceiveronReceive方法中,然后再分發(fā)給ViewRootImpl去處理麸祷。

今天這篇文章,主要講一下App端從socket中收到事件后褒搔,是怎樣調(diào)度到InputDispatcherReceiver.onReceive方法的阶牍。下一篇文章,我們再講后續(xù)ViewRootImpl的分發(fā)流程星瘾。

開始之前走孽,先要要說明的是,接收事件的是App端的主線程琳状,最后分發(fā)和處理事件磕瓷,也是在主線程進(jìn)行操作。

之前我們講MessageQueue的時候說過,主線程會等待在epoll_wait方法困食,直到監(jiān)聽的端口有內(nèi)容寫入边翁,才會被喚醒,繼續(xù)執(zhí)行下面的流程硕盹。更詳細(xì)的內(nèi)容符匾,可以去看看我之前的文章從epoll機(jī)制看MessageQueue

點(diǎn)擊事件的處理流程就是利用的epoll機(jī)制,就是我們常說的主線程的Looper機(jī)制瘩例,下面我們一起來詳細(xì)看看源碼啊胶。

epoll機(jī)制監(jiān)聽socketFd

由前面的分析知道,我們在創(chuàng)建了socket連接后垛贤,會創(chuàng)建一個WindowInputEventReceiver對象焰坪,并將客戶端的InputChannel作為構(gòu)造函數(shù)傳入。下面我們就來看看WindowInputEventReceiver的構(gòu)造方法聘惦。

final class WindowInputEventReceiver extends InputEventReceiver {
    //inputChannel是指socket客戶端琳彩,Looper是指UI線程的Looper
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
        super(inputChannel, looper);
    }
}

WindowInputEventReceiver繼承自InputEventReceiver

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
     ...
     mInputChannel = inputChannel;
     mMessageQueue = looper.getQueue(); //UI線程消息隊(duì)列
     mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
             inputChannel, mMessageQueue);
 }

InputEventReceiver調(diào)用的是nativeInit方法部凑,進(jìn)行初始化

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject inputChannelObj, jobject messageQueueObj) {
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    //獲取UI主線程的消息隊(duì)列
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    //創(chuàng)建NativeInputEventReceiver對象
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    // 調(diào)用setFdEvents露乏,將socket連接的fd添加到主線程Looper的監(jiān)控中
    status_t status = receiver->initialize();
    return reinterpret_cast<jlong>(receiver.get());
}

nativeInit方法中,最終會調(diào)用setFdEvents方法涂邀,將socket連接的fd添加到主線程Looper的監(jiān)控中瘟仿。socket連接的fd通過InputChannel獲取。

void NativeInputEventReceiver::setFdEvents(int events) {
  if (mFdEvents != events) {
      mFdEvents = events;
      int fd = mInputConsumer.getChannel()->getFd();
      if (events) {
          //將socket客戶端的fd添加到主線程的消息池
          mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
      } else {
          mMessageQueue->getLooper()->removeFd(fd);
      }
  }
}  

addFd方法比勉,就是通過epoll_ctl將fd加入監(jiān)聽劳较,同時構(gòu)造一個Request對象,將它加到mRequests隊(duì)列中浩聋。

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    {
       // 構(gòu)造request
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.events = events;
        request.seq = mNextRequestSeq++;
        request.callback = callback; //是指nativeInputEventReceiver
        request.data = data;
        // 構(gòu)造eventItem
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            //通過epoll監(jiān)聽fd
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            //該fd的request加入到mRequests隊(duì)列
            mRequests.add(fd, request); 
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            mRequests.replaceValueAt(requestIndex, request);
        }
    } 
    return 1;
}

我們來看看Request的結(jié)構(gòu):

  • fd:存的是socket通信的fd
  • ident:0
  • events:ALOOPER_EVENT_INPUT
  • callback:就是nativeInputEventReceiver

epoll_wait被喚醒

當(dāng)監(jiān)聽的socket收到數(shù)據(jù)時观蜗,會從pollInner方法喚醒主線程Looper處理消息。

int Looper::pollInner(int timeoutMillis) {
    mPolling = true; //即將處于idle狀態(tài)
    struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //fd最大個數(shù)為16

    //等待事件發(fā)生或者超時衣洁,在nativeWake()方法墓捻,向管道寫端寫入字符;
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    mPolling = false; //不再處于idle狀態(tài)
    //循環(huán)遍歷,處理所有的事件
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        // 如果是從wakeEventFd喚醒坊夫,表示MessageQueue有新消息了砖第,會往這個fd寫入
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                // 這個方法會讀取mWakeEventFd上的所有數(shù)據(jù)
                awoken(); 
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                //處理request,生成對應(yīng)的reponse對象环凿,push到mResponses數(shù)組
                pushResponse(events, mRequests.valueAt(requestIndex));
            }
        }
    }
Done: ;
    //處理帶有Callback()方法的Response事件梧兼,執(zhí)行Reponse相應(yīng)的回調(diào)方法
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            // 處理請求的回調(diào)方法
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            // 正常處理事件會返回1,如果返回0智听,表示窗口被移除
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq); //移除fd
            }
            response.request.callback.clear(); //清除reponse引用的回調(diào)方法
            result = POLL_CALLBACK;  // 發(fā)生回調(diào)
        }
    }
    return result;
}

這個方法的流程:

  • 獲取喚醒的fd和events羽杰,從mRequest中找到對應(yīng)fd的request
  • 將events和request封裝成一個response對象渡紫,然后將他加到mResponses數(shù)組中
  • 循環(huán)處理mResponses數(shù)組中的所有response,調(diào)用request.callback->handleEvent

這個方法會調(diào)到request.callback->handleEvent考赛,也就是NativeInputEventReceiverhandleEvent方法腻惠。這個方法主要調(diào)用了consumeEvents,所以我們直接看ConsumeEvents方法欲虚。

讀取并消費(fèi)所有的message

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    // 循環(huán)消費(fèi)所有的消息
    for (;;) {
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
            if (inputEventObj) {
                //執(zhí)行Java層的InputEventReceiver.dispachInputEvent
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
            } else {
                skipCallbacks = true;
            }
        }
        if (skipCallbacks) {
            //發(fā)生異常集灌,則直接向InputDispatcher線程發(fā)送完成信號。
            mInputConsumer.sendFinishedSignal(seq, false);
        }
    }
}

循環(huán)讀取所有的消息复哆,并且調(diào)用Java層的InputEventReceiver.dispatchInputEvent方法處理事件欣喧。

status_t InputConsumer::consume(InputEventFactoryInterface* factory,
        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    //循環(huán)遍歷所有的Event
    while (!*outEvent) {
        if (mMsgDeferred) {
            mMsgDeferred = false; //上一次沒有處理的消息
        } else {
            // 通過InputChannel接收一條消息
            status_t result = mChannel->receiveMessage(&mMsg);
            if (result) {
                if (consumeBatches || result != WOULD_BLOCK) {
                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
                }
            }
        }
        }
    }
    return OK;
}

單條消息,調(diào)用InputChannel的receiveMessage讀取梯找。

status_t InputChannel::receiveMessage(InputMessage* msg) {
    ssize_t nRead;
    do {
        //讀取InputDispatcher發(fā)送過來的消息
        nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
    } while (nRead == -1 && errno == EINTR);
    return OK;
}

Java端收到事件后唆阿,會回調(diào)到WindowInputEventReceiveronInputEvent方法中,處理事件锈锤。

發(fā)送處理完信號

當(dāng)事件處理完后驯鳖,會調(diào)用finishInputEvent方法,將處理完的結(jié)果返回給系統(tǒng)久免。

從Java端的InputEventReceiver開始浅辙。

public final void finishInputEvent(InputEvent event, boolean handled) {
            int seq = mSeqMap.valueAt(index);
            mSeqMap.removeAt(index);
            // 調(diào)用native的方法
            nativeFinishInputEvent(mReceiverPtr, seq, handled);
}

調(diào)用到NativeInputEventReceiver,最終調(diào)用到sendFinishedSignal阎姥,然后調(diào)用到sendUnchainedFinishedSignal记舆。

status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
    // 組裝一個finished的信號,通過InputChannel的socket發(fā)送給系統(tǒng)
    InputMessage msg;
    msg.header.type = InputMessage::TYPE_FINISHED;
    msg.body.finished.seq = seq;
    msg.body.finished.handled = handled;
    return mChannel->sendMessage(&msg);
}

總結(jié)

App端消費(fèi)事件的流程如下:

  • 在創(chuàng)建完socketpair后呼巴,App端會用mInputChannel創(chuàng)建一個WindowInputEventReceiver對象泽腮,并且注冊對socket fd的監(jiān)聽。
  • 當(dāng)socket fd上有輸入寫入時(即有事件時)衣赶,會喚醒主線程
  • 主線程循環(huán)讀取socket fd上的InputMessage诊赊,然后將message發(fā)送給Java層的InputEventReceiver去處理
  • 處理完之后,組裝一個finished的信號府瞄,通過mInputChannel發(fā)送給system_server
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碧磅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子摘能,更是在濱河造成了極大的恐慌续崖,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件团搞,死亡現(xiàn)場離奇詭異,居然都是意外死亡多艇,警方通過查閱死者的電腦和手機(jī)逻恐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人复隆,你說我怎么就攤上這事拨匆。” “怎么了挽拂?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵惭每,是天一觀的道長。 經(jīng)常有香客問我亏栈,道長台腥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任绒北,我火速辦了婚禮黎侈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闷游。我一直安慰自己峻汉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布脐往。 她就那樣靜靜地躺著休吠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪业簿。 梳的紋絲不亂的頭發(fā)上蛛碌,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音辖源,去河邊找鬼蔚携。 笑死,一個胖子當(dāng)著我的面吹牛克饶,可吹牛的內(nèi)容都是我干的酝蜒。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼矾湃,長吁一口氣:“原來是場噩夢啊……” “哼亡脑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邀跃,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤霉咨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拍屑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體途戒,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年僵驰,在試婚紗的時候發(fā)現(xiàn)自己被綠了喷斋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唁毒。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖星爪,靈堂內(nèi)的尸體忽然破棺而出浆西,到底是詐尸還是另有隱情,我是刑警寧澤顽腾,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布近零,位于F島的核電站,受9級特大地震影響抄肖,放射性物質(zhì)發(fā)生泄漏久信。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一憎瘸、第九天 我趴在偏房一處隱蔽的房頂上張望入篮。 院中可真熱鬧,春花似錦幌甘、人聲如沸潮售。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酥诽。三九已至,卻和暖如春皱埠,著一層夾襖步出監(jiān)牢的瞬間肮帐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工边器, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留训枢,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓忘巧,卻偏偏與公主長得像恒界,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子砚嘴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

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