3.0LTS版本输入法软键盘输入时屏幕闪烁问题分析报告
3.0LTS版本输入法软键盘输入时屏幕闪烁问题分析报告 1 关键字 输入法;input; 2 问题描述 问题现象:开发板安装 gitee OpenHarmony-v3.0-LTS 和 OpenHarmony-v3.0.2-LTS 分支的代码编译出来系统镜像,输入法软键盘输入时,屏幕闪烁,TextInput 输入框中文本不刷新,当输入法界面隐藏时才刷新。 3 问题原因 3.1 正常机制 软键
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> ¤tTopAbility,
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> ¤tTopAbility, 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> ¤tTopAbility, 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 知识分享
日志分析是开发中常用的问题查找技巧,当我们遇到疑难的问题是,采用日志分析法,一般可以快速的定位问题以及解决问题。
更多推荐


所有评论(0)