一、Linux 触摸协议概述
Linux 触摸协议分为 A 协议 和 B 协议,两者的区别如下:
-
A 协议:
- 不支持多触点(Multi-Touch)的槽位(Slot)机制。
- 所有触点信息通过连续的事件上报,每次上报都需要包含所有触点的状态。
- 事件处理效率较低,尤其是在多触点场景下。
-
B 协议:
- 支持多触点的槽位(Slot)机制,每个槽位可以独立存储一个触点的信息。
- 通过
ABS_MT_SLOT切换槽位,只需更新当前槽位的触点信息,避免重复上报。 - 事件处理效率更高,适合多触点场景。
使用A协议还是B协议,是根据触摸屏硬件所决定的。现在的智能手机,一般都是采用B协议。
常用事件类型:
EV_SYN:同步事件,表示一组事件的结束。EV_REL:相对坐标事件(如鼠标移动)。EV_ABS:绝对坐标事件(如触摸屏)。EV_KEY:按键事件。SYN_REPORT:同步报告,标志一组事件的完成。ABS_MT_SLOT:多触点槽位切换。ABS_MT_TRACKING_ID:标识某个触点。BTN_TOUCH:触摸按键事件。
事件上报流程:
- 切换
ABS_MT_SLOT,选择当前操作的槽位。 - 设置
ABS_MT_TRACKING_ID,表示该槽位保存了某个手指的触摸信息。 - 如果不切换槽位,后续事件都会在当前槽位中处理。
- 如果需要处理其他触点,切换到新的槽位,避免重复上报。
- 最后通过
SYN_REPORT上报一组完整的事件。
注意:Android 默认最多支持 16 个槽位。
二、Android InputManager
InputManager是Android系统的输入管理系统,运行在SystemSyster进程中。
InputManager是SystemServer.startOtherService 中创建,并添加到 ServiceManager 中。
并且在InputManager创建时,会通过 JNI,同时创建InputManager(C++)对象,并使用 inputflinger 注册到 ServiceManager 中。
整个InputManager核心的组件包括:
InputDispatcher:负责事件分发。InputClassifier:负责事件分类。InputReader:负责读取输入事件。EventHub:负责监听输入设备的添加,和采集输入事件。
2. EventHub 的事件读取
EventHub::getEvents通过epoll_wait等待触摸事件的到来。scanDevicesLocked扫描/dev/input目录下的设备,并通过openDeviceLocked打开设备节点。- 获取设备属性后,创建一个
EventHub::Device对象。如果是键盘设备,会加载额外的配置文件。 - 设备节点会被注册到
epoll中。
3. InputReader 的工作机制
InputReader创建时,会初始化一个EventHub,内部使用inotify和epoll监听输入设备的设备节点。- 然后创建和启动2个线程:
InputReadThread和InputDispatchThread。
5. 设备事件处理
- 根据设备类型,为其添加相应的
Mapper,例如MultiTouchInputMapper,用于将原始触摸数据转换为input_event。 device->process方法处理原始事件 (RawEvent),内部调用mapper->process进行进一步处理。
6. MultiTouchInputMapper 的处理流程
- 在
sync阶段,MultiTouchInputMapper会将所有槽位的数据转换为RowPointerData。 - 通过
processRawTouch和cookPointerData对数据进行矫正。 - 最后调用
dispatchTouches分发触摸事件。
7. 事件分发
getListener()->notifyMotion将数据存储到mArgsQueue中,直到调用flush方法。- 最终,事件会传递到
InputDispatcher::notifyMotion进行进一步处理。
InputDispatcher 事件分发流程
1. InputDispatcher 的核心职责
- 接收来自
InputReader的输入事件。 - 根据事件类型和目标窗口,将事件分发给对应的
Activity或Window。
2. 事件分发的主要步骤
-
事件接收:
InputDispatcher通过notifyMotion方法接收来自InputReader的触摸事件。- 事件被封装为
MotionEntry,并放入待处理队列中。
-
事件分类:
- 根据事件的坐标和当前窗口的布局,确定事件的目标窗口。
- 通过
findTouchedWindow方法找到最上层的、可接收触摸事件的窗口。
-
事件分发:
- 将事件封装为
InputMessage,并通过InputChannel发送给目标窗口。 - 目标窗口的
ViewRootImpl通过InputEventReceiver接收事件。
- 将事件封装为
-
事件处理:
- 目标窗口的
ViewRootImpl将事件传递给DecorView,再由DecorView分发给具体的View。 View通过onTouchEvent方法处理触摸事件。
- 目标窗口的
-
事件反馈:
- 目标窗口处理完事件后,会通过
InputChannel向InputDispatcher发送反馈。 InputDispatcher根据反馈决定是否需要继续分发后续事件。
- 目标窗口处理完事件后,会通过
3. 事件分发的优化
- 批处理:多个连续的事件可能会被合并为一个批次,减少跨进程通信的开销。
- 焦点管理:
InputDispatcher会跟踪当前焦点窗口,确保按键事件分发给正确的窗口。 - 超时机制:如果目标窗口未及时处理事件,
InputDispatcher会触发超时机制,避免事件堆积。
private void startOtherServices() {
inputManager = new InputManagerService(context);
Slog.i(TAG, "Window Manager");
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
mActivityManagerService.setWindowManager(wm);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
}
public class InputManagerService extends IInputManager.Stub
implements Watchdog.Monitor {
public InputManagerService(Context context) {
this.mContext = context;
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
LocalServices.addService(InputManagerInternal.class, new LocalService())
}
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart(mPtr);
......
}
static jlong nativeInit(JNIEnv* env, jclass clazz,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
......
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
Vector<InputDeviceInfo> inputDevices;
......
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex _l(mLock);
mReaderIsAliveCondition.broadcast();
if (count) {
processEventsLocked(mEventBuffer, count);
}
if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
#endif
mNextTimeout = LLONG_MAX;
timeoutExpiredLocked(now);
}
}
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);
}
} // release lock
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
// on another thread similarly waiting to acquire the InputReader lock thereby
// resulting in a deadlock. This situation is actually quite plausible because the
// listener is actually the input dispatcher, which calls into the window manager,
// which occasionally calls into the input reader.
mQueuedListener->flush();
}