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版…