Android Input 系列專題【inputflinger事件的讀取與分發】

Android輸入系統在native中的核心工作就是,從Linux驅動設備節點中讀取事件,然后將這個事件進行分發,這兩項工作分別交給了InputReader和InputDispatcher來做。

他們的源碼都屬于native層inputflinger里面的一部分,如下架構:

根據Android.bp和目錄結構來看,可以進行如下總結:

  • inputflinger并不是一個獨立的native進程,它以庫的形式存在,即被FW的IMS進行調用
  • inputflinger的核心代碼為InputManager.cpp,因此InputManager充當了JNI的角色與FW的IMS進行交互
  • libinputflinger.so依賴于InputReader.cpp和InputDispatcher.cpp,但進行了分離把他們分別封裝成為libinputreader.so和libinputdispatcher.so來進行引用,因此后續調試可以專門針對這三個庫進行推送調試

一、InputManager與IMS的聯系

前文已經提到了InputManager.cpp主要用來和fw層的InputManagerService來進行聯系,這里從源碼的角度來解析一下他們之間到底是如何聯系起來的。其實還是使用了傳統的方式,讓InputMnagerService通過JNI的方式來調用InputManager.cpp,因此inputflinger的代碼是運行在system_server進程里面的

1、system_server進程如何引用libinputflinger.so?

FrameWork層最重要的兩個framework.jar和services.jar,已經SystemServer等一系列系統服務都被定義在aosp/framework/base/目錄中,有如下信息:

?frameworks/base/services/core/jni/Android.bp  --->集成libinputflinger
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java  --->Android輸入系統FW層IMS服務
frameworks/native/services/inputflinger/Android.bp  --->定義libinputflinger
frameworks/native/services/inputflinger/InputManager.cpp  --->Android輸入系統Native層管理類
?

因此他們同屬一個模塊和進程,libinputflinger以庫的方式被引用進去。

2、InputManagerService

//frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public class InputManagerService extends IInputManager.Stub implements Watchdog.Monitor {static final String TAG = "InputManager";// To enable these logs, run: 'adb shell setprop log.tag.InputManager DEBUG' (requires restart)private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);//定義mNative是一個java類,其內部定義了所有的native方法private final NativeInputManagerService mNative;private final Context mContext;private final InputManagerHandler mHandler;private DisplayManagerInternal mDisplayManagerInternal;//...省略...static class Injector {private final Context mContext;private final Looper mLooper;Injector(Context context, Looper looper) {mContext = context;mLooper = looper;}Context getContext() {return mContext;}Looper getLooper() {return mLooper;}//獲取NativeInputManagerService實例NativeInputManagerService getNativeService(InputManagerService service) {return new NativeInputManagerService.NativeImpl(service, mLooper.getQueue());}void registerLocalService(InputManagerInternal localService) {LocalServices.addService(InputManagerInternal.class, localService);}}public InputManagerService(Context context) {this(new Injector(context, DisplayThread.get().getLooper()));}InputManagerService(Injector injector) {mContext = injector.getContext();mHandler = new InputManagerHandler(injector.getLooper());//獲取NativeInputManagerService實例mNative = injector.getNativeService(this);//后續把mNative實例對象添加到各個模塊,為了讓IMS系統的native層更加方便的與fw各個模塊進行交互mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative);mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore, injector.getLooper());mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());mKeyboardBacklightController =  KEYBOARD_BACKLIGHT_CONTROL_ENABLED ? new KeyboardBacklightController(mContext,  mNative, mDataStore, injector.getLooper()) : new KeyboardBacklightControllerInterface() {};mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());mUseDevInputEventForAudioJack = mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="   + mUseDevInputEventForAudioJack);injector.registerLocalService(new LocalService());}public void start() {Slog.i(TAG, "Starting input manager");//非常重要,初始化native層世界所有的C++類mNative.start(); Watchdog.getInstance().addMonitor(this);// Add ourselves to the Watchdog monitors.}//......省略...
}

根據如上代碼可以進行如下總結:

  • IMS通過持有NativeInputManagerService對象來控制native世界
  • IMS在服務啟動之后通過mNative.start方法來調用native世界對象的start方法進行啟動

3、NativeInputManagerService

NativeInputManagerService就是一個單純的接口,定義了一堆需要和native世界交互的方法。

