积分582 / 贡献0

提问0答案被采纳0文章52

作者动态

[经验分享] 应用与输入法捕获按键事件冲突问题分析报告 原创 精华

Laval社区小助手 显示全部楼层 发表于 2024-9-4 10:48:32

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_enablefalse,使得窗口子系统在分发事件时跳过输入法捕获焦点函数。

// 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);
    }
}
  1. 通过源码得知,如果想将按键事件分发到 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);
}
  1. 当正常情况输入法未开启时 isBindClient变量值为 false,此时返回 false,则 inputMethodHasProcessed最终结果为 false,会触发按键事件分发到 ACE的函数。
  2. 当异常情况下,继续执行进入 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();
}
  1. OnKeyEvent函数中执行按键事件回调,返回 true,从而使得 inputMethodHasProcessed变量为 true,窗口子系统无法将事件分发至 ACE,导致按键操作无效。
  2. 查看进程中,存在输入法进程,尝试杀死输入方进程,按键事件正常分发至 ACE框架。

  1. 查看在隐藏输入法时,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;
}
  1. 输入法在隐藏键盘后,未释放输入法实例,导致 isBindClient状态为 true,从而进入输入法按键事件捕获函数。

6 知识分享

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

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部