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