OpenHarmony开发者论坛

标题: 应用与输入法捕获按键事件冲突问题分析报告 [打印本页]

作者: Laval社区小助手    时间: 2024-9-4 10:48
标题: 应用与输入法捕获按键事件冲突问题分析报告
[md]# 1 关键字

按键事件;应用;输入法;

# 2 问题描述

环境:3.2.9.2及以下版本

问题现象:当在设备上点击任意输入框打开输入法,然后隐藏输入法,应用其他组件无法获取按键事件。

# 3 问题原因

## 3.1 正常机制

窗口子系统分发按键事件到 `ACE`,应用组件触发 `onKeyEvent`事件。

```
08-05 18:37:24.890  2053  2053 I C04200/WindowInputChannel: <44>HandleKeyEvent: Receive key event, windowId: 17, keyCode: 2013
08-05 18:37:24.890  2053  2053 I C04200/WindowInputChannel: <105>IsKeyboardEvent: isKeyFN: 0, isKeyboard: 1
08-05 18:37:24.890  2053  2053 I C04200/WindowInputChannel: <61>HandleKeyEvent: dispatch keyEvent to input method
08-05 18:37:24.891  2130  2131 I C01c00/ImsaKit: line: 38, function: OnRemoteRequest,InputMethodAgentStub::OnRemoteRequest code = 1
08-05 18:37:24.891  2130  2131 I C01c00/ImsaKit: line: 82, function: DispatchKeyEvent,InputMethodAgentStub:ispatchKeyEvent
08-05 18:37:24.891  2130  2131 I C01c00/ImsaKit: line: 303, function: DispatchKeyEvent,key = 2013, status = 3
08-05 18:37:24.891  2053  2053 I C04200/WindowInputChannel: <66>HandleKeyEvent: dispatch keyEvent to ACE
08-05 18:37:24.892  2053  2053 I C04200/WindowImpl: <2108>ConsumeKeyEvent: KeyCode: 2013, action: 3
08-05 18:37:24.892  2053  2053 I C04200/WindowImpl: <2121>ConsumeKeyEvent: Transfer key event to uiContent
```

## 3.2 异常机制

窗口子系统未将按键事件分发到 `ACE`,应用组件无法触发 `onKeyEvent`事件。

```
08-05 18:42:28.998  2053  2053 I C04200/WindowInputChannel: <44>HandleKeyEvent: Receive key event, windowId: 17, keyCode: 2013
08-05 18:42:28.998  2053  2053 I C04200/WindowInputChannel: <105>IsKeyboardEvent: isKeyFN: 0, isKeyboard: 1
08-05 18:42:28.999  2053  2053 I C04200/WindowInputChannel: <61>HandleKeyEvent: dispatch keyEvent to input method
08-05 18:42:28.999  2130  2131 I C01c00/ImsaKit: line: 38, function: OnRemoteRequest,InputMethodAgentStub::OnRemoteRequest code = 1
08-05 18:42:28.999  2130  2131 I C01c00/ImsaKit: line: 82, function: DispatchKeyEvent,InputMethodAgentStub:ispatchKeyEvent
08-05 18:42:28.999  2130  2131 I C01c00/ImsaKit: line: 303, function: DispatchKeyEvent,key = 2013, status = 3
08-05 18:42:28.999  2130  2131 I C01c00/ImsaKit: line: 356, function: OnKeyEvent,run in OnKeyEvent
08-05 18:42:28.999  2130  2131 I C01c00/ImsaKit: line: 412, function: GetKeyEventUVwork,run in GetKeyEventUVwork
08-05 18:42:29.001  2130  2130 I C01c00/ImsaKit: line: 477, function: MoveCursor,InputMethodAbility::MoveCursor
08-05 18:42:29.001  2130  2130 I C01c00/ImsaKit: line: 143, function: MoveCursor,InputDataChannelProxy::MoveCursor
08-05 18:42:29.001  1970  1976 I C01c00/ImsaKit: line: 41, function: OnRemoteRequest,InputDataChannelStub::OnRemoteRequest code = 11
08-05 18:42:29.001  1970  1976 I C01c00/ImsaKit: line: 218, function: MoveCursor,InputDataChannelStub::MoveCursor
08-05 18:42:29.002  1970  2013 I C01c00/ImsaKit: line: 214, function: WorkThread,InputMethodController::WorkThread MoveCursor
```

