Android怎么开发Input系统触摸事件分发

其他教程   发布日期:2023年07月27日   浏览次数:521

本篇内容介绍了“Android怎么开发Input系统触摸事件分发”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    引言

    Input系统: InputReader 处理触摸事件 分析了 InputReader 对触摸事件的处理流程,最终的结果是把触摸事件包装成 NotifyMotionArgs,然后分发给下一环。根据 Input系统: InputManagerService的创建与启动 可知,下一环是 InputClassifier。然而系统目前并不支持 InputClassifier 的功能,因此事件会被直接发送到 InputDispatcher。

    Input系统: 按键事件分发 分析了按键事件的分发流程,虽然分析的目标是按键事件,但是也从整体上,描绘了事件分发的框架。

    1. InputDispatcher 收到触摸事件

    1. void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
    2. if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
    3. args->pointerProperties)) {
    4. return;
    5. }
    6. uint32_t policyFlags = args->policyFlags;
    7. // 来自InputReader/InputClassifier的 motion 事件,都是受信任的
    8. policyFlags |= POLICY_FLAG_TRUSTED;
    9. android::base::Timer t;
    10. // 1. 对触摸事件执行截断策略
    11. // 触摸事件入队前,查询截断策略,查询的结果保存到参数 policyFlags
    12. mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);
    13. if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
    14. ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
    15. std::to_string(t.duration().count()).c_str());
    16. }
    17. bool needWake;
    18. { // acquire lock
    19. mLock.lock();
    20. if (shouldSendMotionToInputFilterLocked(args)) {
    21. // ...
    22. }
    23. // 包装成 MotionEntry
    24. // Just enqueue a new motion event.
    25. std::unique_ptr<MotionEntry> newEntry =
    26. std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
    27. args->source, args->displayId, policyFlags,
    28. args->action, args->actionButton, args->flags,
    29. args->metaState, args->buttonState,
    30. args->classification, args->edgeFlags,
    31. args->xPrecision, args->yPrecision,
    32. args->xCursorPosition, args->yCursorPosition,
    33. args->downTime, args->pointerCount,
    34. args->pointerProperties, args->pointerCoords, 0, 0);
    35. // 2. 把触摸事件加入收件箱
    36. needWake = enqueueInboundEventLocked(std::move(newEntry));
    37. mLock.unlock();
    38. } // release lock
    39. // 3. 如果有必要,唤醒线程处理触摸事件
    40. if (needWake) {
    41. mLooper->wake();
    42. }
    43. }

    InputDispatcher 收到触摸事件后的处理流程,与收到按键事件的处理流程非常相似

    • 对触摸事件进行截断策略查询。

    • 把触摸事件加入 InputDispatcher 收件箱,然后唤醒线程处理触摸事件。

    1.1 截断策略查询

    1. void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
    2. uint32_t&amp; policyFlags) {
    3. bool interactive = mInteractive.load();
    4. if (interactive) {
    5. policyFlags |= POLICY_FLAG_INTERACTIVE;
    6. }
    7. // 受信任,并且是非注入的事件
    8. if ((policyFlags &amp; POLICY_FLAG_TRUSTED) &amp;&amp; !(policyFlags &amp; POLICY_FLAG_INJECTED)) {
    9. if (policyFlags &amp; POLICY_FLAG_INTERACTIVE) {
    10. // 设备处于交互状态下,受信任且非注入的事件,直接发送给用户,而不经过截断策略处理
    11. policyFlags |= POLICY_FLAG_PASS_TO_USER;
    12. } else {
    13. // 只有设备处于非交互状态,触摸事件才需要执行截断策略
    14. JNIEnv* env = jniEnv();
    15. jint wmActions = env-&gt;CallIntMethod(mServiceObj,
    16. gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
    17. displayId, when, policyFlags);
    18. if (checkAndClearExceptionFromCallback(env,
    19. "interceptMotionBeforeQueueingNonInteractive")) {
    20. wmActions = 0;
    21. }
    22. handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
    23. }
    24. } else { // 注入事件,或者不受信任事件
    25. // 只有在交互状态下,才传递给用户
    26. // 注意,这里还有另外一层意思: 非交互状态下,不发送给用户
    27. if (interactive) {
    28. policyFlags |= POLICY_FLAG_PASS_TO_USER;
    29. }
    30. }
    31. }
    32. void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
    33. uint32_t&amp; policyFlags) {
    34. if (wmActions &amp; WM_ACTION_PASS_TO_USER) {
    35. policyFlags |= POLICY_FLAG_PASS_TO_USER;
    36. }
    37. }

    一个触摸事件,必须满足下面三种情况,才执行截断策略

    • 触摸事件是受信任的。来自输入设备的触摸事件都是受信任的。

    • 触摸事件是非注入的。monkey 的原理就是注入触摸事件,因此它的事件是不需要经过截断策略处理的。

    • 设备处于非交互状态。一般来说,非交互状态指的就是显示屏处于灭屏状态。

    另外还需要关注的是,事件在什么时候是不需要经过截断策略,有两种情况

    • 对于受信任且非注入的触摸事件,如果设备处于交互状态,直接发送给用户。 也就是说,如果显示屏处于亮屏状态,输入设备产生的触摸事件一定会发送给窗口。

    • 对于不受信任,或者注入的触摸事件,如果设备处于交互状态,也是直接发送给用户。也就是说,如果显示屏处于亮屏状态,monkey 注入的触摸事件,也是直接发送给窗口的。

    最后还要注意一件事,如果一个触摸事件是不受信任的事件,或者是注入事件,当设备处于非交互状态下(通常指灭屏),那么它不经过截断策略,也不会发送给用户,也就是会被丢弃。

    在实际工作中处理的触摸事件,通常都是来自输入设备,它肯定是受信任的,而且非注入的,因此它只有在设备处于非交互状态下(一般指灭屏)下,非会执行截断策略,而如果设备处于交互状态(通常指亮屏),会被直接分发给窗口。

    现在来看下截断策略的具体实现

    1. // PhoneWindowManager.java
    2. public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
    3. int policyFlags) {
    4. // 1. 如果策略要求唤醒屏幕,那么截断这个触摸事件
    5. // 一般来说,唤醒屏幕的策略取决于设备的配置文件
    6. if ((policyFlags &amp; FLAG_WAKE) != 0) {
    7. if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
    8. PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
    9. // 返回 0,表示截断触摸事件
    10. return 0;
    11. }
    12. }
    13. // 2. 判断非交互状态下,是否截断事件
    14. if (shouldDispatchInputWhenNonInteractive(displayId, KEYCODE_UNKNOWN)) {
    15. // 返回这个值,表示不截断事件,也就是事件分发给用户
    16. return ACTION_PASS_TO_USER;
    17. }
    18. // 忽略 theater mode
    19. if (isTheaterModeEnabled() &amp;&amp; (policyFlags &amp; FLAG_WAKE) != 0) {
    20. wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
    21. PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
    22. }
    23. // 3. 默认截断触摸事件
    24. // 返回0,表示截断事件
    25. return 0;
    26. }
    27. private boolean shouldDispatchInputWhenNonInteractive(int displayId, int keyCode) {
    28. // Apply the default display policy to unknown displays as well.
    29. final boolean isDefaultDisplay = displayId == DEFAULT_DISPLAY
    30. || displayId == INVALID_DISPLAY;
    31. final Display display = isDefaultDisplay
    32. ? mDefaultDisplay
    33. : mDisplayManager.getDisplay(displayId);
    34. final boolean displayOff = (display == null
    35. || display.getState() == STATE_OFF);
    36. if (displayOff &amp;&amp; !mHasFeatureWatch) {
    37. return false;
    38. }
    39. // displayOff 表示屏幕处于 off 状态,但是非 off 状态,并不表示一定是亮屏状态
    40. // 对于 doze 状态,屏幕处于 on 状态,但是屏幕可能仍然是黑的
    41. // 因此,只要屏幕处于 on 状态,并且显示了锁屏,触摸事件不会截断
    42. if (isKeyguardShowingAndNotOccluded() &amp;&amp; !displayOff) {
    43. return true;
    44. }
    45. // 对于触摸事件,keyCode 的值为 KEYCODE_UNKNOWN
    46. if (mHasFeatureWatch &amp;&amp; (keyCode == KeyEvent.KEYCODE_BACK
    47. || keyCode == KeyEvent.KEYCODE_STEM_PRIMARY
    48. || keyCode == KeyEvent.KEYCODE_STEM_1
    49. || keyCode == KeyEvent.KEYCODE_STEM_2
    50. || keyCode == KeyEvent.KEYCODE_STEM_3)) {
    51. return false;
    52. }
    53. // 对于默认屏幕,如果设备处于梦境状态,那么触摸事件不截断
    54. // 因为 doze 组件需要接收触摸事件,可能会唤醒屏幕
    55. if (isDefaultDisplay) {
    56. IDreamManager dreamManager = getDreamManager();
    57. try {
    58. if (dreamManager != null &amp;&amp; dreamManager.isDreaming()) {
    59. return true;
    60. }
    61. } catch (RemoteException e) {
    62. Slog.e(TAG, "RemoteException when checking if dreaming", e);
    63. }
    64. }
    65. // Otherwise, consume events since the user can't see what is being
    66. // interacted with.
    67. return false;
    68. }

    截断策略是否截断触摸事件,取决于策略的返回值,有两种情况

    • 返回 0,表示截断触摸事件。

    • 返回 ACTION_PASS_TO_USER ,表示不截断触摸事件,也就是把触摸事件分发给用户/窗口。

    下面列举触摸事件截断与否的情况,但是要注意一个前提,设备处于非交互状态(一般就是指灭屏状态)

    • 事件会被传递给用户,也就是不截断,情况如下

      • 有锁屏,并且显示屏处于非 off 状态。注意,非 off 状态,并不是表示屏幕处于 on(亮屏) 状态,也可能是 doze 状态(屏幕处于低电量状态),doze 状态屏幕也是黑的。

      • 梦境状态。因为梦境状态下会运行 doze 组件。

    • 事件被截断,情况如下

      • 策略标志位包含 FLAG_WAKE ,它会导致屏幕被唤醒,因此需要截断触摸事件。FLAG_WAKE 一般来自于输入设备的配置文件。

      • 没有锁屏,没有梦境,也没有 FLAG_WAKE,默认就会截断。

    从上面的分析可以总结出了两条结论

    • 如果系统有组件在运行,例如,锁屏、doze组件,那么触摸事件需要分发到这些组件,因此不会被截断。

    • 如果没有组件运行,触摸事件都会被截断。触摸事件由于需要唤醒屏幕,而导致被截断,只是其中一个特例。

    2. InputDispatcher 分发触摸事件

    由 Input系统: InputManagerService的创建与启动 可知,InputDispatcher 通过线程循环来处理收件箱中的事件,而且一次循环只能处理一个事件

    1. void InputDispatcher::dispatchOnce() {
    2. nsecs_t nextWakeupTime = LONG_LONG_MAX;
    3. { // acquire lock
    4. std::scoped_lock _l(mLock);
    5. mDispatcherIsAlive.notify_all();
    6. if (!haveCommandsLocked()) {
    7. // 1. 分发一个触摸事件
    8. dispatchOnceInnerLocked(&amp;nextWakeupTime);
    9. }
    10. // 触摸事件的分发过程不会产生命令
    11. if (runCommandsLockedInterruptible()) {
    12. nextWakeupTime = LONG_LONG_MIN;
    13. }
    14. // 2. 计算线程下次唤醒的时间点,以便处理 anr
    15. const nsecs_t nextAnrCheck = processAnrsLocked();
    16. nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
    17. if (nextWakeupTime == LONG_LONG_MAX) {
    18. mDispatcherEnteredIdle.notify_all();
    19. }
    20. } // release lock
    21. // 3. 线程休眠指定的时长
    22. nsecs_t currentTime = now();
    23. int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    24. mLooper-&gt;pollOnce(timeoutMillis);
    25. }

    一次线程循环处理触摸事件的过程如下

    • 分发一个触摸事件。

    • 当事件分发给窗口后,会计算一个窗口反馈的超时时间,利用这个时间,计算线程下次唤醒的时间点。

    • 利用上一步计算出的线程唤醒的时间点,计算出线程最终需要休眠多长时间。当线程被唤醒后,会检查接收触摸时间的窗口,是否反馈超时,如果超时,会引发 ANR。

    现在来看看如何分发一个触摸事件

    1. void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    2. nsecs_t currentTime = now();
    3. if (!mDispatchEnabled) {
    4. resetKeyRepeatLocked();
    5. }
    6. if (mDispatchFrozen) {
    7. return;
    8. }
    9. // 这里是优化 app 切换的延迟
    10. // mAppSwitchDueTime 是 app 切换的超时时间,如果小于当前时间,那么表明app切换超时了
    11. // 如果app切换超时,那么在app切换按键事件之前的未处理的事件,都将会被丢弃
    12. bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
    13. if (mAppSwitchDueTime < *nextWakeupTime) {
    14. *nextWakeupTime = mAppSwitchDueTime;
    15. }
    16. // mPendingEvent 表示正在处理的事件
    17. if (!mPendingEvent) {
    18. if (mInboundQueue.empty()) {
    19. // ...
    20. } else {
    21. // 1. 从收件箱队列中取出事件
    22. mPendingEvent = mInboundQueue.front();
    23. mInboundQueue.pop_front();
    24. traceInboundQueueLengthLocked();
    25. }
    26. // 如果这个事件需要传递给用户,那么需要同上层的 PowerManagerService,此时有用户行为,这个作用就是延长亮屏的时间
    27. if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
    28. pokeUserActivityLocked(*mPendingEvent);
    29. }
    30. }
    31. ALOG_ASSERT(mPendingEvent != nullptr);
    32. bool done = false;
    33. // 检测丢弃事件的原因
    34. DropReason dropReason = DropReason::NOT_DROPPED;
    35. if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
    36. // 被截断策略截断
    37. dropReason = DropReason::POLICY;
    38. } else if (!mDispatchEnabled) {
    39. // 一般是由于系统正在系统或者正在关闭
    40. dropReason = DropReason::DISABLED;
    41. }
    42. if (mNextUnblockedEvent == mPendingEvent) {
    43. mNextUnblockedEvent = nullptr;
    44. }
    45. switch (mPendingEvent->type) {
    46. // ....
    47. case EventEntry::Type::MOTION: {
    48. std::shared_ptr<MotionEntry> motionEntry =
    49. std::static_pointer_cast<MotionEntry>(mPendingEvent);
    50. if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
    51. // app 切换超时,导致触摸事件被丢弃
    52. dropReason = DropReason::APP_SWITCH;
    53. }
    54. if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
    55. // 10s 之前的事件,已经过期
    56. dropReason = DropReason::STALE;
    57. }
    58. // 这里是优化应用无响应的一个措施,会丢弃mNextUnblockedEvent之前的所有触摸事件
    59. if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
    60. dropReason = DropReason::BLOCKED;
    61. }
    62. // 2. 分发触摸事件
    63. done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
    64. break;
    65. }
    66. // ...
    67. }
    68. // 3. 如果事件被处理,重置一些状态,例如 mPendingEvent
    69. // 返回 true,就表示已经处理了事件
    70. // 事件被丢弃,或者发送完毕,都会返回 true
    71. // 返回 false,表示暂时不知道如何处理事件,因此线程会休眠
    72. // 然后,线程再次被唤醒时,再来处理这个事件
    73. if (done) {
    74. if (dropReason != DropReason::NOT_DROPPED) {
    75. dropInboundEventLocked(*mPendingEvent, dropReason);
    76. }
    77. mLastDropReason = dropReason;
    78. // 重置 mPendingEvent
    79. releasePendingEventLocked();
    80. // 立即唤醒,处理下一个事件
    81. *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
    82. }
    83. }

    Input系统: 按键事件分发 已经分析过 InputDispatcher 的线程循环。而对于触摸事件,是通过 InputDispatcher::dispatchMotionLocked() 进行分发

    1. bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
    2. DropReason* dropReason, nsecs_t* nextWakeupTime) {
    3. if (!entry->dispatchInProgress) {
    4. entry->dispatchInProgress = true;
    5. }
    6. // 1. 触摸事件有原因需要丢弃,那么不走后面的分发流程
    7. if (*dropReason != DropReason::NOT_DROPPED) {
    8. setInjectionResult(*entry,
    9. *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
    10. : InputEventInjectionResult::FAILED);
    11. return true;
    12. }
    13. bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
    14. std::vector<InputTarget> inputTargets;
    15. bool conflictingPointerActions = false;
    16. InputEventInjectionResult injectionResult;
    17. if (isPointerEvent) {
    18. // 寻找触摸的窗口,窗口保存到 inputTargets
    19. // 2. 为触摸事件,寻找触摸的窗口
    20. // 触摸的窗口保存到 inputTargets 中
    21. injectionResult =
    22. findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
    23. &conflictingPointerActions);
    24. } else {
    25. // ...
    26. }
    27. if (injectionResult == InputEventInjectionResult::PENDING) {
    28. // 返回 false,表示暂时不知道如何处理这个事件,这会导致线程休眠
    29. // 等线程下次被唤醒时,再来处理这个事件
    30. return false;
    31. }
    32. // 走到这里,表示触摸事件已经被处理,因此保存处理的结果
    33. // 只要返回的不是 InputEventInjectionResult::PENDING
    34. // 都表示事件被处理,无论是权限拒绝还是失败,或是成功
    35. setInjectionResult(*entry, injectionResult);
    36. if (injectionResult == InputEventInjectionResult::PERMISSION_DENIED) {
    37. ALOGW("Permission denied, dropping the motion (isPointer=%s)", toString(isPointerEvent));
    38. return true;
    39. }
    40. if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
    41. CancelationOptions::Mode mode(isPointerEvent
    42. ? CancelationOptions::CANCEL_POINTER_EVENTS
    43. : CancelationOptions::CANCEL_NON_POINTER_EVENTS);
    44. CancelationOptions options(mode, "input event injection failed");
    45. synthesizeCancelationEventsForMonitorsLocked(options);
    46. return true;
    47. }
    48. // 走到这里,表示触摸事件已经成功找到触摸的窗口
    49. // Add monitor channels from event's or focused display.
    50. // 3. 触摸事件找到了触摸窗口,在分发给窗口前,保存 global monitor 到 inputTargets 中
    51. // 开发者选项中的 Show taps 和 Pointer location,利用的 global monitor
    52. addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
    53. if (isPointerEvent) {
    54. // ... 省略 portal window 处理的代码
    55. }
    56. if (conflictingPointerActions) {
    57. // ...
    58. }
    59. // 4. 分发事件给 inputTargets 中的所有窗口
    60. dispatchEventLocked(currentTime, entry, inputTargets);
    61. return true;
    62. }

    一个触摸事件的分发过程,可以大致总结为以下几个过程

    • 如果有原因表明触摸事件需要被丢弃,那么触摸事件不会走后面的分发流程,即被丢弃。

    • 通常触摸事件是发送给窗口的,因此需要为触摸事件寻找触摸窗口。窗口最终被保存到 inputTargets 中。

    • inputTargets 保存触摸窗口后,还要保存 global monitor 窗口。例如开发者选项中的 Show taps 和 Pointer location,就是利用这个窗口实现的。

    • 启动分发循环,把触摸事件分发给 inputTargets 保存的窗口。 由于 Input系统: 按键事件分发 已经分发过这个过程,本文不再分析。

    2.1 寻找触摸的窗口

    1. InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
    2. nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
    3. nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
    4. // ...
    5. // 6. 对于非 DOWN 事件,获取已经 DOWN 事件保存的 TouchState
    6. // TouchState 保存了接收 DOWN 事件的窗口
    7. const TouchState* oldState = nullptr;
    8. TouchState tempTouchState;
    9. std::unordered_map<int32_t, TouchState>::iterator oldStateIt =
    10. mTouchStatesByDisplay.find(displayId);
    11. if (oldStateIt != mTouchStatesByDisplay.end()) {
    12. oldState = &(oldStateIt->second);
    13. tempTouchState.copyFrom(*oldState);
    14. }
    15. // ...
    16. // 第一个条件 newGesture 表示第一个手指按下
    17. // 后面一个条件,表示当前窗口支持 split motion,并且此时有另外一个手指按下
    18. if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
    19. /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
    20. // 触摸点的获取 x, y 坐标
    21. int32_t x;
    22. int32_t y;
    23. int32_t pointerIndex = getMotionEventActionPointerIndex(action);
    24. if (isFromMouse) {
    25. // ...
    26. } else {
    27. x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X));
    28. y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));
    29. }
    30. // 这里检测是否是第一个手指按下
    31. bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
    32. // 1. 对于 DOWN 事件,根据触摸事件的x,y坐标,寻找触摸窗口
    33. // 参数 addOutsideTargets 表示,只有在第一个手指按下时,如果没有找到触摸的窗口,
    34. // 那么需要保存那些可以接受 OUTSIZE 事件的窗口到 tempTouchState
    35. newTouchedWindowHandle&

    以上就是Android怎么开发Input系统触摸事件分发的详细内容,更多关于Android怎么开发Input系统触摸事件分发的资料请关注九品源码其它相关文章!