1 关键字

输入法;input;

2 问题描述

问题现象:开发板安装 gitee OpenHarmony-v3.0-LTS 和 OpenHarmony-v3.0.2-LTS 分支的代码编译出来系统镜像,输入法软键盘输入时,屏幕闪烁,TextInput 输入框中文本不刷新,当输入法界面隐藏时才刷新。

3 问题原因

3.1 正常机制

软键盘输入能及时在 input 组件显示。

3.2 异常机制

输入法软键盘输入时,屏幕闪烁,TextInput 输入框中文本不刷新。

input client 端在收到拉起输入法应用的消息后。AbilityManagerService 调用 StartAbility,拉起输入法应用。

 void PerUserSession::BindInputAbility()
    {
        IMSA_HILOGE("PerUserSession::BindInputAbility");
        AAFwk::Want want;
        want.SetAction("action.system.inputmethod");
        want.SetElementName("com.example.kikakeyboard", "com.example.kikakeyboard.MainAbility");
        sptr<InputMethodAbilityConnectionStub> stub(new (std::nothrow) InputMethodAbilityConnectionStub(0));
        sptr<AAFwk::AbilityConnectionProxy> connCallback = new (std::nothrow) AAFwk::AbilityConnectionProxy(stub);
      GetAbilityManagerService()->StartAbility(want);
    }
​

输入法应用在拉起后,输入法的 MainAbility 来到前台,生命周期状态为 Activate,用户程序的 Ability 的生命周期状态变为 Background,软键盘在输入后,由于用户程序的 Ability的生命周期变为 Background ,用户程序的 Ability 页面不会刷新,当输入法界面隐藏时,用户程序的 Ability的生命周期变为 Activate,界面刷新,输入框输入的文本显示到界面上。软键盘在输入时,输入法的 MainAbility 同用户程序的 Ability 不断抢占焦点,导致屏幕不停闪烁。

4 解决方案

在拉起输入法应用时,在启动参数中设置 Ability window 的层级。在启动参数中设置 windowMode 为 MULTI_WINDOW_DISPLAY_FLOATING。设置该参数后,输入法 Ability window 将浮动在用户程序上方。

下面将引导读者修该对应的源码。

可以将 3.0.2 lts 分支下的 对应代码移植过来。

修改peruser_session.cpp

修改 base\miscservices\inputmethod\services\src\peruser_session.cpp 文件中的 BindInputAbility 方法

void PerUserSession::BindInputAbility()
{
    IMSA_HILOGE("PerUserSession::BindInputAbility");
    AAFwk::Want want;
    want.SetAction("action.system.inputmethod");
    want.SetElementName("com.example.kikakeyboard", "com.example.kikakeyboard.MainAbility");
    sptr<InputMethodAbilityConnectionStub> stub(new (std::nothrow) InputMethodAbilityConnectionStub(0));
    sptr<AAFwk::AbilityConnectionProxy> connCallback = new (std::nothrow) AAFwk::AbilityConnectionProxy(stub);
    // GetAbilityManagerService()->StartAbility(want);
​
    std::shared_ptr<AAFwk::AbilityStartSetting> setting = AAFwk::AbilityStartSetting::GetEmptySetting();
    setting->AddProperty(AAFwk::AbilityStartSetting::WINDOW_MODE_KEY,
    std::to_string(AAFwk::AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_FLOATING));
    GetAbilityManagerService()->StartAbility(want, *setting, nullptr, -1);
}

修改ability_stack_manager.cpp

修改 foundation\aafwk\standard\services\abilitymgr\src\ability_stack_manager.cpp 中的 MoveMissionAndAbility 方法

void AbilityStackManager::MoveMissionAndAbility(const std::shared_ptr<AbilityRecord> &currentTopAbility,
    std::shared_ptr<AbilityRecord> &targetAbilityRecord, std::shared_ptr<MissionRecord> &targetMissionRecord)
{
    ...
​
   // add caller record
    targetAbilityRecord->SetMissionRecord(targetMissionRecord);
    // reparent mission record, currentMissionStack is the target mission stack.
    targetMissionRecord->SetMissionStack(currentMissionStack_, currentMissionStack_->GetMissionStackId());
    targetAbilityRecord->SetMissionStackId(currentMissionStack_->GetMissionStackId());
    // add ability record to mission record.
    // if this ability record exist this mission record, do not add.
    targetMissionRecord->AddAbilityRecordToTop(targetAbilityRecord);
    // add mission record to mission stack.
    // if this mission record exist this mission stack, do not add.
    currentMissionStack_->AddMissionRecordToTop(targetMissionRecord);
    // move mission record to top
    // if this mission exist at top, do not move.
    currentMissionStack_->MoveMissionRecordToTop(targetMissionRecord);
}

修改ability_record.cpp

foundation\aafwk\standard\services\abilitymgr\src\ability_record.cpp 添加如下方法,并添加对应头文件。