# 4 解决方案

方案一:跳过输入法捕获焦点函数。

在 `foundation/window/window_manager/wm/BUILD.gn`文件中,设置 `imf_enable`为 `false`,使得窗口子系统在分发事件时跳过输入法捕获焦点函数。

```
// foundation/window/window_manager/wm/BUILD.gn
...
imf_enable = false // 添加此行
if (imf_enable == true) {
   external_deps += [ "imf:inputmethod_client" ]
   defines += [ "IMF_ENABLE" ]
}
...
```

方案二:在隐藏输入法时,释放输入法实例。

```
// foundation/arkui/ace_engine/frameworks/core/components/text_field/render_text_field.cpp
bool RenderTextField::CloseKeyboard(bool forceClose)
{
   if (!isOverlayShowed_ || !isOverlayFocus_ || forceClose) {
       ...
#if defined(ENABLE_STANDARD_INPUT)
       auto inputMethod = MiscServices::InputMethodController::GetInstance();
       if (!inputMethod) {
           LOGE("Request close soft keyboard failed because input method is null.");
           return false;
       }
       inputMethod->HideTextInput();
       inputMethod->Close();  // 添加此行
#else
       if (HasConnection()) {
           connection_->Close(GetInstanceId());
           connection_ = nullptr;
       }
#endif
       ...
   }
   return false;
}
```

# 5 定位过程

1. 对比正常机制和异常机制捕获的日志,发现正常机制可以将按键事件分发到 `ACE`框架中,查看窗口子系统分发事件的逻辑代码。

```
// foundation/window/window_manager/wm/src/window_input_channel.cpp
void WindowInputChannel::HandleKeyEvent(std::shared_ptr<MMI::KeyEvent>& keyEvent)
{
   ...
   bool inputMethodHasProcessed = false;
#ifdef IMF_ENABLE
   bool isKeyboardEvent = IsKeyboardEvent(keyEvent);
   if (isKeyboardEvent) {
       WLOGFI("dispatch keyEvent to input method");
       inputMethodHasProcessed = MiscServices::InputMethodController::GetInstance()->dispatchKeyEvent(keyEvent);
   }
#endif // IMF_ENABLE
   if (!inputMethodHasProcessed) {
       WLOGFI("dispatch keyEvent to ACE");
       window_->ConsumeKeyEvent(keyEvent);
   }
}
```

2. 通过源码得知,如果想将按键事件分发到 `ACE`,需要 `inputMethodHasProcessed`变量为 `false`,追踪 `MiscServices::InputMethodController::GetInstance()->dispatchKeyEvent(keyEvent)`执行逻辑。

```
// base/inputmethod/imf/frameworks/inputmethod_ability/src/input_method_ability.cpp
bool InputMethodAbility:ispatchKeyEvent(int32_t keyCode, int32_t keyStatus)
{
   IMSA_HILOGI("key = %{public}d, status = %{public}d", keyCode, keyStatus);
   if (!isBindClient) {
       return false;
   }
   if (!kdListener_) {
       IMSA_HILOGI("InputMethodAbility:ispatchKeyEvent kdListener_ is nullptr");
       return false;
   }
   return kdListener_->OnKeyEvent(keyCode, keyStatus);
}
```

3. 当正常情况输入法未开启时 `isBindClient`变量值为 `false`,此时返回 `false`,则 `inputMethodHasProcessed`最终结果为 `false`,会触发按键事件分发到 `ACE`的函数。
4. 当异常情况下,继续执行进入 `OnKeyEvent`函数。

