欢迎到 CPF-Flutter 鸿蒙社区 交流适配问题。本篇基于社区的 window_manager_plus 修复鸿蒙关闭拦截问题,修复源码已提交到我的个人仓库 abaoge/window_manager_plus,位于 fix/ohos-window-close 分支,对应提交为 845d15b9。

前言

我在开发 Flutter 鸿蒙电脑应用时,给表单页面接入了 window_manager_plus,想处理一个很常见的操作:表单填到一半,用户误点了窗口关闭按钮。这时候,我希望先弹出一个提示,让用户选择继续编辑还是放弃修改,避免一次误操作把刚填的内容丢掉。

不过,在鸿蒙电脑模拟器上测试该包的 1.0.5-ohos-1.0.0 版本时,我发现开启防关闭后,点系统标题栏的关闭按钮,窗口还是直接退出了,页面没有留下可供操作的确认框。这个看起来不大的问题,让我开始排查该包的鸿蒙关闭适配。

于是,我补齐了这部分实现,并用一个普通表单页面验证修复前后的效果。这篇文章就记录一下这次遇到的问题,以及我是怎样复现、定位和修复它的。

一、使用场景与痛点

image
图1 使用场景

我关心的是用户点下窗口 X 的那一刻:他是准备结束应用,还是只想暂时收起窗口?当前还有没有需要处理的内容?把这些问题放到桌面应用中,关闭前的判断会出现在很多地方。

常见情景示例场景关闭影响优化体验
编辑内容尚未保存表单填写未保存内容丢失检查修改,提示保存
退出还是最小化音视频播放播放停止提供退出/最小化选择
任务还在执行云盘上传、下载传输中断提示未完成任务
会话尚未结束视频、语音通话对话中断退出前二次确认
多个窗口或文档同时打开多窗口应用多窗口同时关闭确认关闭范围

表1 常见场景

从用户留存的角度,我更看重减少误退出带来的挫败感。比如注册资料、报名信息已经填了不少,误点关闭后如果只能重填,用户就可能放弃这次操作。让愿意继续的人回到原来的位置,保住已经付出的时间,是关闭确认的实际价值。用户明确选择退出时,也应该顺利退出;没有待处理内容时,则不必多加一道确认。

所以,这个功能在界面上可能只有一个弹窗,却关系到用户刚才的输入、正在做的任务,以及关闭按钮是否符合他的预期。我在本文选择普通表单,是为了把其中最容易复现的情况单独拿出来验证。

上面列的是关闭处理的适用场景。本篇补丁负责让应用在窗口退出前收到关闭请求,并有机会取消这次关闭。 保存内容、最小化、后台执行和会话管理仍需应用另行实现,其中最小化也不等于获得后台保活能力。下文只实测表单的关闭确认与输入保留,不把这些扩展场景算作已经验证的功能。

我接入 window_manager_plus,调用 setPreventClose(true),再用 isPreventClose() 回读,得到的也是 true。可原版在鸿蒙电脑模拟器上,点系统标题栏右上角的 X,窗口仍直接消失,页面没有留下可供操作的确认框。刚填过的文字也随着这次退出丢失。

虽然是小问题,但项目中确实需要这个能力:误点关闭后,还能回到刚才的表单,核对输入、继续填写。对已经接入 window_manager_plus 的应用,这次修复要让原有关闭接口能够拦住鸿蒙系统标题栏的关闭请求。

二、修复前后的效果

2.1 同一份表单的关闭对照

原版来自鸿蒙稳定 tag 1.0.5-ohos-1.0.0。我在电脑模拟器上对照原版与补丁版,分别检查系统标题栏 X、应用内关闭按钮和确认框的处理结果,具体记录见表 2。

Bug复现

图2 修复前效果

修复后

图3 修复后效果

这次原版与补丁都使用同一份普通表单、宿主配置和依赖锁文件,原生差异仍只落在包内的 WindowManagerPlus.ets。表 2 和图 8 对应的固定验证中,我把预填标题改为“表单填写示例2”;修复后动图则追加了“!!!”。两次记录都通过修改后的文字核对继续编辑时是否保留输入。

用户操作原版稳定 tag本次最小补丁
开启防关闭后点系统 X窗口关闭,原进程退出出现确认框,窗口与进程保留
选择继续编辑没有机会显示确认框已修改标题保留,仍是同一个进程
确认框出现后继续点 X原版首次已关闭,没有测试这一项共三次请求,只显示一个确认框
点击“关闭窗口”方法返回,窗口保留,但没有确认通知出现确认框,可以继续编辑
选择放弃并关闭本轮未测试这一决定路径窗口和进程退出,释放阶段没有重复通知

