Android7 Input(八)App Input事件接收器InputEventReceiver

概述

????????上一個章節,我們講解了App如何使用InputChannel通道與input系統服務建立通信的橋梁的過程,本章我們講述App如何從input系統服務中獲取上報的輸入事件,也就是我們本章講述的InputEventReceiver。

本文涉及的源碼路徑

frameworks/base/core/java/android/view/InputChannel.java

frameworks/base/core/java/android/view/ViewRootImpl.java

frameworks/base/core/jni/android_view_InputChannel.cpp

frameworks/base/core/jni/android_view_InputEventReceiver.cpp

frameworks/base/core/jni/com_android_server_input_InputManagerService.cpp

frameworks/base/services/core/java/com/android/server/wm/Session.java

frameworks/base/core/java/android/view/WindowState.java

WindowInputEventReceiver事件接收器

上一章節,我們講述了當App初始化完成InputChannel后,會創建一個事件接收器,如下所示:

mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());

其中 WindowInputEventReceiver是InputEventReceiver的子類,實現如下所示:

final class WindowInputEventReceiver extends InputEventReceiver {public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}@Overridepublic void onInputEvent(InputEvent event) {enqueueInputEvent(event, this, 0, true);}@Overridepublic void onBatchedInputEventPending() {if (mUnbufferedInputDispatch) {super.onBatchedInputEventPending();} else {scheduleConsumeBatchedInput();}}@Overridepublic void dispose() {unscheduleConsumeBatchedInput();super.dispose();}}

從中我們可以知道,核心實現是InputEventReceiver,因此接下來我們重點講述InputEventReceiver的實現。

InputEventReceiver 輸入事件接收器

InputEventReceiver的類,我們主要講述核心方法的實現,如下所示:

public abstract class InputEventReceiver {......private long mReceiverPtr;private InputChannel mInputChannel;private MessageQueue mMessageQueue;// Map from InputEvent sequence numbers to dispatcher sequence numbers.private final SparseIntArray mSeqMap = new SparseIntArray();private static native long nativeInit(WeakReference<InputEventReceiver> receiver,InputChannel inputChannel, MessageQueue messageQueue);private static native void nativeDispose(long receiverPtr);private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,long frameTimeNanos);public InputEventReceiver(InputChannel inputChannel, Looper looper) {......mInputChannel = inputChannel;mMessageQueue = looper.getQueue();mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);}public void onInputEvent(InputEvent event) {finishInputEvent(event, false);}public final void finishInputEvent(InputEvent event, boolean handled) {if (event == null) {throw new IllegalArgumentException("event must not be null");}if (mReceiverPtr == 0) {Log.w(TAG, "Attempted to finish an input event but the input event "+ "receiver has already been disposed.");} else {int index = mSeqMap.indexOfKey(event.getSequenceNumber());if (index < 0) {Log.w(TAG, "Attempted to finish an input event that is not in progress.");} else {int seq = mSeqMap.valueAt(index);mSeqMap.removeAt(index);nativeFinishInputEvent(mReceiverPtr, seq, handled);}}event.recycleIfNeededAfterDispatch();}// Called from native code.@SuppressWarnings("unused")private void dispatchInputEvent(int seq, InputEvent event) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event);}// Called from native code.@SuppressWarnings("unused")private void dispatchBatchedInputEventPending() {onBatchedInputEventPending();}public static interface Factory {public InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);}
}

InputEventReceiver的初始化

InputEventReceiver的構造方法實現,如下:

public InputEventReceiver(InputChannel inputChannel, Looper looper) {......mInputChannel = inputChannel;mMessageQueue = looper.getQueue();mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);}

該方法的初始化使用了App自己的inputChannel通道和App的Looper消息隊列,然后調用JNI方法完成最終的初始化,如下所示:

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject inputChannelObj, jobject messageQueueObj) {sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,inputChannelObj);if (inputChannel == NULL) {jniThrowRuntimeException(env, "InputChannel is not initialized.");return 0;}sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == NULL) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);status_t status = receiver->initialize();if (status) {String8 message;message.appendFormat("Failed to initialize input event receiver.  status=%d", status);jniThrowRuntimeException(env, message.string());return 0;}receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the objectreturn reinterpret_cast<jlong>(receiver.get());
}

nativeInit方法主要完成如下的邏輯:

1、找到java層的InputChannel對象對應的Native層的C++對象inputChannel;

2、找到java層的messageQueue對象對應的Native層的C++對象messageQueue;

3、調用NativeInputEventReceiver的initialize完成初始化;

我們繼續講解NativeInputEventReceiver的實現,如下所示:

class NativeInputEventReceiver : public LooperCallback {
public:NativeInputEventReceiver(JNIEnv* env,jobject receiverWeak, const sp<InputChannel>& inputChannel,const sp<MessageQueue>& messageQueue);status_t initialize();......virtual int handleEvent(int receiveFd, int events, void* data);
};

NativeInputEventReceiver繼承LooperCallback, 并重載實現了handleEvent。我們這里重點提及到LooperCallback,是因為App獲取Input輸入事件是通過監控inputChannel通道,并通過LooperCallback實現輸入事件的獲取,監控代碼如下所示:

void NativeInputEventReceiver::setFdEvents(int events) {if (mFdEvents != events) {mFdEvents = events;int fd = mInputConsumer.getChannel()->getFd();if (events) {mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);} else {mMessageQueue->getLooper()->removeFd(fd);}}
}

該接口的實現,我加入了對ALOOPER_EVENT_INPUT事件類型的監控,InputDispatcher上報的輸入事件會觸發ALOOPER_EVENT_INPUT事件,并最終調用到NativeInputEventReceiver中的handleEvent方法。關于Looper的實現,我們這里不詳細展開,感興趣的同學可以,自行去閱讀AOSP源碼。

App Input輸入事件的獲取

當InputDispatcher上報事件時,將輸入事件寫入InputChannel后,最終會觸發NativeInputEventReceiver中的handleEvent執行,如下所示:

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {......if (events & ALOOPER_EVENT_INPUT) {JNIEnv* env = AndroidRuntime::getJNIEnv();status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");return status == OK || status == NO_MEMORY ? 1 : 0;}......return 1;
}

我們只保留了InputDispatcher上報事件時的觸發回調代碼,然后繼續調用consumeEvents接口,如下所示:

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {......for (;;) {uint32_t seq;InputEvent* inputEvent;status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);......if (!skipCallbacks) {......switch (inputEvent->getType()) {case AINPUT_EVENT_TYPE_KEY:if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Received key event.", getInputChannelName());}inputEventObj = android_view_KeyEvent_fromNative(env,static_cast<KeyEvent*>(inputEvent));break;case AINPUT_EVENT_TYPE_MOTION: {if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());}MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {*outConsumedBatch = true;}inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);break;}default:assert(false); // InputConsumer should prevent this from ever happeninginputEventObj = NULL;}if (inputEventObj) {env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);} else {ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());skipCallbacks = true;}}......}
}

consumeEvents實現比較復雜,涉及到的邏輯比較多,為了簡化描述,我們只保留首次上報輸入事件的處理,主要實現邏輯如下所示:

1、mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent);獲取事件;

2、根據不同的事件類型,封裝為一個inputEventObj對象;

3、調用InputEventReceiver的java方法env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);正式開始向App上報輸入事件;

調用InputEventReceiver的dispatchInputEvent如下所示:

// Called from native code.@SuppressWarnings("unused")private void dispatchInputEvent(int seq, InputEvent event) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event);}

最終調用到了 WindowInputEventReceiver子類的onInputEvent方法,如下所示:

 final class WindowInputEventReceiver extends InputEventReceiver {public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}@Overridepublic void onInputEvent(InputEvent event) {enqueueInputEvent(event, this, 0, true);}......}

繼續調用enqueueInputEvent,如下所示:

void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {adjustInputEventForCompatibility(event);QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);......QueuedInputEvent last = mPendingInputEventTail;if (last == null) {mPendingInputEventHead = q;mPendingInputEventTail = q;} else {last.mNext = q;mPendingInputEventTail = q;}mPendingInputEventCount += 1;Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,mPendingInputEventCount);if (processImmediately) {doProcessInputEvents();} else {scheduleProcessInputEvents();}}

1、將上報的輸入事件加入到處理隊列中;

2、調用doProcessInputEvents立即開始App對輸入事件的處理,我們在后續文章進行講解。這里不再敘述。

總結

本文講述了App通過注冊輸入事件接收器的方式,當InputDispacther上報輸入事件時,通過Looper的回調機制App獲取到了事件,并通過doProcessInputEvents接口,正式開啟App對input事件的處理。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/83826.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83826.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/83826.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