```
// base/inputmethod/imf/interfaces/kits/js/napi/inputmethodability/js_keyboard_delegate_setting.cpp
bool JsKeyboardDelegateSetting::OnKeyEvent(int32_t keyCode, int32_t keyStatus)
{
   IMSA_HILOGI("run in OnKeyEvent");
   KeyEventPara para{ keyCode, keyStatus, false };
   std::string type = (keyStatus == ARGC_TWO ? "keyDown" : "keyUp");
   auto isDone = std::make_shared<BlockData<bool>>(MAX_TIMEOUT, false);
   uv_work_t *work = GetKeyEventUVwork(type, para, isDone);   // 获取按键事件执行的work实例,不为空
   if (work == nullptr) {
       IMSA_HILOGE("GetKeyEventUVwork nullptr");
       return false;
   }
   uv_queue_work(
       loop_, work, [](uv_work_t *work) {},
       [](uv_work_t *work, int status) {
           bool isResult = false;
           std::shared_ptr<UvEntry> entry(static_cast<UvEntry *>(work->data), [work](UvEntry *data) {
               delete data;
               delete work;
           });
           bool isOnKeyEvent = false;
           for (auto item : entry->vecCopy) {
               napi_value jsObject =
                   GetResultOnKeyEvent(item->env_, entry->keyEventPara.keyCode, entry->keyEventPara.keyStatus);
               if (jsObject == nullptr) {
                   IMSA_HILOGE("get GetResultOnKeyEvent failed: %{punlic}p", jsObject);
                   continue;
               }
               napi_value callback = nullptr;
               napi_value args[] = { jsObject };
               napi_get_reference_value(item->env_, item->callback_, &callback);
               if (callback == nullptr) {
                   IMSA_HILOGE("callback is nullptr");
                   continue;
               }
               napi_value global = nullptr;
               napi_get_global(item->env_, &global);
               napi_value result = nullptr;
               napi_status callStatus = napi_call_function(item->env_, global, callback, 1, args, &result); // 执行按键回调函数,返回结果为 true
               if (callStatus != napi_ok) {
                   IMSA_HILOGE(
                       "notify data change failed callStatus:%{public}d callback:%{public}p", callStatus, callback);
                   continue;
               }
               if (result != nullptr) {
                   napi_get_value_bool(item->env_, result, &isResult);
                   if (isResult) {
                       isOnKeyEvent = true;
                   }
               }
           }
           entry->isDone->SetValue(isOnKeyEvent);
       });
   return isDone->GetValue();
}
```

5. 在 `OnKeyEvent`函数中执行按键事件回调,返回 `true`,从而使得 `inputMethodHasProcessed`变量为 `true`,窗口子系统无法将事件分发至 `ACE`,导致按键操作无效。
6. 查看进程中,存在输入法进程,尝试杀死输入方进程,按键事件正常分发至 `ACE`框架。

![](file:///C:/Users/kuangansheng/Desktop/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E6%A1%88%E4%BE%8B/%E5%BA%94%E7%94%A8%E4%B8%8E%E8%BE%93%E5%85%A5%E6%B3%95%E6%8D%95%E8%8E%B7%E6%8C%89%E9%94%AE%E4%BA%8B%E4%BB%B6%E5%86%B2%E7%AA%81%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A/figures/img_1.png?lastModify=1687674388)

![](https://devpress.csdnimg.cn/1ddd10aa48b34199a3d81ce3eabd4233.png)

7. 查看在隐藏输入法时,`ACE`框架所做的处理。

```
// foundation/arkui/ace_engine/frameworks/core/components/text_field/render_text_field.cpp
bool RenderTextField::CloseKeyboard(bool forceClose)
{
   if (!isOverlayShowed_ || !isOverlayFocus_ || forceClose) {
       if (!textFieldController_) {
           StopTwinkling();
       }
       LOGI("Request close soft keyboard");
#if defined(ENABLE_STANDARD_INPUT)
       auto inputMethod = MiscServices::InputMethodController::GetInstance();
       if (!inputMethod) {
           LOGE("Request close soft keyboard failed because input method is null.");
           return false;
       }
       inputMethod->HideTextInput();
#else
       if (HasConnection()) {
           connection_->Close(GetInstanceId());
           connection_ = nullptr;
       }
#endif

       if (onKeyboardClose_) {
           onKeyboardClose_(forceClose);
           onKeyboardClose_ = nullptr;
           UpdateSelection(GetEditingValue().selection.GetEnd());
           MarkNeedLayout();
       }
       ResetSlidingPanelParentHeight();
       if (keyboard_ != TextInputType::MULTILINE && keyboard_ != TextInputType::VISIBLE_PASSWORD) {
           resetToStart_ = true;
           MarkNeedLayout();
       }
       return true;
   }
   return false;
}
```

7. 输入法在隐藏键盘后,未释放输入法实例,导致 `isBindClient`状态为 `true`,从而进入输入法按键事件捕获函数。

# 6 知识分享

按键事件的分发到应用组件的流程为:多模输入->窗口子系统->ARKUI框架
[/md]




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5