表2 关闭结果

在最开始未修复时,点击“关闭窗口”后,没有确认框;接着点系统标题栏 X,窗口消失。

修复后,选择继续编辑,修改后的标题和内容都保留。切到只读预览再返回编辑,输入仍在;最后选择放弃,窗口消失。

2.2 验证环境与固定版本

先把环境列明,免得读者把手机窗口行为、电脑镜像版本和编译 SDK 混在一起。本次使用的是电脑模拟器,截图中的桌面与系统标题栏也来自该模拟器。

项目本次使用值
DevEco Studio26.0.0.865
HarmonyOS SDK26.0.0.105,API 26,Release
Flutter 鸿蒙发行版3.41.9+ohos-1.0.2
Dart3.11.5
原版插件window_manager_plus 1.0.5,鸿蒙 tag 1.0.5-ohos-1.0.0
原版固定提交9c47e819888a93443f5e84e880a2723c274dc414
修复固定提交845d15b96727ddf343f3d365d5d2e9ad1a514b63,分支 fix/ohos-window-close
模拟器MateBook Pro ,设备类型 2in1
模拟器镜像版本7.0.0.107(SP8DEVC00E47R1P3)
设备实际回报的系统版本OpenHarmony-7.0.0.105
屏幕信息依赖screen_retriever 0.1.9,固定提交 24162d7e01d3d5f51f076302d830f5e77e34030f
构建方式unsigned debug HAP,安装到上述模拟器

表3 验证环境

三、原理与核心代码

3.1 为什么继续修这个已有的包

image

图4 社区仓库

动手之前,我查了 oh-Flutter、CPF-Flutter 和 hxa-flutter 的仓库,并读了命中候选的鸿蒙源码。已有的 flutter_window_close 提供了鸿蒙实现,当前核对到的鸿蒙 tag 是 1.3.0-ohos-1.0.0-beta.1,属于 Beta,其源码已经接入 windowWillClose。不过,我日常使用的是 window_manager_plus,这次没有运行这个替代库,只核对了相关源码。

所以,我继续修复 window_manager_plus,是为了修好它已经公开的关闭接口。对已经依赖这个包的应用,补丁可以保留原有 Dart 调用方式。

还有一个容易写过头的地方。通用 README 和 Dart 接口注释描述了关闭前确认的用法,但鸿蒙专项能力表没有单独承诺这项系统按钮行为。我不能把通用描述改写成“鸿蒙功能清单明确宣称已经完成”。我的修复依据是已有接口、实际鸿蒙调用链和原版复现结果,三者需要一起看。

为了排除“旧 tag 已经修好”的情况,我还检查了默认分支和更新开发分支。三者在其他窗口方法上有差异,但都没有注册系统预关闭事件,关闭通知仍出现在释放路径。因此我将固定稳定版作为基线,同时把查到的当前实现情况写进了选题记录。

3.2 问题出在关闭请求抵达的时间

我最先检查的是两个入口。第一个入口是页面按钮调用 WindowManagerPlus.current.close(),它通过 MethodChannel 进入插件的 close 分支;第二个入口是用户点击系统标题栏 X,它由鸿蒙窗口系统处理。两个入口看起来都在关窗口,却不会自动经过同一段代码。

原版 setPreventClose 只把传入值存到 isPreventClose 字段,随后返回成功。原版 close 分支会读这个字段:允许关闭时调用销毁;防关闭开启时返回 false。可是这个分支既没有通知 Dart,也没有为系统按钮注册监听。于是页面按钮只是“不关了”,系统按钮则完全没有经过这个判断。

这解释了两个表面相反的现象。应用内按钮按了没有反应,不能算关闭确认正常,因为页面没有收到请求;系统 X 一点就退出,也不能靠再设置一次布尔值修好,因为系统没有订阅者可以询问。回读为 true 只能证明字段已被写入,不能替代一次真实系统操作。

我还区分了方法返回与用户决定。Dart 的 close() 返回类型是 Future<void>,等待它完成,只说明这次通道调用已经结束,不能据此判断用户选择了放弃。若在按钮回调的 await 后立即清空备注,即便窗口成功留下,输入也会被应用自己的代码删掉。退出决定应留在确认回调里处理,页面按钮只负责提出请求。