最終把這些接口方法轉換為native層方法,即這些方法的實現全部都在native層里面的那些C++庫里面。

這些native方法的根據包名轉換到:com_android_server_input_InputManagerService.cpp

?4、JNI如何引用到InputManager.cpp?

  • NativeInputManager對象的構造:改對象直接實例化了InputManager.cpp
//frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
//NativeInputManager構造函數
NativeInputManager::NativeInputManager(jobject serviceObj, const sp<Looper>& looper): mLooper(looper), mInteractive(true) {JNIEnv* env = jniEnv();//拿到FW的IMS對象實例mServiceObj = env->NewGlobalRef(serviceObj);//實例化C++世界的核心類InputManager.cppInputManager* im = new InputManager(this, *this);mInputManager = im;//直接像c++世界的servicemanager注冊服務inputflinger,注意該方法只是注冊服務實例對象,并不是注冊進程,這里的進程還是system_server進程defaultServiceManager()->addService(String16("inputflinger"), im);
}
//NativeInputManager析構函數
NativeInputManager::~NativeInputManager() {JNIEnv* env = jniEnv();env->DeleteGlobalRef(mServiceObj);
}
  • nativeInit和nativeStart的實現
//frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {return reinterpret_cast<NativeInputManager*>(env->GetLongField(clazz, gNativeInputManagerServiceImpl.mPtr));
}
static jlong nativeInit(JNIEnv* env, jclass /* clazz */, jobject serviceObj, jobject messageQueueObj) {sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == nullptr) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}static std::once_flag nativeInitialize;NativeInputManager* im = nullptr;std::call_once(nativeInitialize, [&]() {// Create the NativeInputManager, which should not be destroyed or deallocated for the lifetime of the process.//核心代碼:創建NativeInputManager實例化對象,其實就是封裝了InputManager.cppim = new NativeInputManager(serviceObj, messageQueue->getLooper());});LOG_ALWAYS_FATAL_IF(im == nullptr, "NativeInputManager was already initialized.");return reinterpret_cast<jlong>(im);
}static void nativeStart(JNIEnv* env, jobject nativeImplObj) {NativeInputManager* im = getNativeInputManager(env, nativeImplObj);//核心代碼:拿到NativeInputManager實例化對象,并調用start,就是調用了InputManager.start方法status_t result = im->getInputManager()->start();if (result) {jniThrowRuntimeException(env, "Input manager could not be started.");}
}

    綜上,IMS與InputManager的關系如下,system_server進程的IMS通過JNI的方式啟動了native世界的InputManager.cpp的start方法。

    5、InputManager的封裝

    綜上所述,從IMS到InputManager之間的關系如下圖:

    二、InputReader事件讀取

    InputReader事件讀取的原理就是直接從驅動設備節點/dev/input/里面讀取事件,通常有多個輸入設備,在我手上的這臺Android平板的該目錄如下:

    • 在我觸摸屏幕的時候event3節點文件中會打印數據:

    • 在我按音量-的時候event0節點文件中會打印數據
    • 在我按音量+的時候event2節點文件中會打印數據
    • 輸入getevent命令可以直接獲取/dev/input/里面的事件:

    1、EventHub

    InputReader事件讀取的原理就是直接從驅動設備節點/dev/input/里面讀取事件。如果對應設備有事件,例如鼠標事件,屏幕觸摸事件,驅動會直接向這些節點寫入數據。

    • 設備節點的路徑:他的日志可以過濾EventHub

    • 設備節點的讀取:EventHub.cpp做的事情就是從這個節點里面讀取數據,并進行封裝整理成為events向量:如下代碼,這里我直接引用https://xiaoxu.blog.csdn.net/article/details/146344278
    size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {……RawEvent* event = buffer;size_t capacity = bufferSize;bool awoken = false;for (;;) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);// 如果需要,重新打開輸入設備if (mNeedToReopenDevices) {mNeedToReopenDevices = false;closeAllDevicesLocked();mNeedToScanDevices = true;break; // return to the caller before we actually rescan}// 報告最近添加/刪除的任何設備for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {std::unique_ptr<Device> device = std::move(*it);ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str());event->when = now;event->deviceId = (device->id == mBuiltInKeyboardId)? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID : device->id;event->type = DEVICE_REMOVED;event += 1;it = mClosingDevices.erase(it);mNeedToSendFinishedDeviceScan = true;if (--capacity == 0) {break;}}// 掃描新的輸入設備,第一次為trueif (mNeedToScanDevices) {mNeedToScanDevices = false;// 打開 /dev/input/ 目錄下的input設備后,將其注冊到epoll的監控隊列中。scanDevicesLocked();mNeedToSendFinishedDeviceScan = true;}// 報告設備添加事件while (!mOpeningDevices.empty()) {std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());mOpeningDevices.pop_back();ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());event->when = now;event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;// 對于新開的設備,生成一個 DEVICE_ADDED 類型的 RawEvent 并添加到輸出緩沖區。event->type = DEVICE_ADDED;event += 1;// 嘗試為設備匹配相應的視頻設備(如觸摸屏),并將設備信息插入到 mDevices 映射中for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end(); it++) {std::unique_ptr<TouchVideoDevice>& videoDevice = *it;if (tryAddVideoDeviceLocked(*device, videoDevice)) {// videoDevice was transferred to 'device'it = mUnattachedVideoDevices.erase(it);break;}}auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));if (!inserted) {ALOGW("Device id %d exists, replaced.", device->id);}mNeedToSendFinishedDeviceScan = true;if (--capacity == 0) {break;}}// 發送設備掃描完成通知if (mNeedToSendFinishedDeviceScan) {mNeedToSendFinishedDeviceScan = false;event->when = now;event->type = FINISHED_DEVICE_SCAN;event += 1;if (--capacity == 0) {break;}}// 處理待處理事件隊列中的下一個輸入事件bool deviceChanged = false;while (mPendingEventIndex < mPendingEventCount) {const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];// 處理INotify事件if (eventItem.data.fd == mINotifyFd) {if (eventItem.events & EPOLLIN) {mPendingINotify = true;}//...省略....continue;}// 處理喚醒管道事件if (eventItem.data.fd == mWakeReadPipeFd) {if (eventItem.events & EPOLLIN) {ALOGV("awoken after wake()");awoken = true;char wakeReadBuffer[16];ssize_t nRead;do {nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer));} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer));}//...省略....continue;}// 處理輸入設備事件Device* device = getDeviceByFdLocked(eventItem.data.fd);//...省略....// 處理觸摸屏輸入事件if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) {//...省略....continue;}// 處理標準輸入設備事件// 對于標準輸入設備(如鍵盤、鼠標等),檢查是否有可讀事件 (EPOLLIN)if (eventItem.events & EPOLLIN) {// 從device中得到fd后再去讀取設備,獲取input事件int32_t readSize = read(device->fd, readBuffer, sizeof(struct input_event) * capacity);if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {// 讀取失敗或設備已被移除,則關閉該設備deviceChanged = true;closeDeviceLocked(*device);} //...省略....else {// 讀取成功,將每個input_event轉換為RawEvent并添加到輸出緩沖區中int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;size_t count = size_t(readSize) / sizeof(struct input_event);for (size_t i = 0; i < count; i++) {struct input_event& iev = readBuffer[i];event->when = processEventTimestamp(iev);event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);event->deviceId = deviceId;event->type = iev.type;event->code = iev.code;event->value = iev.value;event += 1;capacity -= 1;}if (capacity == 0) {// 緩沖區已滿。重置掛起的事件索引,等待下一次再次嘗試讀取設備。mPendingEventIndex -= 1;break;}}} else if (eventItem.events & EPOLLHUP) {// 處理掛起事件 (EPOLLHUP),則關閉該設備deviceChanged = true;closeDeviceLocked(*device);}//...省略....}// 如果存在未處理的 INotify 事件并且所有待處理事件都已處理完畢if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {mPendingINotify = false;// 處理設備節點的變化readNotifyLocked();deviceChanged = true;}// 報告添加或移除的設備if (deviceChanged) {continue;}//...省略....// 等待更多事件的到來int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);//...省略....// 所有操作完成后,返回我們讀取的事件數return event - buffer;
    }
    • events3節點:從下面的代碼來看有些懷疑專門針對屏幕的定制,但具體代碼有帶研究

    2、InputReader的輪詢

    接下來就是InputReader的主要邏輯,她封裝了事件的讀取和事件的分發前階段。

    1)InputReader開啟線程輪詢

    InputReader的構造函數和start與stop方法如下,其核心就是通過封裝的InputThread類來創建線程,線程名為InputReader,并在EventHub收到事件的時候喚起,調用loopOnce函數。

    2)loopOnce

    //frameworks/native/services/inputflinger/reader/InputReader.cpp
    void InputReader::loopOnce() {int32_t oldGeneration;int32_t timeoutMillis;bool inputDevicesChanged = false;std::vector<InputDeviceInfo> inputDevices;std::list<NotifyArgs> notifyArgs;//流程1:讀取驅動設備節點的事件數據,并以RawEvent數組的方式進行封裝std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);{ // acquire lockstd::scoped_lock _l(mLock);mReaderIsAliveCondition.notify_all();if (!events.empty()) {//流程2:成功讀取到事件之后,通過processEventsLocked進行解析,并以NotifyArgs數組的方式進行封裝notifyArgs += processEventsLocked(events.data(), events.size());}//....省略....} // release lock//...省略...//流程3:遍歷mQueuedListener隊列中所有的監聽器,并發布事件,其實就是進行所有設備的事件分發notifyAll(std::move(notifyArgs));//流程4:更新隊列mQueuedListener.flush();
    }
    void InputReader::notifyAll(std::list<NotifyArgs>&& argsList) {for (const NotifyArgs& args : argsList) {mQueuedListener.notify(args);}
    }

    在一次輪詢中,核心的任務就兩個:

    • 讀取原始數據格式的RawEvent事件,然后并封裝成NotifyArgs事件
    • 然后遍歷所有監聽器,進行事件分發,實際上搞了一個裝飾者模式,將不同類型的事件分發出去,這塊邏輯我們在事件分發的時候詳細介紹

    3、InputReader原始事件數據解析

    InputReader通過processEventsLocked函數對從驅動設備節點讀取出來的原始數據事件的處理,但是原始數據事件里面不一定全是由物理設備觸發上來的數據,還有一些事件是系統發送出來的,所有首先需要對真實物理事件和合成事件的區分

    1)合成事件與物理事件的隔離

    接著上文這里對真實物理事件和系統發出的合成事件進行了區分:?

    • 如果是真實物理事件,查找原始事件里面的設備ID,通過processEventsForDeviceLocked去找到輸入子設備InputDevice,然后通過裝飾者的方式在各個子設備中處理原始數據并進行封裝。
    • 什么是物理事件呢?由硬件物理設備觸發的真實事件,例如手指接觸屏幕,屏幕設備觸發的觸摸事件,GPIO電源鍵被按下觸發的真實事件。
    • 如果是合成事件,判斷具體類型,進行設備注冊和注銷,和設備的掃描,這些事件都不是由物理設備觸發的,而是由系統觸發的,所以把她們叫做合成事件。
    //frameworks/native/services/inputflinger/reader/include/EventHub.h
    class EventHubInterface {enum {//定義合成事件:當檢測到新輸入設備連接時觸發(如插入USB鼠標或藍牙鍵盤),系統會通過該事件通知InputReader加載設備驅動并初始化配置。事件攜帶設備ID和時間戳,觸發addDeviceLocked()調用完成設備注冊?DEVICE_ADDED = 0x10000000,//定義合成事件:輸入設備斷開連接時生成(如拔出觸摸屏或手柄),觸發removeDeviceLocked()清理設備資源。該事件會確保后續輸入事件不會分發給已移除設備?DEVICE_REMOVED = 0x20000000,//設備掃描周期完成標志事件,每次掃描(包括冷啟動時的初始掃描)至少發送一次。用于同步設備狀態變更,觸發handleConfigurationChangedLocked()更新全局輸入配置(如鍵盤布局切換FINISHED_DEVICE_SCAN = 0x30000000,//區分合成事件:通常認為小于FIRST_SYNTHETIC_EVENT的事件為真實的物理事件//真實物理事件:由硬件物理設備觸發的真實事件,例如手指接觸屏幕,屏幕設備觸發的觸摸事件,GPIO電源鍵被按下觸發的真實事件FIRST_SYNTHETIC_EVENT = DEVICE_ADDED,};
    }
    //frameworks/native/services/inputflinger/reader/InputReader.cpp
    std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {std::list<NotifyArgs> out;//遍歷原始事件的數組for (const RawEvent* rawEvent = rawEvents; count;) {int32_t type = rawEvent->type;size_t batchSize = 1;//如果是真實物理事件:只要小于FIRST_SYNTHETIC_EVENT都被定義為真實事件if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {//獲取設備ID:原始事件觸發的時候會把物理設備ID帶進去,這里直接獲取int32_t deviceId = rawEvent->deviceId;while (batchSize < count) {if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||  rawEvent[batchSize].deviceId != deviceId) {break;}batchSize += 1;}if (debugRawEvents()) {ALOGD("BatchSize: %zu Count: %zu", batchSize, count);}//通過設備ID找到輸入設備,去進行事件處理out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize);} else {//如果是合成事件:因為不是真實物理設備事件,因此不需要進行設備事件處理switch (rawEvent->type) {case EventHubInterface::DEVICE_ADDED://添加輸入設備并進行注冊addDeviceLocked(rawEvent->when, rawEvent->deviceId);break;case EventHubInterface::DEVICE_REMOVED://移除輸入設備并進行注銷removeDeviceLocked(rawEvent->when, rawEvent->deviceId);break;case EventHubInterface::FINISHED_DEVICE_SCAN://掃描事件,由系統發出handleConfigurationChangedLocked(rawEvent->when);break;default:ALOG_ASSERT(false); // can't happenbreak;}}count -= batchSize;rawEvent += batchSize;}return out;
    }

    2)processEventsForDeviceLocked

    前文已經提到了當有物理設備注冊或者注銷的時候,系統觸發幾個合成事件:DEVICE_ADDED和DEVICE_REMOVED?進行設備的添加和移除,其實這里的設備管理也使用了一個觀察者德模式。如下邏輯

    那么設processEventsForDeviceLocked是如何去找尋原始events數據里面對于的物理設備呢?如/dev/input/events3節點觸發的事件就應該找到觸摸屏設備,如果是/dev/input/events0節點觸發的事件就應該去找到物理按鍵設備。

    //frameworks/native/services/inputflinger/reader/InputReader.cpp
    std::list<NotifyArgs> InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,  size_t count) {//通過設備ID找到對應的設備,這里其實就是從原始的events數據里面找到對應的設備auto deviceIt = mDevices.find(eventHubId);if (deviceIt == mDevices.end()) {ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);return {};}std::shared_ptr<InputDevice>& device = deviceIt->second;if (device->isIgnored()) {// ALOGD("Discarding event for ignored deviceId %d.", deviceId);return {};}//調用設備抽象類的process方法,這個抽象父類其實就是InputDevicereturn device->process(rawEvents, count);
    }

    最后調用inputdevice的process函數來進行數據處理。關鍵其實就是在這個函數里面,是一種典型的裝飾者設計模式。

    4、InputDevice如何裝飾所有子設備?

    那么這些輸入設備是如何管理的呢?那么我就需要研究一下InputDevice了。

    1)InputMapper裝飾者

    她是如何通過InputMapper來進行裝飾。分為如下四個流程:

    //frameworks/native/services/inputflinger/reader/InputReader.cpp
    std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {//....省略....switch (rawEvent->type) {case EventHubInterface::DEVICE_ADDED://流程1:當收到輸入設備注冊德時候,調用此方法創建添加關聯設備ID的InputDeviceaddDeviceLocked(rawEvent->when, rawEvent->deviceId);break;//....省略....
    }
    void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {//如果mDevices列表存在關聯設備,直接返回if (mDevices.find(eventHubId) != mDevices.end()) {ALOGW("Ignoring spurious device added event for eventHubId %d.", eventHubId);return;}//流程2:通過createDeviceLocked創建InputDevice,并關聯設備IDInputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);//...省略...
    }
    std::shared_ptr<InputDevice> InputReader::createDeviceLocked( int32_t eventHubId, const InputDeviceIdentifier& identifier) {//創建InputDevicestd::shared_ptr<InputDevice> device;if (deviceIt != mDevices.end()) {device = deviceIt->second;} else {int32_t deviceId = (eventHubId < END_RESERVED_ID) ? eventHubId :  nextInputDeviceIdLocked();device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(), identifier);}//流程3:創建InputDevice,并調用addEventHubDevice來進行關聯設備ID,設備ID作為參數傳遞了進去device->addEventHubDevice(eventHubId, mConfig);return device;
    }
    //frameworks/native/services/inputflinger/reader/InputDevice.cpp
    void InputDevice::addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig) {if (mDevices.find(eventHubId) != mDevices.end()) {return;}//封裝設備ID為InputDeviceContext類型std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));//流程4:通過封裝設備ID的contextPtr來創建mappers,這個mapper就是一個裝飾者std::vector<std::unique_ptr<InputMapper>> mappers = createMappers(*contextPtr, readerConfig);//InputDevice的主要工作其實就是交給mappermDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});bumpGeneration();
    }
    

    最后在createMappers函數中根據不同的設備ID創建不同德inputMapper,如下代碼邏輯:

    2)InputMapper所有派生物理設備

    如上所有的XXXInputMapper,這里分別介紹一下這些Mapper對應什么物理設備:

    • CursorInputMapper:處理鼠標設備輸入,包括移動、點擊和滾輪事件,通過PointerController控制光標位置
    • KeyboardInputMapper:映射物理鍵盤輸入,處理按鍵掃描碼到Android鍵值的轉換,支持組合鍵檢測
    • TouchInputMapper:觸摸屏幕的基類,派生出如下三個觸摸屏設備。2025年左右使用的屏幕基本上是MultiTouchInputMapper
    • SingleTouchInputMapper:單點觸控設備(早期電阻屏)
    • MultiTouchInputMapper:多點觸控設備(現代電容屏)
    • TouchpadInputMapper:筆記本觸控板設備
    • JoystickInputMapper:處理游戲手柄/操縱桿的軸運動和按鈕事件,支持力反饋特性
    • ExternalStylusInputMapper:管理外接手寫筆輸入,支持壓感、傾斜等高級特性(如Wacom數位板)
    • RotaryEncoderInputMapper:對應旋轉編碼器設備(如智能手表表冠、旋鈕控制器)
    • SensorInputMapper:處理加速度計、陀螺儀等傳感器數據,但實際傳感器事件通常通過獨立子系統傳遞
    • VibratorInputMapper:控制設備的觸覺反饋(震動馬達),嚴格來說屬于輸出設備
    • SwitchInputMapper:映射物理開關事件(如蓋子開關、滑動開關等)

    最后回到processEventsForDeviceLocked函數中的device->process(rawEvents, count)的邏輯中,最終其實就是調用了對應XXXInputMapper的process函數:

    //frameworks/native/services/inputflinger/reader/InputReader.cpp
    std::list<NotifyArgs> InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,  size_t count) {//通過設備ID找到對應的設備,這里其實就是從原始的events數據里面找到對應的設備auto deviceIt = mDevices.find(eventHubId);if (deviceIt == mDevices.end()) {ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);return {};}std::shared_ptr<InputDevice>& device = deviceIt->second;if (device->isIgnored()) {// ALOGD("Discarding event for ignored deviceId %d.", deviceId);return {};}//流程1:調用設備抽象類的process方法,這個抽象父類其實就是InputDevicereturn device->process(rawEvents, count);
    }
    ///frameworks/native/services/inputflinger/reader/InputDevice.cpp
    std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t count) {std::list<NotifyArgs> out;for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {if (mDropUntilNextSync) {if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {mDropUntilNextSync = false;ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun.");} else {ALOGD_IF(debugRawEvents(), "Dropped input event while waiting for next input sync.");}} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());mDropUntilNextSync = true;out += reset(rawEvent->when);} else {//流程2:根據設備ID遍歷mapper,調用mapper的process函數for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {out += mapper.process(rawEvent);});}--count;}return out;
    }

    三、InputDispatcher事件分發

    InputDispatcher作為native層的事件分發,他起到的作用,就是把InputReader封裝好的事件分發到FW層的窗口或者控件之中。這個過程中主要涉及到

    • 如何接收來自InputReader過來的事件
    • 查詢此事件關聯的窗口或者控件
    • 最后把事件傳遞到ViewRootImpl中

    2、InputDispatcher的輪詢

    InputDispatcher的設計與InputReader基本一致,其代碼邏輯也基本相同:

    1)InputDispatcher開啟線程輪詢

    InputDispatcher的構造函數和start與stop方法如下,其核心就是通過封裝的InputThread類來創建線程,線程名為InputDispatcher,這里是通過mLooper的喚起來調用dispatchOnce函數

    2)dispatchOnce

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

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

    相關文章

    【大模型LLM】GPU計算效率評估指標與優化方法:吞吐率

    GPU計算效率評估指標與優化方法&#xff1a;吞吐率 一、核心效率指標二、大模型吞吐率&#xff08;Large Model Throughput&#xff09;三、關鍵性能瓶頸分析四、實際測量工具五、優化策略總結 一、核心效率指標 吞吐率&#xff08;Throughput&#xff09; 定義&#xff1a;單位…

    Nestjs框架: 集成 Prisma

    概述 在 NestJS 的官方文檔中&#xff0c;有兩處對數據庫進行了介紹 第一處位于左側“Techniques&#xff08;技術&#xff09;”部分下的“數據庫”板塊&#xff0c;中文文檔里同樣有這個位置。 Database 第二處是下面的“Recipes (秘籍)”板塊&#xff0c;這里有多個部分都與…

    CppCon 2018 學習:What Do We Mean When We Say Nothing At All?

    提供的內容深入探討了C編程中的一些關鍵概念&#xff0c;特別是如何編寫清晰、易維護的代碼&#xff0c;并展示了一些C17的新特性。我將對這些內容做中文的解釋和總結。 1. 良好的代碼設計原則 什么是“良好的代碼”&#xff1f; 能工作&#xff1a;代碼實現了預期功能。能在…

    C語言中的輸入輸出函數:構建程序交互的基石

    在C語言的世界里&#xff0c;輸入輸出&#xff08;I/O&#xff09;操作是程序與用戶或外部數據源進行交互的基本方式。無論是從鍵盤接收用戶輸入&#xff0c;還是將處理結果顯示到屏幕上&#xff0c;亦或是讀寫文件&#xff0c;都離不開C語言提供的輸入輸出函數。本文將深入探討…

    高速信號眼圖

    橫軸體系時域的抖動大小&#xff1b;縱軸體現電壓的噪聲。 噪聲越大&#xff0c;眼高越小。 抖動越大&#xff0c;眼寬越窄。 眼圖的模板是定義好的最大jitter和噪聲的模板范圍。就是信號的不可觸碰區域。信號波形不能夠觸碰到模板或者進行模板中。也就是眼圖中的線軌跡要在眼…

    VisualSVN Server 禁止的特殊符號 導致的。具體分析如下:錯誤提示解讀

    是由于 文件夾名稱中包含了 VisualSVN Server 禁止的特殊符號 導致的。具體分析如下&#xff1a; 錯誤提示解讀 錯誤信息明確說明&#xff1a; Folder name cannot contain following symbols < > : " / | and start or end by period. 即 文件夾名稱不能包含以下…

    再見,WebSecurityConfigurerAdapter!你好,SecurityFilterChain

    對于許多經驗豐富的 Spring開發者來說&#xff0c;WebSecurityConfigurerAdapter 是一個再熟悉不過的名字。在很長一段時間里&#xff0c;它幾乎是所有 Spring Security 配置的起點和核心。然而&#xff0c;隨著 Spring Boot 3.x 和 Spring Security 6.x 的普及&#xff0c;這個…

    web前端面試-- MVC、MVP、MVVM 架構模式對比

    MVC、MVP、MVVM 架構模式對比 基本概念 這三種都是用于分離用戶界面(UI)與業務邏輯的架構模式&#xff0c;旨在提高代碼的可維護性、可測試性和可擴展性。 1. MVC (Model-View-Controller) 核心結構&#xff1a; Model&#xff1a;數據模型和業務邏輯View&#xff1a;用戶界面展…

    【C#】MVVM知識點匯總-2

    在C#中實現MVVM&#xff08;Model-View-ViewModel&#xff09;架構時&#xff0c;可以總結以下幾個關鍵知識點&#xff0c;并通過具體的代碼示例來進行說明。 1. 模型 (Model) 模型包含應用程序中的數據和業務邏輯。通常與數據庫交互。 public class User { public int Id {…

    一文了解PMI、CSPM、軟考、、IPMA、PeopleCert和華為項目管理認證

    1 引言 常見的項目管理方面的認證有PMI、IPMA、PeopleCert、CSPM、軟考和華為項目管理認證6個認證。本篇文章讓你一文了解各認證的基本主要內容。 2 核心定位 目前全球范圍內最具影響力的六大認證體系各有特色&#xff0c;源于不同的管理哲學和實踐背景。六大認證體系的核心…

    bean注入的過程中,Property of ‘java.util.ArrayList‘ type cannot be injected by ‘List‘

    一、問題 在spring實踐bean注入ArrayList屬性的時候報錯&#xff1a;Property of ‘java.util.ArrayList’ type cannot be injected by ‘List’二、原因分析 在嘗試將 Spring 配置中的 注入到一個 ArrayList 類型的屬性時出現了類型不匹配問題。核心問題在于&#xff1a;Spr…

    自注意力機制原理: 向量矩陣案例進行說明

    自注意力機制原理: 向量矩陣案例進行說明 目錄 自注意力機制原理: 向量矩陣案例進行說明一個單詞和所有單詞進行乘法運算,提取特征一、場景設定:翻譯句子“我喜歡深度學習”二、向量矩陣構建:以“我”為例計算自注意力三、矩陣視角:批量計算整個序列的自注意力四、向量矩…

    D3 面試題100道之(61-80)

    這里是D3的面試題,我們從第 61~80題 開始逐條解答。一共100道,陸續發布中。 ?? 面試題(第 61~80 題) 61. D3 中如何繪制餅圖? 使用 d3.pie() 生成角度數據,再結合 d3.arc() 創建路徑。 示例: const data = [10, 20, 30

    flutter更改第三方庫pub get的緩存目錄;更改.gradle文件夾存放目錄

    1.在目標目錄中新建文件夾flutter_pub_cache 2.在“用戶變量“或“系統變量”中點擊“新建” 變量名: PUB_CACHE 變量值: D:\flutter_pub_cache 3.打開新的終端運行或者從Android studio 控制臺運行&#xff1a;flutter pub cache repair或者flutter pub clean pub讀取新的變…

    《Redis》哨兵模式

    文章目錄 為什么要有哨兵模式呢&#xff1f;哨兵自動恢復故障主節點使用docker搭建分布式系統查看哨兵節點工作哨兵選舉新的主節點的流程 總結 為什么要有哨兵模式呢&#xff1f; 主從復制的問題 Redis 的主從復制模式可以將主節點的數據改變同步給從節點&#xff0c;這樣從節…

    零基礎保姆級本地化部署文心大模型4.5開源系列

    近兩年隨著大模型的迅猛崛起&#xff0c;吸引了各行各業的廣泛關注&#xff0c;更對我們的工作方式與生活產生著顯著積極影響。在這樣一個技術范式轉換的關鍵節點&#xff0c;百度文心大模型開源事件無疑具有里程碑意義——它不僅為中國自主研發的AI技術底座打開了通向世界的大…

    【筆記】PyCharm 2025.2 EAP 創建 Poetry 和 Hatch 環境的踩坑實錄與反饋

    https://youtrack.jetbrains.com/issue/PY-82407/Incorrect-Python-Version-and-Virtual-Environment-Path-When-Creating-Poetry-and-Hatch-Environments-via-GUI-in-PyCharm-2025.2-EAP 在 Python 開發的道路上&#xff0c;PyCharm 一直是我們信賴的開發利器。然而&#xff0…

    ASP.NET Web Pages 安裝使用教程

    一、ASP.NET Web Pages 簡介 ASP.NET Web Pages 是微軟推出的一種輕量級 Web 開發框架&#xff0c;適合快速開發動態網站。它使用 Razor 語法&#xff0c;可以將 HTML 與 C# 或 VB.NET 無縫融合&#xff0c;特別適合初學者和小型項目。 二、Web Pages 與 MVC 的區別 特性Web …

    基于 ethers.js 的區塊鏈事件處理與錢包管理

    幣圈工具箱 bqbot.cn 月訪問量達90whttps://bqbot.cn/jms.html &#xff08;在線版地址&#xff09; Event事件 檢索事件 const { ethers } require("hardhat"); async function SearchEvent() {try {const provider new ethers.JsonRpcProvider("http://1…

    SpringBoot系列—入門

    目錄 1 第一個SpringBoot程序 1.1 創建SpringBoot項目 1.2 選擇SpringBoot版本和必要依賴 1.3 項目目錄結構 1.4 編寫Hello World代碼 1.5 運行程序 1.6 不需要IDEA也能創建SpringBoot程序 1.7 部署程序 1.8 pom.xml依賴問題 1.9 無Maven選項問題 1.10 SpringBoot版…