VS2017編譯librdkafka 2.1.0

VS2017編譯librdkafka 2.1.0 本篇是 Windows系統編譯Qt使用的kafka(librdkafka)系列中的其中一篇,編譯librdkafka整體步驟大家可以參考: Windows系統編譯Qt使用的kafka(librdkafka) 由于項目需要,使用kafka,故自己編譯了一次,編譯的過程,踩了太多的坑了,特寫了本篇…

第 1 章:數字 I/O 與串口通信(GPIO UART)

本章目標: 掌握 GPIO 的硬件原理、寄存器配置與典型驅動框架 深入理解 UART/USART 的幀格式、波特率配置、中斷與 DMA 驅動 通過實戰案例,將 GPIO 與 UART 結合,實現 AT 命令式外設控制 章節結構 GPIO 概述與硬件原理 GPIO 驅動實現:寄存器、中斷與去抖 UART/USART 原理與幀…

通義千問-langchain使用構建(三)

目錄 序言docker 部署xinference1WSL環境docker安裝2拉取鏡像運行容器3使用的界面 本地跑chatchat1rag踩坑2使用的界面2.1配置個前置條件然后對話2.2rag對話 結論 序言 在前兩天的基礎上&#xff0c;將xinference調整為wsl環境&#xff0c;docker部署。 然后langchain chatcha…

winfrom中創建webapi

參照一下兩篇 Winform窗體利用WebApi接口實現ModbusTCP數據服務_winform webapi-CSDN博客 C#.NET WebApi返回各種類型(圖片/json數據/字符串)&#xff0c;.net圖片轉二進制流或byte - 冰封的心 - 博客園

文本分類任務Qwen3-0.6B與Bert:實驗見解

文本分類任務Qwen3-0.6B與Bert&#xff1a;實驗見解 前言 最近在知乎上刷到一個很有意思的提問Qwen3-0.6B這種小模型有什么實際意義和用途。查看了所有回答&#xff0c;有人提到小尺寸模型在邊緣設備場景中的優勢&#xff08;低延遲&#xff09;、也有人提出小模型只是為了開…

前端獲取用戶的公網 IP 地址

可以使用免費的免費的公共服務網站 一&#xff1a;https://www.ipify.org/ 獲取 JSON 格式的 IP 地址 // 舊地址不好使 // https://api.ipify.org/?formatjson // 新地址 https://api64.ipify.org/?formatjson 二&#xff1a;https://ipinfo.io/ https://ipinfo.io/ 三&a…

12.vue整合springboot首頁顯示數據庫表-實現按鈕:【添加修改刪除查詢】

vue整合springboot首頁顯示數據庫表&#xff1a;【添加修改刪除查詢】 提示&#xff1a;幫幫志會陸續更新非常多的IT技術知識&#xff0c;希望分享的內容對您有用。本章分享的是node.js和vue的使用。前后每一小節的內容是存在的有&#xff1a;學習and理解的關聯性。【幫幫志系…

LLM筆記(九)KV緩存(2)

文章目錄 1. 背景與動機2. 不使用 KV Cache 的情形2.1 矩陣形式展開2.2 計算復雜度 3. 使用 KV Cache 的優化3.1 核心思想3.2 矩陣形式展開3.3 計算復雜度對比 4. 總結5. GPT-2 中 KV 緩存的實現分析5.1 緩存的數據結構與類型5.2 在注意力機制 (GPT2Attention) 中使用緩存5.3 緩…

2025年滲透測試面試題總結-各廠商二面試題02(題目+回答)

網絡安全領域各種資源&#xff0c;學習文檔&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各種好玩的項目及好用的工具&#xff0c;歡迎關注。 目錄 各廠商二面試題02 模塊六&#xff1a;基礎技術擴展 1. HTTP請求方式 2. 域名解析工具與技術 3. Web十…

專業漏洞掃描機構如何助力企業保障安全并提升競爭力?

在這個信息化的當下&#xff0c;專業漏洞掃描機構扮演著至關重要的角色。他們運用專業的技術和手段&#xff0c;對各種軟件和系統進行細致的漏洞檢測&#xff0c;確保其安全可靠&#xff0c;同時幫助企業提高產品質量和市場競爭力。 服務項目 我們專注于向客戶供應周到詳盡的…