原版确实有 onWindowClose,但我沿调用链找到的是:宿主进入 onWindowStageWillDestroy,调用 releaseWindowManager,再由管理对象的 release() 发出 close 事件。这个时候系统已经走到窗口销毁流程。Dart 收到的更像释放通知,已经无法把用户停在输入页上等确认。

carbon 1

图5 日志节选

日志把时间关系保留下来了:原版先出现 releaseWindowManager:0,紧接着是 onWindowClose,然后才看到 Dart 的 request=1。即使日志里的 mounted=true,也不表示窗口还拥有取消销毁的机会。Widget 对象是否仍挂载,与系统是否已经开始关闭,是两个不同层面的状态。

所以我没有往 Dart 多加一次延时,也没有让确认框提前常驻。要修的地方是鸿蒙的预关闭事件:在窗口真正消失之前接住请求,并向系统返回本次是否继续关闭。

3.3 宿主装配与防关闭初始化

这个包的鸿蒙实现依赖宿主提供 WindowStage。我沿用原包 Example 的接法:EntryAbility 登记实际创建的 stage,Dart 初始化时由插件取得主窗口。下面这张流程示意图把宿主、页面监听和防关闭设置串起来;如果连主窗口都没取得,就还没走到本篇要修复的关闭监听。

宿主登记 WindowStage,Dart 初始化并注册监听,再开启与回读防关闭状态的流程

图6 初始化流程

下面是宿主文件 EntryAbility.ets 中的完整创建回调,它属于 EntryAbility 类。我只展示与装配有关的完整函数,不展开该包的多窗口功能。

onWindowStageCreate(windowStage: window.WindowStage): void {
  super.onWindowStageCreate(windowStage);
  if (this.windowName) {
    // 把宿主实际创建的 stage 交给原包,不能另造一个窗口对象。
    WindowStageManager.addStage(this.windowName, windowStage);
  }
}

本次使用默认窗口 ID 0。宿主还负责 Flutter 插件注册、入口传参与生命周期清理。页面监听要在开启防关闭前接好,否则原生即使取消了关闭,Dart 也可能没有接收事件的对象。

我在配置期间禁用页面的“关闭窗口”按钮,回读成功后才开放;失败则显示错误与重试。guard=true 只说明保护状态已写入,能否拦住系统 X 仍要实际点击。 图中的错误分支对应下面的 configure(),不涵盖 main() 中 ensureInitialized 的所有失败。

Dart 页面 lib/main.dart 使用下面这个完整函数,代码与当前 Example 一致。

Future<void> configure() async {
  try {
    // 使用原库公开接口,不让页面自己接管鸿蒙窗口事件。
    await WindowManagerPlus.current.setPreventClose(true);
    final enabled = await WindowManagerPlus.current.isPreventClose();
    debugPrint('CLOSE_PROBE guard=$enabled');
    if (mounted) {
      setState(() {
        ready = enabled;
        errorMessage = enabled ? null : '关闭保护未能开启,请先保留你的修改,再重试。';
      });
    }
  } catch (error) {
    debugPrint('CLOSE_PROBE setup_failed=${error.runtimeType}');
    if (mounted) {
      setState(() => errorMessage = '窗口关闭保护初始化失败,请重试。');
    }
  }
}

本例只使用应用自己的窗口事件,没有新增文件访问、全局输入监听或预退出权限。表单内容只保存在内存里,没有存盘操作,也就没有文件授权步骤。

3.4 原生先回答系统:这次关闭是否取消

我选择的公开接口是 window.Window.on('windowWillClose')。它从 API 15 起提供,回调返回 Promise<boolean>。这个布尔值的方向非常容易看反:resolve true 表示取消本次关闭,resolve false 或 reject 表示继续关闭。

Dart 弹窗里的 true 却表示“用户同意放弃并退出”,不能原样传给系统。我把两者分开:原生按已有防关闭状态回答系统,Dart 异步处理用户选择。下面的泳道图只画本例已开启保护时,点击系统 X 的路径。

原生读取防关闭快照、异步通知 Dart 并取消系统关闭;Dart 让用户选择继续编辑或主动销毁窗口

图7 关闭处理流程

补丁保存一个固定的回调成员,后续注册和注销都使用同一个函数引用。下面是完整成员初始化与两个核心函数;它们位于 WindowManagerPlus.ets 的类体中。

private hasWindowWillCloseListener: boolean = false;

private windowWillCloseCallback: () => Promise<boolean> = (): Promise<boolean> => {
  // reject 会被系统视为继续关闭,因此这里不等待 Dart 弹窗的决定。
  return Promise.resolve(this.handleCloseRequest());
};