void AbilityRecord::SetConfiguration(const std::shared_ptr<DummyConfiguration> &config)
{
    ConfigurationHolder::Init(config);
}

修改configuration_holder.cpp

修改 foundation\aafwk\standard\services\abilitymgr\src\configuration_holder.cpp 中的 Init 方法。

void ConfigurationHolder::Init(const std::shared_ptr<DummyConfiguration> &config)
{
    baseConfiguration_ = config;
}

修改 foundation\aafwk\standard\services\abilitymgr\src\configuration_holder.cpp 中的 ProcessConfigurationChangeInner 方法。

bool ConfigurationHolder::ProcessConfigurationChangeInner(const std::shared_ptr<DummyConfiguration> &config)
{
    auto configChanges = baseConfiguration_ ? baseConfiguration_->Differ(config) : CHANGE_CONFIG_ALL_CHANGED;
    if (configChanges > 0) {
    if (baseConfiguration_) {
    baseConfiguration_ = config;
    return OnConfigurationChanged(*(baseConfiguration_.get()), configChanges);
    }
    // update base configuration immediately
    baseConfiguration_ = config;
    HILOG_DEBUG("have changes.");
    }
    HILOG_DEBUG("there is no change.");
    return false;
}

 

修改kernal_system_app_manager.cpp

修改 foundation\aafwk\standard\services\abilitymgr\src\kernal_system_app_manager.cpp 中的 GetOrCreateAbilityRecord 方法。

void KernalSystemAppManager::GetOrCreateAbilityRecord(
    const AbilityRequest &abilityRequest, std::shared_ptr<AbilityRecord> &targetAbility)
{
    ...
    targetAbility = AbilityRecord::CreateAbilityRecord(abilityRequest);
    // add line
    auto configSptr = std::make_shared<DummyConfiguration>();
    // add line
    targetAbility->SetConfiguration(configSptr);
    abilities_.push_front(targetAbility);
}

修改mission_record.cpp

修改 foundation\aafwk\standard\services\abilitymgr\src\mission_record.cpp 中的 SetMissionStack 方法。

void MissionRecord::SetMissionStack(const std::shared_ptr<MissionStack> &missionStack, int stackId)
{
    CHECK_POINTER(missionStack);
    parentMissionStack_ = missionStack;
    // UpdateConfiguration(missionStack->GetConfiguration());
    ConfigurationHolder::Init(missionStack->GetConfiguration());
    for (auto &it : abilities_) {
        it->SetMissionStackId(stackId);
    }
}

修改 foundation\aafwk\standard\services\abilitymgr\src\mission_record.cpp 中的 Resume 方法。

void MissionRecord::Resume(const std::shared_ptr<MissionRecord> &backup)
{
   ...
    for (auto &ability : abilities_) {
        if (ability->IsAbilityState(AbilityState::INITIAL)) {
            // add line
            ability->ClearFlag();
            ability->SetRestarting(true);
        }
    }
}

修改mission_stack.cpp

修改 foundation\aafwk\standard\services\abilitymgr\src\mission_stack.cpp 中的 MissionStack 方法。

MissionStack::MissionStack(int id, int userId) : missionStackId_(id), userId_(userId)
{
    // add line
    auto configSptr = std::make_shared<DummyConfiguration>();
    // add line
    ConfigurationHolder::Init(configSptr);
}

修改kikaInput.hap

需要将输入法应用源码中的config.json 中的 launchType 修改为 singleton 为。然后重新编译打包。或者直接用用3.0.2 lts 中的输入法应用替换。

"launchType": "singleton"

将替换后hap放置 applications\standard\hap\kikaInput.hap 目录中。

编译

调用对应的编译命令编译镜像并烧录开发板。

测试

在使用 input 组件时,能够正常的拉起输入法应用,并且 input 输入框中文本能及时刷新,且页面不再闪烁。

5 定位过程

在拉起输入法应用后,输入框输入,但是用户程序不刷新。针对界面不刷新,结合以往经验,考虑是否是因为程序生命周期的影响,于是,查看日志,发现在拉起输入法应用后,输入法的 MainAbility 生命周期状态为 Activate,用户程序的 Ability 的生命周期状态变为 Background,当输入法界面隐藏时,用户程序的 Ability的生命周期变为 Activate,界面刷新,输入框输入的文本显示到界面上。正确的机制应该是输入法拉起后,用户程序在的生命周期不应该变为Background。于是查看,启动输入法的源代码。

void PerUserSession::BindInputAbility()
{
	IMSA_HILOGE("PerUserSession::BindInputAbility");
	AAFwk::Want want;
	want.SetAction("action.system.inputmethod");
	want.SetElementName("com.example.kikakeyboard", "com.example.kikakeyboard.MainAbility");
	sptr<InputMethodAbilityConnectionStub> stub(new (std::nothrow) InputMethodAbilityConnectionStub(0));
	sptr<AAFwk::AbilityConnectionProxy> connCallback = new (std::nothrow) AAFwk::AbilityConnectionProxy(stub);
	GetAbilityManagerService()->StartAbility(want);
}

