1 关键字

CustomDialogController;Dialog;

2 问题描述

问题现象:在Launcher桌面中,拖动应用图标后,长按应用图标,点击卸载菜单弹出确认按钮,并没有弹出卸载对话框。

api版本:3.1 Release

硬件环境:rk3568

3 问题原因

3.1 正常机制

TS调用 CustomDialogController 中的 open 接口,能正常的弹出 Dialog 对话框。

3.2 异常机制

Launcher桌面的对话框在弹起后,由于某种异常操作,触发的对话框关闭的操作,调用了 close 方法,CustomDialogController 调用 close 接口后,通过 NAPI 机制调用 js_custom_dialog_controller.cpp 文件中的 JsCloseDialog 方法,JsCloseDialog 方法内部调用 CloseDialog 方法,方法内部将 pending 设置为 true。CloseDialog 方法内部执行 executor.PostTask,task 内部 进入if (!lastStack || !dialogComponent) 分支内部,方法 return, 导致 pending 状态没有复原。

void JSCustomDialogController::CloseDialog()
{
   ......
    pending_ = true;
    auto task = [lastStack, dialogComponent = dialogComponent_, this]() {
        if (!lastStack || !dialogComponent) {
            return;
        }
       ......
    };
......
}

在后续的操作中,点击卸载菜单弹出确认按钮,CustomDialogController 调用 open 接口,通过 NAPI 机制调用 js_custom_dialog_controller.cpp 文件中的 JsOpenDialog 方法,JsOpenDialog 方法内部调用 ShowDialog 方法,ShowDialog 方法在202行 判断 pending 状态,pending 为 true,执行逻辑进入 ShowDialog 内部,ShowDialog 方法 内部 return ,导致后续弹出 Dialog 对话框的代码没有调用。

void JSCustomDialogController::ShowDialog()
{
    LOGI("JSCustomDialogController(ShowDialog)");
......
    if (pending_) {
        dialogOperation_.emplace_back(DialogOperation::DIALOG_OPEN);
        return;
    }
......
}

4 解决方案

应用层解决方案

每次弹出对话框时,重新构造 CustomDialogController 对象。(治标不治本,不建议)

let customDialogController = null
function showDialog() {
    customDialogController = new CustomDialogController();
    customDialogController.open()
}

Native解决方案

在 ShowDialog 和 CloseDialog 方法中,分别添加方法异常后的 pending 状态复原处理。

  • ShowDialog

void JSCustomDialogController::ShowDialog()
{
    LOGI("JSCustomDialogController(ShowDialog)");
    ......
    auto stack = context->GetLastStack();
    auto result = false;
    if (stack) {
        LOGE("JSCustomDialogController(PostTask) stack");
        result =  executor->PostTask(task, TaskExecutor::TaskType::UI);
    } else {
        LOGE("JSCustomDialogController(PostDelayedTask) !!!!stack");
        result =  executor->PostDelayedTask(task, TaskExecutor::TaskType::UI, DELAY_TIME_FOR_STACK);
    }
    if (!result) {
        LOGW("JSCustomDialogController(ShowDialog) fail to post task, reset pending status");
        pending_ = false;
    }
}
  • CloseDialog

void JSCustomDialogController::CloseDialog()
{
    LOGI("JSCustomDialogController(CloseDialog)");
   ......
    pending_ = true;
    auto task = [lastStack, dialogComponent = dialogComponent_, this]() {
        if (!lastStack || !dialogComponent) {
            LOGI("JSCustomDialogController(CloseDialog) stack or dialog is null.");
            this->NotifyDialogOperation(DialogOperation::DIALOG_CLOSE);
            return;
        }
        ......
    };
    auto result =  executor->PostTask(task, TaskExecutor::TaskType::UI);
    if (!result) {
        LOGW("JSCustomDialogController(CloseDialog) fail to post task, reset pending status");
        pending_ = false;
    }
}

源文件路径: foundation\ace\ace_engine\frameworks\bridge\declarative_frontend\jsview\dialog\js_custom_dialog_controller.cpp

5 定位过程

在点击打开卸载弹窗后,弹窗没有弹起,查看系统日志。

# C:\Users\xxx>hdc_std shell
# hilog | grep JSCustomDialogController
 03900/Ace: [js_custom_dialog_controller.cpp(ShowDialog)-(1000000)] JSCustomDialogController(ShowDialog)
​

通过日志发现,'ShowDialog' 已经打印,说明 js_custom_dialog_controller.cpp 内部调用了 ShowDialog 方法,但却没有打开弹窗的日志,结合代码,猜测应该是执行到 ShowDialog 方法内部的某一个 return 的分支。为没有添加日志的 return 分支添加log日志,再次查看系统日志。ShowDialog 方法执行到 pending 为true的分支。

# C:\Users\xxx>hdc_std shell
# hilog | grep JSCustomDialogController
 03900/Ace: [js_custom_dialog_controller.cpp(ShowDialog)-(1000000)] JSCustomDialogController(ShowDialog)
03900/Ace: [js_custom_dialog_controller.cpp(ShowDialog)-(1000000)] JSCustomDialogController(ShowDialog) current state is pending.

查看 ShowDialog 和 CloseDialog 方法内部代码,在设置pending 为 true 后,存在部分return 但是没有复原pending 状态的异常分支,在异常分支添加log 后,再次复现问题,添加的log 正常打印,问题得到定位。

6 知识分享

CustomDialogController 是 Openharmony ArkUi 中提供的全局基础弹窗,可以基于该自定义弹窗来实现提示消息弹窗、确认弹窗、输入弹窗的UI组件封装。

Logo

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

更多推荐