HarmonyOS 7 独立子窗不跟主窗退后台?API 26 的创建、去重与销毁边界
在鸿蒙电脑的自由多窗模式里,主窗切到后台后,一个辅助面板仍留在屏幕上。这不一定是窗口没关掉。HarmonyOS 7(API 26)的独立子窗本来就允许这种行为:它不跟随主窗的前后台切换,但会随主窗销毁。
真正容易出错的地方有两个:按钮连点创建出多扇窗;主窗切后台时按旧子窗的思路把面板销毁,回来后又创建一次。两种问题看起来都像“窗口状态乱了”,处理方式却不同。

先把能力边界说准
华为的子窗口开发指导于 2026 年 9 月 9 日更新,明确写了:从 API 26.0.0 开始,可以在 createSubWindowWithOptions() 的 SubWindowOptions 中将 zLevelAboveParentLoosened 设为 true,创建独立子窗。在自由窗口状态下,它不随主窗切换前后台,只随主窗销毁;主窗和子窗可以通过点击调整层级。
这句话有两个限制不能省略。第一,这不是应用外全局悬浮窗。第二,非自由窗口状态下,子窗仍只在主窗范围内显示。手机上做一个应用内提示层,未必需要子窗,普通组件或弹层通常更直接。
| 现象 | 应先判断什么 | 不应直接做什么 |
|---|---|---|
| 主窗切后台,辅助面板还在 | 是否启用了独立子窗,设备是否处于自由窗口状态 | 把它认作内存泄漏 |
| 连点按钮出现多扇辅助面板 | 创建 Promise 是否共享;是否仅在创建完成后才记录实例 | 每次点击都重新调用创建接口 |
| 子窗白屏 | 页面路径、setUIContent 与 showWindow 的先后及错误回调 | 只重试 showWindow,不检查加载失败 |
| 主窗销毁后面板消失 | 是否属于预期的父子窗生命周期 | 把独立子窗当成永久系统窗 |
案例一:连点“打开面板”,创建了三扇窗
复现条件是创建子窗需要一点时间,用户在第一次创建完成前连续点击。只用 if (this.window) 拦截不够,因为那时 window 还没赋值,三个调用都能进入 createSubWindowWithOptions()。
这里要锁住的是完整的创建流程,而不只是创建接口本身。创建、设置大小、加载页面、显示全部完成前,后续点击应复用同一个 opening Promise。加载失败时还要销毁已经创建的半成品,否则下一次重试更难判断当前窗口是哪一个。
下面是我抽出的窗口流程控制器。它不直接依赖系统 API,port 负责接入真正的 HarmonyOS 窗口;这样“是否重复创建”“失败是否清理”可以先做自动化测试。
export class IndependentSubwindowController {
constructor(port) {
this.port = port;
this.window = null;
this.opening = null;
this.closing = null;
}
async open() {
if (this.closing) await this.closing;
if (this.window) return this.port.show(this.window);
if (this.opening) return this.opening;
const opening = this.createAndShow();
this.opening = opening;
try {
await opening;
} finally {
if (this.opening === opening) this.opening = null;
}
}
async createAndShow() {
const win = await this.port.create();
try {
await this.port.configure(win);
await this.port.load(win);
await this.port.show(win);
this.window = win;
} catch (error) {
try {
await this.port.destroy(win);
} catch {
// 保留最初的加载错误,避免清理错误覆盖现场。
}
throw error;
}
}
async close() {
if (this.opening) {
try {
await this.opening;
} catch {
return;
}
}
if (this.closing) return this.closing;
if (!this.window) return;
const win = this.window;
this.window = null;
const closing = this.port.destroy(win);
this.closing = closing;
try {
await closing;
} finally {
if (this.closing === closing) this.closing = null;
}
}
}
port.create() 在 API 26 的接入点对应以下调用;页面路径、尺寸和位置要换成自己的工程值。华为文档建议在 showWindow() 前设置尺寸和位置。未设置尺寸时,自由窗口状态下可能以当前物理屏幕大小显示,非自由窗口状态下可能以主窗大小显示。
import { window } from '@kit.ArkUI';
const options: window.SubWindowOptions = {
title: 'IndependentPanel',
decorEnabled: true,
zLevelAboveParentLoosened: true
};
// windowStage 从 UIAbility 的 onWindowStageCreate 获取并传入。
const panel: window.Window = await windowStage.createSubWindowWithOptions(
'IndependentPanel', options
);
// 接下来按顺序完成 resize / moveWindowTo、setUIContent、showWindow,
// 各步都处理异步失败;不用“创建成功”代替“页面显示成功”。
这里没有把上面的控制器说成完整的 ArkTS 工程:窗口 API 的接入、页面注册和设备显示还需要在 API 26 SDK 工程中核验。能在本地复现的是流程控制逻辑,而不是系统绘制结果。
案例二:主窗切后台,面板“没有一起消失”
这次先别急着修。用自由窗口模式做四步验证:
- 打开主窗,创建独立子窗并确认两扇窗都可点击。
- 点击另一应用,让主窗退到后台,观察独立子窗是否仍能单独显示与获焦。
- 再点回主窗,确认没有额外创建第二个同名面板。
- 销毁主窗,确认独立子窗跟随销毁;若业务要求提前关闭,主动调用
destroyWindow(),不要仅等待主窗生命周期。
第二步和第四步是两种不同的生命周期事件。把“主窗不在前台”误写成“主窗已销毁”,就会破坏 API 26 新能力。反过来,若辅助面板包含敏感信息,产品确实希望退后台时隐藏,也应该明确做隐藏/关闭策略,并验证回前台时是否复用旧实例,而不是靠窗口默认行为碰运气。
我实际验证了哪部分
我把上面控制器的同一份代码用 Node.js 跑了三个测试:连续三次打开只创建一扇窗;创建过程中关闭只销毁一次;页面加载失败会清理半成品并允许重试。结果是 3 pass / 0 fail。这证明了流程控制器的并发和失败分支,不证明 API 26 SDK 已编译通过,也不证明鸿蒙电脑、平板的实际窗口层级完全一致。
要完成设备验收,至少保留这几条日志:创建调用次数、创建返回的窗口名、页面加载回调、显示回调、主动销毁原因,以及主窗前后台/销毁时间。截图要分别覆盖自由窗口和非自由窗口状态。只看一张“子窗还在”的截图,无法判断是预期独立层级还是重复创建。
哪种做法更合适
若需求只是应用内提示、确认框或轻量浮层,先选组件级弹层;它更容易跟随主窗布局,也不需要处理第二扇窗口。若需求是在鸿蒙电脑自由多窗里提供可独立切换层级的工具面板,才考虑 API 26 独立子窗。选择后用一个控制器统一创建、加载、显示、关闭与失败清理,页面按钮不要各自调用窗口 API。
以后排查“窗口关不掉”时,我会先问三个问题:是否真用了 zLevelAboveParentLoosened;当前是不是自由窗口状态;看到的是同一扇窗,还是连点后留下的多扇窗。这三个答案通常比先改 z-index 或重启页面更有用。
更多推荐



所有评论(0)