查看代码可知,AbilityManagerService 直接以默认方式启动了输入法的MainAbility,这就解释了在拉起输入法应用后,输入法的 MainAbility 生命周期状态为 Activate,用户程序的 Ability 的生命周期状态变为 Background的原因。

通过查看 OpenHarmony 3.1Byte 分支的代码,于是在启动输入法Ability时,增加 Window modeType 为MULTI_WINDOW_DISPLAY_FLOATING模式。

void PerUserSession::BindInputAbility()
{
	......
	std::shared_ptr<AAFwk::AbilityStartSetting> setting = AAFwk::AbilityStartSetting::GetEmptySetting();
	setting->AddProperty(AAFwk::AbilityStartSetting::WINDOW_MODE_KEY,
	std::to_string(AAFwk::AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_FLOATING));
	GetAbilityManagerService()->StartAbility(want, *setting, nullptr, -1);
}

在将上述修改编译镜像后,再次测试,发现输入法在拉起后,用户程序界面变为了lunch界面。再次查看日志。并同正常版本的日志对比。

  • 异常日志:

......
[ability_record.cpp(AddCallerRecord:618)]pointer is nullptr.

// 异常调用
[ability_record.cpp(NotifyMultiWinModeChanged:962)]Notify multi window mode changed.
[ability_scheduler_stub.cpp(OnRemoteRequest:70)]AbilitySchedulerStub::OnRemoteRequest, cmd = <private>, flags= <private>
[ability_thread.cpp(NotifyMultiWinModeChanged):865] NotifyMultiWinModeChanged.key:102,flag:1
......
  • 正常日志:

[ability_record.cpp(AddCallerRecord:618)]pointer is nullptr.

// 正确调用
[ability_stack_manager.cpp(MoveMissionAndAbility:281)]Move mission and ability.
[ability_stack_manager.cpp(StartAbilityAsSpecialLocked:260)]First create mission record ,missionId:2
[ability_stack_manager.cpp(StartAbilityLifeCycle:198)]ChangeType: 0, needBackAbility : none
[ability_record.cpp(ProcessActivate:387)]ability record: /com.example.kikakeyboard/com.example.kikakeyboard.MainAbility
[ability_record.cpp(LoadAbility:126)]<private>

再看关键日志后查看对应的源码。

  • ability_stack_manager foundation\aafwk\standard\services\abilitymgr\src\ability_stack_manager.cpp

int AbilityStackManager::StartAbilityAsSpecialLocked(
    const std::shared_ptr<AbilityRecord> &currentTopAbility, const AbilityRequest &abilityRequest)
{
   ...... //正常进入if 分支
    if (targetMissionRecord->GetMissionStack() == nullptr ||
        targetMissionRecord->GetMissionStack()->GetMissionStackId() == targetStack->GetMissionStackId()) {
        MoveMissionStackToTop(targetStack);
        MoveMissionAndAbility(currentTopAbility, targetAbilityRecord, targetMissionRecord);
		......
        return StartAbilityLifeCycle(lastTopAbility, currentTopAbility, targetAbilityRecord);
    }
	...... //异常进入else
    isMultiWinMoving_ = true;
    CompleteMoveMissionToStack(targetMissionRecord, targetStack);
    MoveMissionAndAbility(currentTopAbility, targetAbilityRecord, targetMissionRecord);
  ......
}

经过排查正常日志,发现 targetMissionRecord->GetMissionStack()应该返回 null,所以代码应该会进入if 分支,异常情况下,targetMissionRecord->GetMissionStack() 方法返回了具体对象。

查看 targetMissionRecord 赋值的代码,发现在launchMode为SINGLETON时,targetMissionRecord 会赋值为空。

void AbilityStackManager::GetMissionRecordAndAbilityRecord(const AbilityRequest &abilityRequest,
    const std::shared_ptr<AbilityRecord> &currentTopAbility, std::shared_ptr<AbilityRecord> &targetAbilityRecord,
    std::shared_ptr<MissionRecord> &targetMissionRecord)
{
  	......
    if (abilityRequest.abilityInfo.launchMode == AppExecFwk::LaunchMode::SINGLETON) {
        GetRecordBySingleton(abilityRequest, currentTopAbility, targetAbilityRecord, targetMissionRecord);
    } else {
        GetRecordByStandard(abilityRequest, currentTopAbility, targetAbilityRecord, targetMissionRecord);
    }
}

abilityRequest.abilityInfo.launchMode 值为解析的应用的 cnfig.json 的 launchType,经过对比,发现3.0lts 异常版本的输入法的 aunchType为 standard ,而3.0.2 lts 正常版本的输入法的launchType为 singleton,于是将 输入法应用的 lunchType 修改为 singleton。

6 知识分享

日志分析是开发中常用的问题查找技巧,当我们遇到疑难的问题是,采用日志分析法,一般可以快速的定位问题以及解决问题。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