卷積神經網絡中的二維卷積與三維卷積詳解

【內容摘要】 本文聚焦卷積神經網絡中的二維卷積與三維卷積&#xff0c;詳細解析兩者的區別、操作原理及應用場景&#xff0c;涵蓋二維/三維卷積操作示意圖、多通道輸入處理方式&#xff0c;以及RGB圖像不采用三維卷積的原因&#xff0c;助力理解不同卷積類型的特性與適用場景。…

Oracle 的 ASSM 表空間

Oracle 的 ASSM&#xff08;Automatic Segment Space Management&#xff09;表空間 是一種自動管理段空間的技術&#xff0c;通過位圖&#xff08;Bitmap&#xff09;機制跟蹤數據塊的使用情況&#xff0c;替代傳統的手動管理&#xff08;MSSM&#xff0c;即 Freelist 管理&am…

螞蟻金服大數據面經及參考答案

Java 如何保證跨平臺性?請從 JVM 底層適配機制及向上提供的統一接口角度說明 Java 的跨平臺性是其核心優勢之一,依賴于 JVM(Java Virtual Machine)的底層適配機制和向上層提供的統一接口。從底層來看,JVM 針對不同操作系統和硬件平臺進行了定制化實現,負責解析和執行 Ja…

P1009 [NOIP 1998 普及組] 階乘之和

題目描述 用高精度計算出 S1!2!3!?n!&#xff08;n≤50&#xff09;。 其中 ! 表示階乘&#xff0c;定義為 n!n(n?1)(n?2)?1。例如&#xff0c;5!54321120。 輸入格式 一個正整數 n。 輸出格式 一個正整數 S&#xff0c;表示計算結果。 輸入輸出樣例 輸入 3 輸出…

Python 的 os 庫常見使用方法(操作目錄及文件)

前言&#xff1a; os 模塊是 Python 標準庫中用于與操作系統交互的核心模塊&#xff0c;提供了許多操作文件和目錄的功能。以下是常見的使用方法&#xff1a; 1. 目錄操作 方法功能說明示例os.getcwd()獲取當前工作目錄print(os.getcwd())os.chdir(path)切換當前工作目錄os.ch…

vue3 el-table實現字段可編輯

在Vue 3中&#xff0c;如果你想讓el-table&#xff08;Element Plus的表格組件&#xff09;的字段可編輯&#xff0c;你可以通過以下方式來實現&#xff1a; 使用cell-mouse-enter和cell-mouse-leave事件動態顯示編輯圖標或控件 你可以在鼠標進入單元格時顯示一個編輯圖標或輸…

基于shardingsphere的分庫分表方案

一、準備docker容器 啟動兩個mysql的docker容器 docker run -v /root/mysql_volume/data:/var/lib/mysql -v /root/mysql_volume/conf:/etc/mysql/conf.d -v /root/mysql_volume/my.cnf:/etc/my.cnf -p 3306:3306 --name mysql --restartalways --privilegedtrue -e MYSQL_RO…

SearxNG本地搜索引擎

SearxNG 是一個強大、開源的 元搜索引擎(meta search engine),它不會存儲用戶信息,注重隱私保護,并支持從多個搜索引擎聚合結果,用戶可以自建部署,打造一個無廣告、可定制的搜索平臺。 ?? 什么是 SearxNG? SearxNG 是 Searx 的一個積極維護的分支(fork),意在改進…

Vue3.5 企業級管理系統實戰(十九):菜單管理

篇幅原因&#xff0c;本節先探討菜單管理頁面增刪改查相關功能&#xff0c;角色菜單&#xff0c;菜單權限&#xff0c;動態菜單等內容放在后面。 1 菜單 api 在 src/api/menu.ts 中添加菜單 api&#xff0c;代碼如下&#xff1a; //src/api/menu.ts import service from &qu…

【android bluetooth 協議分析 01】【HCI 層介紹 8】【ReadLocalVersionInformation命令介紹】

1. HCI_Read_Local_Version_Information 命令介紹 1. 功能&#xff08;Description&#xff09; HCI_Read_Local_Version_Information 命令用于讀取本地 Bluetooth Controller 的版本信息&#xff0c;包括 HCI 和 LMP 層的版本&#xff0c;以及廠商 ID 和子版本號。 這類信息用…