应用与输入法捕获按键事件冲突问题分析报告
1 关键字 按键事件;应用;输入法; 2 问题描述 环境:3.2.9.2及以下版本 问题现象:当在设备上点击任意输入框打开输入法,然后隐藏输入法,应用其他组件无法获取按键事件。 3 问题原因 3.1 正常机制 窗口子系统分发按键事件到ACE,应用组件触发onKeyEvent事件。 08-05 18:37:24.890 2053 2053 I C04200/WindowInputChannel: <
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::DispatchKeyEvent
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::DispatchKeyEvent
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 定位过程
-
对比正常机制和异常机制捕获的日志,发现正常机制可以将按键事件分发到
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);
}
}
-
通过源码得知,如果想将按键事件分发到
ACE
,需要inputMethodHasProcessed
变量为false
,追踪MiscServices::InputMethodController::GetInstance()->dispatchKeyEvent(keyEvent)
执行逻辑。
// base/inputmethod/imf/frameworks/inputmethod_ability/src/input_method_ability.cpp
bool InputMethodAbility::DispatchKeyEvent(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::DispatchKeyEvent kdListener_ is nullptr");
return false;
}
return kdListener_->OnKeyEvent(keyCode, keyStatus);
}
-
当正常情况输入法未开启时
isBindClient
变量值为false
,此时返回false
,则inputMethodHasProcessed
最终结果为false
,会触发按键事件分发到ACE
的函数。 -
当异常情况下,继续执行进入
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();
}
-
在
OnKeyEvent
函数中执行按键事件回调,返回true
,从而使得inputMethodHasProcessed
变量为true
,窗口子系统无法将事件分发至ACE
,导致按键操作无效。 -
查看进程中,存在输入法进程,尝试杀死输入方进程,按键事件正常分发至
ACE
框架。
-
查看在隐藏输入法时,
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;
}
-
输入法在隐藏键盘后,未释放输入法实例,导致
isBindClient
状态为true
,从而进入输入法按键事件捕获函数。
6 知识分享
按键事件的分发到应用组件的流程为:多模输入->窗口子系统->ARKUI框架
更多推荐
所有评论(0)