private registerWindowCloseListener(): void {
  if (this.hasWindowWillCloseListener) {
    return;
  }
  const currentWindow = this.windowClass;
  if (currentWindow === null) {
    throw new Error('Cannot prevent close: window is not available');
  }
  // 注册成功之后才记录状态,供后续设置与注销复用。
  currentWindow.on('windowWillClose', this.windowWillCloseCallback);
  this.hasWindowWillCloseListener = true;
}

private handleCloseRequest(): boolean {
  const preventClose = this.isPreventClose;
  try {
    // 系统 X 与应用 close() 共用通知,弹窗内容仍由 Dart 决定。
    this.onWindowClose();
  } catch (exception) {
    const error = exception as BusinessError;
    console.error(`Failed to notify close request. Cause code: ${error.code}, message: ${error.message}`);
  }
  // 通知失败也不能把已经开启的拦截变成放行。
  return preventClose;
}

对照图和代码,我主要检查三个地方:

  • 先取值,再通知,再返回。 handleCloseRequest 保存当前防关闭快照,发送事件后立即返回,不等待用户选择。窗口没有被这次请求关闭,继续编辑只需收起弹窗,输入控制器也不必重建。
  • 确认退出后调用 destroy()。 close() 仍是一次允许拦截的关闭请求。用户已选择放弃,再调用它会重复确认;明确退出时才走 destroy()。应用内的 close() 也复用请求处理,但它不经过图中的系统 Promise<boolean> 回调。
  • 通知失败不自动放行。 catch 保留原生错误上下文,返回值仍是先前的快照。它只处理本次通知同步抛出的异常;Dart 没有监听、事件无法抵达等情况,不能算作已经兜底,应用也不会因此自动显示确认框。

注册过程还有一个区别:窗口初始化时,首次注册监听的异常只记录日志;调用 setPreventClose(true) 时会再次检查,必要时重试注册。只有注册未抛错才写入保护状态,否则把 SDK 错误交给 Dart。因此,ensureInitialized 返回本身不能证明监听已经接好。

设备边界: windowWillClose 受自由窗口状态限制,不适用时可能不报错却不生效。我仍以电脑模拟器中实际点击系统 X 的结果为准;注册失败和不支持设备的路径尚未做运行对照。

3.5 Dart 负责让用户决定

收到 close 事件之后,页面要做的事情很直接:如果没有确认框,就显示一个;用户选择继续编辑,不调用退出;选择放弃,再主动销毁窗口。下面是完整回调,和本次原版、补丁版使用的是同一份 Dart 逻辑。


void onWindowClose([int? windowId]) async {
  requests += 1;
  debugPrint('CLOSE_PROBE request=$requests mounted=$mounted');
  // 原生可能重复通知;页面保证同一时刻只有一个确认框。
  if (!mounted || dialogOpen) return;
  dialogOpen = true;
  try {
    final close = await showDialog<bool>(
      context: context,
      barrierDismissible: false,
      barrierColor: const Color(0x63203229),
      builder: (context) => const CloseFormDialog(),
    );
    debugPrint('CLOSE_PROBE decision=${close == true ? "discard" : "keep"}');
    // 继续编辑只收起弹窗,只有明确放弃才主动销毁窗口。
    if (close == true) await WindowManagerPlus.current.destroy();
  } catch (error) {
    debugPrint('CLOSE_PROBE close_failed=${error.runtimeType}');
    if (mounted) {
      setState(() => errorMessage = '关闭处理失败,当前修改仍保留,请重试。');
    }
  } finally {
    dialogOpen = false;
  }
}

CloseFormDialog 是同一文件中的完整 Widget。它询问“放弃未保存的内容?”,明确说明关闭后本次填写的内容会丢失,把“继续编辑”作为主操作,“放弃并关闭”作为退出选择;两个按钮分别通过 Navigator.pop 返回 false 和 true。预览状态下关闭也使用这个确认框,切换预览并没有提交或保存表单。

我把 dialogOpen 放在第一次 await 之前设置。否则用户连续点 X,前一个异步回调还没显示完弹窗,下一个回调就可能继续创建新弹窗。这里使用的是页面本地状态,和“到底收到多少次请求”分开:计数照常增加,重复弹窗才被挡住,日志因而能看见真实输入。

