Android重学系列 IMS与事件分发(下)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android重学系列 IMS与事件分发(下)相关的知识,希望对你有一定的参考价值。
参考技术A 上一篇文章和大家聊到了IMS在SystemServer进程native层中的原理,本文来聊聊App进程是怎么监听IMS分发出来的输入信号的.还记得我写过WMS系列文章 WMS在Activity启动中的职责 添加窗体(三) 中,提到了App第一次渲染的时候会通过ViewRootImpl的addWindow方法,在WMS中为当前的Activity中的PhoneWindow添加一个对应的WindowState进行管理。
让我们先看看ViewRootImpl中做了什么。
如果遇到什么问题,欢迎来到本文 https://www.jianshu.com/p/e26bb5fae995 下讨论
文件:/ frameworks / base / core / java / android / view / ViewRootImpl.java
在这个过程中,我们可以把它视作三大部分的逻辑
核心就是第二和第三点。先来看看第二点,Session的addToDisplay最后是调用到了WMS的addWindow中。
文件:/ frameworks / base / services / core / java / com / android / server / wm / WindowManagerService.java
我们把InputChannel相关的逻辑抽离出来:
我们来看看WindowState的openInputChannel方法。
文件:/ frameworks / base / services / core / java / com / android / server / wm / WindowState.java
能看到实际上这个过程诞生了一个很重要的对象InputWindowHandle,输入窗口的句柄。这个句柄最核心的对象就是通过WindowToken获取AppToken的InputApplicationHandle。
能看到这个过程,实际上和上一篇文章十分相似的monitorInput一节中的内容十分相似。
依次执行了如下的逻辑:
这样通过socketpair创建的一对socket对象,注册了一个新的发送端到IMS的native层中,就能被App端的InputChannel监听到。
从这里就可以知道,0号位置的InputChannel对应的socket就是服务端(发送端)。关于如何创建InputChannel,以及如何注册到IMS。这里就不多赘述,请阅读 IMS与事件分发(上) 。
这个对象很简单,他继承于InputEventReceiver。InputEventReceiver对象就是专门监听IMS输入事件的基类。每当IMS发送信号来了就会调用子类的onInputEvent方法,onBatchedInputEventPending。
我们先来看看InputEventReceiver的初始化。
核心实际上就是调用native方法在native层初始化了IMS事件监听器。
文件:/ frameworks / base / core / jni / android_view_InputEventReceiver.cpp
这里只是简单的生成一个NativeInputEventReceiver对象,并调用了NativeInputEventReceiver的initialize方法。为全局的clazz对象新增一个强引用计数。
从NativeInputEventReceiver的申明能看到实际上他是实现了LooperCallback。LooperCallback这个对象,可以阅读 Handler与相关系统调用的剖析(上) ,里面有讲解到LooperCallback实际上就是native层Looper回调后的监听对象,回调的方法就是虚函数handleEvent。
在NativeInputEventReceiver有一个十分重要的对象InputConsumer。当IMS回调了输入事件后,NativeInputEventReceiver使用InputConsumer在native层中进行处理。
构造函数没什么好看的,直接看看initialize初始化的方法。
能看到这里面实际上很简单,就是获取InputConsumer中的InputChannel中的fd,这里fd就是上面初始化好的接收端的InputChannel。因此就是获取主线程的Looper并使用Looper监听客户端的InputChannel。
一旦IMS有信号发送过来则立即回调LooperCallback中的handleEvent。
当输入信号从native层传送过来了,则会开始回调handleEvent方法。关于IMS如果读取输入事件,处理后传输过来,可以阅读我写的 IMS与事件分发(上) 。
大致上可以分为两种情况,分别对象Looper注册的事件类型ALOOPER_EVENT_INPUT和ALOOPER_EVENT_OUTPUT。
很多地方没解析清楚:
在NativeInputEventReceiver中,ALOOPER_EVENT_INPUT代表从驱动读取到的输入事件传递过来;ALOOPER_EVENT_OUTPUT代表此时需要关闭输入事件的监听,而传递过去的后返回的事件处理。
我们先来看看ALOOPER_EVENT_INPUT对应的事件处理。
核心处理方法是consumeEvents。
文件:/ frameworks / native / libs / input / InputTransport.cpp
先从InputChannel的recv系统调用获取socket里面的InputMessage数据。
虽然此时consumeBatches为false,但是result正常情况下不会是WOULD_BLOCK,会先执行consumeBatch批量处理触点事件。
在这个方法中分为两个类型处理:
能看到实际上Batch就是一个InputMessage的集合。每当检测到AMOTION_EVENT_ACTION_MOVE或者AMOTION_EVENT_ACTION_HOVER_MOVE的触点类型,则会添加到mBatches集合中,等待下一次的更新。
当下一次触点触发了回调,在这个outEvent链表不为空的循环前提下,canAddSample判断到当前PointerCount和之前的一致,会把InputMessage不断的添加到Batch的samples集合中。如果出现了不一致则需要consumeSamples进行更新Batch中记录的InputMessage。
这样就能跟踪到了这一批次的触点的轨迹,以及新增的触点。
如果只有单个触点则生成MotionEvent对象赋值给指针返回。
我们来看看InputEventReceiver是通过InputConsumer消费后是怎么触发接下来的逻辑。我们只看单点触发的逻辑。
实际上对应的是:
而onInputEvent这个方法实际上就是对应WindowInputEventReceiver。
可以看到最后回调到了enqueueInputEvent方法中。
能看到整个很久爱都难,就是生成一个obtainQueuedInputEvent对象,添加到mPendingInputEventTail链表的末端,调用scheduleProcessInputEvents方法分发。如果是需要立即响应则调用doProcessInputEvents方法。
能看到此时发送了一个MSG_PROCESS_INPUT_EVENTS一个Asynchronous异步消息。其实就是一个能在同步屏障内优先执行的消息。
核心还是调用了doProcessInputEvents。
Choreographer.mFrameInfo 更新了分发时间后,整个过程最核心的逻辑就是循环遍历mPendingInputEventHead调用deliverInputEvent进行事件的分发QueuedInputEvent。
逻辑分为如下几个步骤:
能看到这里面构建很多InputStage对象。这些对象都是通过责任链设计全部嵌套到一起。
我们简单的看看它的UML图,来区分他们的直接的关系:
先来看看InputStage的deliver
deliver的入口会判断当前QueuedInputEvent的状态。
我们来看看对整个链路从NativePreImeInputStage开始逆推回去,关键还是看apply中的方法。
在所有的InputStage中分为两类,一类是直接继承InputStage,一类是继承AsyncInputStage,我们优先看看AsyncInputStage。
在AsyncInputStage存储了一个QueuedInputEvent链表。当判断到事件打开了FLAG_FINISHED,其在核心方法forward做了如下的事情:
能看到这个过程中很简单,如果QueuedInputEvent持有了InputEventReceiver对象则会InputEventReceiver.finishInputEvent进行native方法的调用,告诉native层销毁了当前的事件。
能看到很简单就是调用InputConsumer的sendFinishedSignal方法发送该输入事件的序列号处理对应在InputDispatcher中事件。
当InputStage需要开始分发事件,就会调用apply方法,而apply中就会调用onProcess方法。每一个子类InputStage的onProcess其实就是意味着这个InputStage做了什么事情。
接下来我们就按照责任链的嵌套顺序来看看InputStage,每一个输入阶段都做了什么。
NativePreImeInputStage实际上就是就是处理InputQueue。
ViewPreImeInputStage 这个InputStage是预处理KeyEvent,把键盘等事件通过DecorView的dispatchKeyEventPreIme进行预处理分发。
ImeInputStage专门处理软键盘
IMS:InputDispatcher的焦点设置
IMS:InputDispatcher的焦点设置
android11-release
IMS:InputDispatcher线程分发事件
ANR InputDispatching TimeOut超时判断
KeyEvent 和 MotionEvent中对应inputTargets
- dispatchKeyLocked 中 focusedWindowHandle、focusedApplicationHandle判断
- dispatchMotionLocked 中 findTouchedWindowAtLocked 查找 windowHandle
焦点窗口和焦点app设置
findFocusedWindowTargetsLocked
findTouchedWindowTargetsLocked
dispatchKeyLocked 中 focusedWindowHandle、focusedApplicationHandle
findFocusedWindowTargetsLocked
sp<InputWindowHandle> focusedWindowHandle =
getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
sp<InputApplicationHandle> focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
// Focus tracking for keys, trackball, etc.
std::unordered_map<int32_t, sp<InputWindowHandle>> mFocusedWindowHandlesByDisplay
GUARDED_BY(mLock);
std::unordered_map<int32_t, TouchState> mTouchStatesByDisplay GUARDED_BY(mLock);
// Focused applications.
std::unordered_map<int32_t, sp<InputApplicationHandle>> mFocusedApplicationHandlesByDisplay
GUARDED_BY(mLock);
mFocusedWindowHandlesByDisplay设置
/**
* Called from InputManagerService, update window handle list by displayId that can receive input.
* A window handle contains information about InputChannel, Touch Region, Types, Focused,...
* If set an empty list, remove all handles from the specific display.
* For focused handle, check if need to change and send a cancel event to previous one.
* For removed handle, check if need to send a cancel event if already in touch.
*/
void InputDispatcher::setInputWindowsLocked(
const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
if (DEBUG_FOCUS) {
std::string windowList;
for (const sp<InputWindowHandle>& iwh : inputWindowHandles) {
windowList += iwh->getName() + " ";
}
ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str());
}
// Copy old handles for release if they are no longer present.
const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);
sp<InputWindowHandle> newFocusedWindowHandle = nullptr;
bool foundHoveredWindow = false;
for (const sp<InputWindowHandle>& windowHandle : getWindowHandlesLocked(displayId)) {
// Set newFocusedWindowHandle to the top most focused window instead of the last one
if (!newFocusedWindowHandle && windowHandle->getInfo()->hasFocus &&
windowHandle->getInfo()->visible) {
newFocusedWindowHandle = windowHandle;
}
if (windowHandle == mLastHoverWindowHandle) {
foundHoveredWindow = true;
}
}
if (!foundHoveredWindow) {
mLastHoverWindowHandle = nullptr;
}
sp<InputWindowHandle> oldFocusedWindowHandle =
getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
if (!haveSameToken(oldFocusedWindowHandle, newFocusedWindowHandle)) {
if (oldFocusedWindowHandle != nullptr) {
if (DEBUG_FOCUS) {
ALOGD("Focus left window: %s in display %" PRId32,
oldFocusedWindowHandle->getName().c_str(), displayId);
}
sp<InputChannel> focusedInputChannel =
getInputChannelLocked(oldFocusedWindowHandle->getToken());
if (focusedInputChannel != nullptr) {
CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
"focus left window");
synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
enqueueFocusEventLocked(*oldFocusedWindowHandle, false /*hasFocus*/);
}
mFocusedWindowHandlesByDisplay.erase(displayId);
}
if (newFocusedWindowHandle != nullptr) {
if (DEBUG_FOCUS) {
ALOGD("Focus entered window: %s in display %" PRId32,
newFocusedWindowHandle->getName().c_str(), displayId);
}
mFocusedWindowHandlesByDisplay[displayId] = newFocusedWindowHandle;
enqueueFocusEventLocked(*newFocusedWindowHandle, true /*hasFocus*/);
}
if (mFocusedDisplayId == displayId) {
onFocusChangedLocked(oldFocusedWindowHandle, newFocusedWindowHandle);
}
}
std::unordered_map<int32_t, TouchState>::iterator stateIt =
mTouchStatesByDisplay.find(displayId);
if (stateIt != mTouchStatesByDisplay.end()) {
TouchState& state = stateIt->second;
for (size_t i = 0; i < state.windows.size();) {
TouchedWindow& touchedWindow = state.windows[i];
if (!hasWindowHandleLocked(touchedWindow.windowHandle)) {
if (DEBUG_FOCUS) {
ALOGD("Touched window was removed: %s in display %" PRId32,
touchedWindow.windowHandle->getName().c_str(), displayId);
}
sp<InputChannel> touchedInputChannel =
getInputChannelLocked(touchedWindow.windowHandle->getToken());
if (touchedInputChannel != nullptr) {
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
"touched window was removed");
synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options);
}
state.windows.erase(state.windows.begin() + i);
} else {
++i;
}
}
}
// Release information for windows that are no longer present.
// This ensures that unused input channels are released promptly.
// Otherwise, they might stick around until the window handle is destroyed
// which might not happen until the next GC.
for (const sp<InputWindowHandle>& oldWindowHandle : oldWindowHandles) {
if (!hasWindowHandleLocked(oldWindowHandle)) {
if (DEBUG_FOCUS) {
ALOGD("Window went away: %s", oldWindowHandle->getName().c_str());
}
oldWindowHandle->releaseChannel();
}
}
}
mFocusedApplicationHandlesByDisplay设置
void InputDispatcher::setFocusedApplication(
int32_t displayId, const sp<InputApplicationHandle>& inputApplicationHandle) {
if (DEBUG_FOCUS) {
ALOGD("setFocusedApplication displayId=%" PRId32 " %s", displayId,
inputApplicationHandle ? inputApplicationHandle->getName().c_str() : "<nullptr>");
}
{ // acquire lock
std::scoped_lock _l(mLock);
sp<InputApplicationHandle> oldFocusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
if (oldFocusedApplicationHandle == mAwaitedFocusedApplication &&
inputApplicationHandle != oldFocusedApplicationHandle) {
resetNoFocusedWindowTimeoutLocked();
}
if (inputApplicationHandle != nullptr && inputApplicationHandle->updateInfo()) {
if (oldFocusedApplicationHandle != inputApplicationHandle) {
mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle;
}
} else if (oldFocusedApplicationHandle != nullptr) {
oldFocusedApplicationHandle.clear();
mFocusedApplicationHandlesByDisplay.erase(displayId);
}
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
findTouchedWindowTargetsLocked->findTouchedWindowAtLocked 中 windowHandles
其实和上面一样windowHandles 从mWindowHandlesByDisplay
获取
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
int32_t y, TouchState* touchState,
bool addOutsideTargets,
bool addPortalWindows) {
if ((addPortalWindows || addOutsideTargets) && touchState == nullptr) {
LOG_ALWAYS_FATAL(
"Must provide a valid touch state if adding portal windows or outside targets");
}
// Traverse windows from front to back to find touched window.
const std::vector<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);
for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
const InputWindowInfo* windowInfo = windowHandle->getInfo();
if (windowInfo->displayId == displayId) {
int32_t flags = windowInfo->layoutParamsFlags;
if (windowInfo->visible) {
if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
bool isTouchModal = (flags &
(InputWindowInfo::FLAG_NOT_FOCUSABLE |
InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
int32_t portalToDisplayId = windowInfo->portalToDisplayId;
if (portalToDisplayId != ADISPLAY_ID_NONE &&
portalToDisplayId != displayId) {
if (addPortalWindows) {
// For the monitoring channels of the display.
touchState->addPortalWindow(windowHandle);
}
return findTouchedWindowAtLocked(portalToDisplayId, x, y, touchState,
addOutsideTargets, addPortalWindows);
}
// Found window.
return windowHandle;
}
}
if (addOutsideTargets && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
touchState->addOrUpdateWindow(windowHandle,
InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
BitSet32(0));
}
}
}
}
return nullptr;
}
InputMonitor.java桥梁
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
上面设置窗口是InputDispatcher内部方法,实质上窗口就是WMS设置下来的,通过WMS -> InputMonitor -> InputManagerService -> InputDispatcher
桥梁,在updateInputWindowsLw方法中更新。
InputMonitor.java中InputDispatcher连接点:
上面代码明显日志:
setFocusedApplication displayId
setInputWindows displayId
Focus left window:
Focus entered window:
以上是关于Android重学系列 IMS与事件分发(下)的主要内容,如果未能解决你的问题,请参考以下文章
InputDispatcher线程分发事件-Android12
Android 事件分发MotionEvent.ACTION_DOWN 按下事件分发流程( Activity | ViewGroup | View )