前序
一般情况下很多同学对于点击事件的认识都只存在于从 Activity 开始的,然后从 Window 中进行分发,或者有些人有过自定义View的经验可能会涉及事件拦截处理等,会用到 onTouchEvent
和 dispatchTouchEvetn
这几个方法,会处理屏幕滑动,Down事件,Up事件等,但仅停留于对于 View 层的了解。
自定义View的事件处理其实在整个Android输入系统中只能算是最上层的,但这些事件是怎么产生的,上层View是怎么获得到这些事件的呢?喜欢研究底层实现原理的我,今天就给大家讲讲。
输入系统
事件产生的底层主要是输入子系统,Android 中的输入设备有很多,例如屏幕,鼠标,键盘等都是输入设备,对于应用开发者,接触最多的也就是屏幕了。当输入设备可用时,Linux会在 /dev/input 中创建对应的设备节点。用户操作输入设备就会产生各种事件,这些事件的原始信息就会被 Linux内核中的输入子系统采集,原始信息由 Kernel space 的驱动层一直传递到设备结点。
Android提供了一些api可以让开发者在设备节点(dev/input/)中读取内核写入的事件。
InputManagerService
IMS 的工作就是监听 /dev/input 下所有设备的节点,当有数据时就会进行加工处理,并交给中间层的 WMS 派发给合适的 Window。后面简称为IMS。
WindowManagerService
Android的WMS主要是负责窗口管理,窗口的启动、添加、删除,管理窗口大小、层级等,WMS与IMS息息相关。IMS将底层封装好的事件通过Socket pair传递给WMS,WMS通过绑定好的InputChannel找到对应的窗口进行app层的事件分发,这时候就到我们熟悉的Activity的事件分发流程了。
下面借张图,整体流程画的非常详细:
今天我们主要讲底层硬件驱动产生的事件的获取及分发流程,至于硬件驱动层就不展开介绍了。而这里面最重要的就是IMS相关的流程,下面开始补足知识盲区。
IMS的初始化
IMS的构造
和WMS一样,IMS 也是在 SystemServer 中创建的 ,也是 startOtherServers 方法中。
less
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
...
inputManager = new InputManagerService(context);
wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
...
inputManager.start();
}
主要做了三件事:
- 调用了IMS的构造方法
- 将IMS作为参数传递给WMS
- 调用了IMS的启动方法start
IMS 构造函数
scss
public InputManagerService(Context context) {
this.mContext = context;
//注释1
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
mStaticAssociations = loadStaticInputPortAssociations();
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
//注释2
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
}
我们重点关注1创建了一个在android.display 线程的InputManagerHandler,所以后面这个handler处理的消息都是执行在android.display 线程中,android.display 线程是系统共享的单例前台线程,可以用做一些低延时显示的相关操作,WMS 的创建也是在 android.display 中创建的。
注释2 的话,调用了nativeInit 方法,进入native侧创建了 NativeInputManager ,并将该对象指针返回给了 java层,方便后续调用处理。
scss
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == nullptr) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
接下来看下NativeInputManager的创建
scss
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();
mContextObj = env->NewGlobalRef(contextObj);//注释1
mServiceObj = env->NewGlobalRef(serviceObj);//注释2
...
sp<EventHub> eventHub = new EventHub();//注释3
mInputManager = new InputManager(eventHub, this, this);//4
}
- 注释1:将java层的Context上下文保存在mContextObj中
- 注释2:将java层传递下来的InputManagerService对象保存在mServiceObj中。
- 注释3:创建了一个EventHub,EventHub 通过 Linux 内核的 Notify 与 Epoll 机制监听设备节点,通过 EventHub 的 getEvent 函数读取设备节点的增删事件和原始输入事件。
- 最后创建了InputManager 对象
scss
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
//注释1
mDispatcher = createInputDispatcher(dispatcherPolicy);
mClassifier = new InputClassifier(mDispatcher);
//注释2
mReader = createInputReader(readerPolicy, mClassifier);
}
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) {
return new InputReader(std::make_unique<EventHub>(), policy, listener);
}
其内部会创建InputDispatcher和InputReader。
- InputDispatcher,该类主要用于对原始事件的分发,传递给 WMS。
- InputReader,会不断的读取 EventHub 中的原始信息进行加工并交给 InputDispatcher ,InputDispatcher 中保存了 Window 的信息(WMS 会将窗口的信息实时更新到 InputDispatcher 中),可以将事件信息派发到合适的窗口,InputReader 和 InputDispatcher 都是耗时操作,会在单独线程中执行。
IMS构造主要是创建了NativeInputManager的native对象并返回给java层,将java层的IMS对象保存在native层的mServiceObj中,创建用于读取设备节点事件的EventHub对象。在InputManager构造中,创建了一个InputDispatcher和InputReader对象,以及用于读取事件的InputReaderThread线程和分发事件的InputDispatcherThread线程。
IMS启动
在 IMS 创建完成之后,就会调用他的 start 方法进行启动
csharp
public void start() {
nativeStart(mPtr);
}
调用native的start并传入native 层 NativeInputManager 对象的指针,会调native的InputManager的start方法,该方法内部分别会IputDispatcher和InputReader的start方法。
scss
status_t InputManager::start() {
//注释1
status_t result = mDispatcher->start();
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
//注释2
result = mReader->start();
if (result) {
ALOGE("Could not start InputReader due to error %d.", result);
mDispatcher->stop();
return result;
}
return OK;
}
- 调用IputDispatcher 的 start 方法,用于对事件进行分发
- 调用InputReader 的 start 方法,用于从 EventHub 中获取原始事件进行处理。
InputReader 读取事件
kotlin
status_t InputReader::start() {
if (mThread) {
return ALREADY_EXISTS;
}
mThread = std::make_unique<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}
InputReader中实例化了一个InputThread ,该类继承了 Thread,其内部有一个循环,当线程运行时,会调用 threadloop 函数,如果返回了 true 并且没有调用 requestExit 函数,就会循环调用 threadloop 函数,线程中真正执行的是loopOnce
scss
void InputReader::loopOnce() {
......
//1
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
if (count) {
//2
processEventsLocked(mEventBuffer, count);
}
} // release lock
......
//3
mQueuedListener->flush();
}
主要做三件事:
- 调用 EventHub 的 getEvents 函数来获取设备节点的信息到 mEventBuffer 中,事件信息主要有两种,一种是设备的增删事件(设备事件),一种是原始的输入事件,注:方法内部在没有输入事件的时候会进行休眠,并不会持续占用CPU
- 对 mEventBuffer 中的输入事件信息进行加工处理,加工处理后的事件会交给 InputDispatcher 来处理
- 如果无事件就调用flush方法,一次性将所有事件给InputDispatcher处理
我们继续看下怎么对事件进行加工处理的,
c
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type;
size_t batchSize = 1;
//注释1
if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
int32_t deviceId = rawEvent->deviceId;
while (batchSize < count) {
if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
rawEvent[batchSize].deviceId != deviceId) {
break;
}
batchSize += 1;
}
//注释2
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
switch (rawEvent->type) {//3
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 happen
break;
}
}
count -= batchSize;
rawEvent += batchSize;
}
}
void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
...
std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
...
mDevices.emplace(eventHubId, device);
...
}
所有事件用 RawEvent 封装,但对原始原始事件和设备事件分开处理事件和设备事件分开处理,其中设备事件有三种类型,分别是节点的添加,删除、扫描等操作,原始事件主要是交给processEventsForDeviceLocked处理,先来看下processEventsForDeviceLocked逻辑。
c
void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
size_t count) {
//注释1
auto deviceIt = mDevices.find(eventHubId);
if (deviceIt == mDevices.end()) {
ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
return;
}
//注释2
std::shared_ptr<InputDevice>& device = deviceIt->second;
if (device->isIgnored()) {
// ALOGD("Discarding event for ignored deviceId %d.", deviceId);
return;
}
device->process(rawEvents, count);
}
mDevices 中存的key是eventHub,而 value就是设备InputDevice,查到后进行处理
ini
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
if (mDropUntilNextSync) {
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
mDropUntilNextSync = false;
} else {
.....
}
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
//缓冲区溢出
ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
mDropUntilNextSync = true;
reset(rawEvent->when);
} else {
for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) {
mapper.process(rawEvent);
});
}
--count;
}
}
inline void for_each_mapper_in_subdevice(int32_t eventHubDevice,
std::function<void(InputMapper&)> f)
{
auto deviceIt = mDevices.find(eventHubDevice);
if (deviceIt != mDevices.end()) {
auto& devicePair = deviceIt->second;
auto& mappers = devicePair.second;
for (auto& mapperPtr : mappers) {
f(*mapperPtr);
}
}
最终是使用不同的InputMapper进行处理,原始输入事件的类型很多,因此 InputMapper 有很多子类用于加工不同的原始输入事件,例如 TouchInputMapper 用于处理触摸输入事件,KeyboardInputMapper 处理键盘输入事件等。以触摸事件为例,看下TouchInputMapper执行。
rust
void TouchInputMapper::process(const RawEvent* rawEvent) {
mCursorButtonAccumulator.process(rawEvent); //处理鼠标事件,type == EV_KEY 进行处理
mCursorScrollAccumulator.process(rawEvent); //处理鼠标滚轮事件,type == EV_REL 进行处理
mTouchButtonAccumulator.process(rawEvent); //处理屏幕触摸事件,type == EV_KEY 进行处理
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when, rawEvent->readTime);
}
}
arduino
void TouchInputMapper::processRawTouches(bool timeout) {
....
cookAndDispatch(when, readTime);
....
}
scss
void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentCookedState.buttonState;
// Dispatch pointer up events.
while (!upIdBits.isEmpty()) {
.....
dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0,
isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, buttonState, 0,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
dispatchedIdBits.clearBit(upId);
mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId);
}
// Dispatch move events if any of the remaining pointers moved from their old locations.
// Although applications receive new locations as part of individual pointer up
// events, they do not generally handle them except when presented in a move event.
if (moveNeeded && !moveIdBits.isEmpty()) {
...
dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
metaState, buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
// Dispatch pointer down events using the new pointer locations.
while (!downIdBits.isEmpty()) {
...
dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN,
0, 0, metaState, buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits,
downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
}
会根据记录的上一次触摸位置,根据事件类型做相应的分发。事件类型有Up,Down,Move等,最终调用
arduino
void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
uint32_t source, int32_t action, int32_t actionButton,
int32_t flags, int32_t metaState, int32_t buttonState,
int32_t edgeFlags, const PointerProperties* properties,
const PointerCoords* coords, const uint32_t* idToIndex,
BitSet32 idBits, int32_t changedId, float xPrecision,
float yPrecision, nsecs_t downTime) {
//最终形成的 NoteifyMotionArgs 对象
NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
policyFlags, action, actionButton, flags, metaState, buttonState,
MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,downTime, std::move(frames));
//回调到 InputDispatcher 的 notifyMotion 方法中
getListener()->notifyMotion(&args);
}
c
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
.......
bool needWake;
{
// Just enqueue a new motion event.
std::unique_ptr<MotionEntry> newEntry =
std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
......);
needWake = enqueueInboundEventLocked(std::move(newEntry));
mLock.unlock();
} // release lock
if (needWake) {
mLooper->wake();
}
}
bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
bool needWake = mInboundQueue.empty();
//将事件添加到 minboundQueue 中
mInboundQueue.push_back(std::move(newEntry));
EventEntry& entry = *(mInboundQueue.back());
traceInboundQueueLengthLocked();
...
return needWake;
}
最终调用enqueueInboundEventLocked 方法并将MotionEntry添加到队列,然后决定是否唤醒InputDispatcher线程。
获取事件的流程至此结束,我们总结下:
- InputReader 只是调用 EventHub 的 getEvent 获取了原始事件,获取到事件后,就会根据原始事件找到对应的 InputDevice(设备对象)。
- 在 InputDevice 中,根据事件获取到对应的 InputMapper 用于加工事件。InputMapper 子类分别对应了不同的事件类型,例如触摸事件,多点触摸事件,按键事件等。
- 最终事件被加工结束后都会通过 getListener 回调掉 InputDispatcher 中对应的两个方法,比如触摸事件被封装成了 MotionEntry 对象。最后调用 enqueueInboundEventLocked 方法,决定是否需要唤醒 InputDispatcher 线程
总结
由于篇幅过长,为避免带来阅读不适,事件分发后面再讲。感谢关注,感谢一键三连。
微信公众号首发,第一时间获取新知识请关注,感谢支持,欢迎大家关注、转发、评论。