本轮连续点击三次,日志出现 request 一到三,界面只有一个确认框。这个结果应归因于原生拦截与页面已有去重共同工作。因为两版 Dart 完全相同,我没有把 dialogOpen 写成补丁新增加的功能,更没有把它塞进窗口插件,让库去判断某个业务弹窗是否打开。

重复请求也没有替用户累计成一次“强制同意”。页面会记录它们,但已有确认框还在时直接返回,不追加新的退出任务。等用户选择继续编辑,finally 才把去重状态复位,下一次独立关闭请求就能再次询问。如果把这个标志永久保持为 true,第一次取消后再点关闭便会没有任何反馈,所以它的复位时机同样属于页面职责。

选择继续编辑后,标题仍是“表单填写示例2”,内容也保留,进程仍为 5287,窗口没有关闭重建。重新启动会加载预填的“表单填写示例”;这是 Example 的初始值,不能算恢复了本轮修改。

选择继续编辑后,已修改的表单标题保留在原窗口中

图8 继续编辑

Example 从预填示例开始,输入只保留在当前窗口。这里统一启用防关闭,并不根据字段是否为空、是否恢复原文来开关保护。状态文字说明用户正在做什么,实际退出仍交给同一套插件接口。业务应用可以根据自己的保存记录决定何时提示,但这不属于本次鸿蒙补丁。

3.6 release 只做清理,不再发一次关闭请求

接上预关闭事件之后,原版 release() 中那次 onWindowClose() 就必须重新审视。用户已经在确认框里选择放弃,Dart 也已经调用销毁,如果释放时再发一条 close,请求又回到页面,就可能重复弹窗或者在正在消失的界面上处理新的确认。

我删除的就是这次释放阶段的通知。它不该承担“请用户决定是否退出”的职责,因为释放已经是决定之后的清理。下面是修改后的完整函数,原包其他窗口监听的清理逻辑保留,新增关闭监听则按实例的回调引用注销。

public release() {
  // release 已处于销毁清理,不重复发出可以取消的 close 请求。
  this.isPreventClose = false;
  if (this.hasWindowWillCloseListener) {
    try {
      // 只注销本实例添加的关闭监听,不移除宿主的同类监听。
      this.windowClass?.off('windowWillClose', this.windowWillCloseCallback);
      this.hasWindowWillCloseListener = false;
    } catch (exception) {
      const error = exception as BusinessError;
      console.error(`Failed to unregister close callback. Cause code: ${error.code}, message: ${error.message}`);
    }
  }
  this.channel?.setMethodCallHandler(null);
  try {
    this.windowClass?.off('windowEvent');
    this.windowClass?.off('windowStatusChange');
    this.windowClass?.off('windowRectChange');
  } catch (exception) {
    console.error(`Failed to unregister callback. Cause code: ${exception.code}, message: ${exception.message}`);
  }
}

对应的 Dart 页面在 dispose 中移除监听器、释放文本控制器,这样页面结束后不会继续把自己留在事件接收列表里。原生与 Dart 各自清理自己注册的对象,不依赖“进程反正马上会退出”来掩盖生命周期问题。

补丁日志的末尾是用户决定 discard,随后出现 releaseWindowManager:0,之后没有额外的 CLOSE_PROBE request。我还确认窗口消失、PID 查询为空,才把放弃退出记为通过。只看方法返回成功,无法证明窗口已经退出;只看桌面空了,也无法证明没有多余回调。

这次变化也会影响错误使用旧事件时机的代码。如果某个消费方一直把 close 当成“销毁完成通知”,现在应重新检查用途。它在公开接口中的含义是关闭请求,应用不能把用户取消的请求记成已经退出,更不能在每次请求到达时立刻丢弃输入。

四、踩坑与边界条件

4.1 复现中确认的问题与接入时要注意的地方

回过头看,这次最容易误判的是把接口回执当成了用户操作结果。我把已复现的问题和代码接入时的注意点分开列,后者没有另做故障注入,不能当成新增实测。

表4 接入注意

类型容易误判的地方我的处理方式
原版已复现isPreventClose() 返回 true,就认为系统 X 已受控必须实际点系统标题栏 X,核对窗口、弹窗和进程
原版已复现应用内 close() 已返回,就认为用户已收到确认同时检查 Dart 是否收到请求,页面是否留下可操作的确认框
接入注意把系统回调和弹窗结果中的 true 当成同一个意思系统 true 是取消关闭;本例弹窗 true 是明确放弃,分别处理
接入注意确认放弃后再调用 close()放弃分支调用 destroy(),避免再次提出同一个关闭请求
补丁已验证连续点 X 时,只看请求数,不看弹窗数保留请求日志,用页面的 dialogOpen 去重;本轮三次请求只有一个框
接入注意看到重启后又出现预填内容,就当成恢复了草稿区分固定预填内容与本轮编辑结果;当前 Example 没有存盘

4.2 已测范围与未验证的条件

这次实测覆盖的是电脑自由窗口下的主窗口与系统标题栏 X。官方接口说明,这个预关闭监听并不会覆盖所有关闭方式。系统强杀、崩溃和断电没有机会给页面慢慢询问,Dock、托盘或快捷键等入口也不能从本轮结果直接推断。

我没有扩大到 UIAbility 的预退出钩子。那是另一个层级,涉及自己的权限、设备条件、回调优先级及超时约束;把它与窗口关闭监听同时接入,需要重新梳理谁先处理退出。本篇只修现有库的一个明确入口,避免用更大范围的退出接管掩盖原本的缺口。

表5 验证边界

范围本文可以确认的内容
继续编辑本轮同一进程内,原输入保留,页面可继续使用
放弃并关闭本轮窗口和进程退出,释放阶段没有重复通知
关闭请求重复到达本轮三次请求,页面已有去重只显示一个框
防关闭设为 false实现保留该状态分支,尚未做运行对照
注册失败、能力不支持有错误传递逻辑,尚未做对应环境实测
多窗口、全屏、真机本轮未验证
保存与恢复Example 未实现保存业务,不能据此声称防丢数据或重启恢复

五、接入与复用建议

5.1 接入自己的项目

我在接入时主要检查三件事,保存规则和确认文案仍由应用决定:

  1. 初始化顺序。 按第 3.3 节装配宿主,先注册 Dart 监听,再调用 setPreventClose(true) 并回读;失败时显示错误,不把保护状态标为已开启。
  2. 退出决定。 继续编辑只收起弹窗,明确放弃才调用 destroy()。需要保存时,先保存成功再退出,失败则保留窗口。
  3. 资源清理。 页面释放时移除监听器和文本控制器,避免已结束的页面继续接收事件。

5.2 取得源码与验收步骤

我把修复放在个人仓库的 fix/ohos-window-close 分支。复现时固定到本文对应的提交:

git clone --branch fix/ohos-window-close https://atomgit.com/abaoge/window_manager_plus.git
cd window_manager_plus
git checkout --detach 845d15b96727ddf343f3d365d5d2e9ad1a514b63

已有项目可将插件的 Git 依赖指向该仓库和提交,保留原有依赖锁文件。仓库保留上游 example/,不含截图中的普通表单验证工程;补丁尚未合入原社区仓库,也未发布新的包版本。

接入后,我按下面四步验收,系统标题栏 X 和应用内按钮都要检查:

  1. 在 HiLog 中确认 guard=true,修改表单标题。
  2. 连续点三次系统 X,确认只出现一个弹窗;选择继续编辑,检查输入和 PID 均未改变。
  3. 点击应用内“关闭窗口”,确认仍可取消;再发起关闭并选择放弃。
  4. 确认窗口消失、PID 查询为空,释放阶段没有新增关闭请求。

本次两版表单均完成了无签名 debug HAP 构建、模拟器运行和页面范围的 flutter analyze lib 检查;整仓静态检查未全部通过。无签名构建仅用于上述模拟器,真机需另行配置签名。

下面两图来自早期最小 Probe。普通表单沿用其 2in1、multiton 和空权限配置;日志用于说明原版先释放、后通知的顺序,不代表新版 UI 或进程 PID。

早期最小Probe的module.json5配置,窗口类型、启动模式和权限列表由普通表单沿用

图9 早期宿主配置

原版 HiLog 节选显示防关闭为 true,释放窗口后才发出 close 通知

图10 早期关闭日志

小结

现在可以说清楚这次升级的用途:已经使用 window_manager_plus 的鸿蒙电脑编辑器,开启防关闭后,用户点击系统 X,会先进入原有 Dart 确认逻辑。用户选择继续编辑,窗口和内存输入留下;选择放弃,再主动退出。应用内关闭按钮也走同一套请求通知。

真正需要改的鸿蒙部分,是系统预关闭事件的注册、布尔返回值、通道通知时机和释放清理。确认框本来就在 Flutter 页面里,修复让它终于有机会在正确的时候出现。原版与补丁的同条件结果已经验证了这一点,剩余设备状态和失败路径则继续按未测项保留。

参考资料

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