【共创稿事节】鸿蒙闪控窗与球全攻略:先过权限,再写窗口
先说一个场景
我想做一个盯盘小工具:行情页看完,退到桌面,还能有一块小窗一直挂在那儿,涨跌一眼就能看到;嫌它挡事,就缩成一个小球贴在屏幕边。
鸿蒙 7 给了两个正好对得上的能力:闪控窗(floatView,API 26)和 闪控球(floatingBall,API 20)。一个负责“挂着看”,一个负责“缩起来”。官方示例代码也不长,创建控制器、设页面、设尺寸、启动,四步。
但真正动手,第一步不是写窗口,是过权限。这两个能力都挂在受限开放权限上,普通签名的应用连装都装不上。我把这一路踩过的门整理出来,顺序是:先判断能力,再解决权限,最后才是窗口代码。

为什么照着示例写,第一步就卡住
第一次我把权限直接写进 module.json5,用默认调试签名构建,安装阶段就停了:
error: failed to install bundle.
code:9568289
error: install failed due to grant request permissions failed.
PermissionName: ohos.permission.FLOAT_VIEW
9568289 不是代码问题,是签名问题:权限声明了,但签名用的 Profile 里没有对应的 ACL 条目。受限权限不是“写一行就能申请”,它要先进 AGC 审批,再进 Profile。
把声明去掉,应用能装了,但运行时的门还在。申请权限没有弹窗,authResults 直接回 [2, 2];继续调 create(),返回 201 Permission verification failed;换成闪控球,startFloatingBall() 报的是同一类错误:
闪控球启动失败:201 Permission verification failed.
[FBWindow][startFloatingBall]msg: no permission.
消息传回页面,就是下面这一行。设备能力全是“支持”,权限两行是“未授权”,启动按钮按下去只会得到 201:

这就是四个校验点,任何一个不过,窗口都不会出现:

这里先把边界说清楚:本文的验证停在权限关,本机没有拿到 ACL,闪控窗和闪控球都没有真正浮起来。下面窗口部分的代码按官方接口写全,ACL 批下来可以直接跑;没有实拍的部分,我不会写成“已经跑通”。
闪控窗和闪控球,分别解决什么
两个能力长得像,定位不一样。
闪控窗是一块有内容的悬浮小窗,落在桌面或其他应用之上。它适合“持续看”:行情、直播间互动面板、倒计时。窗口可以拖动,拖的时候会自动避让状态栏、导航条和输入法;标题栏能关闭;在手机和平板上,还能拖到底部中间删除,或者收进侧边栏。
闪控球是一条更窄的悬浮条,标准高度 40vp,宽度在 70vp 到 98vp 之间,四种模板:静态(图标 + 标题)、普通文本(标题 + 内容)、强调文本(图标 + 标题 + 内容)、纯文本(只有标题)。它适合“随时点一下回到任务”:搜题、比价、抢单、翻译、盯盘。
两者可以绑定。绑定之后,点一下就在窗和球之间切换:想看细节就看窗,想让它让路就缩成球。绑定状态下,启动任一控制器会同时创建两个窗口,但同一时刻只显示一个;停止任一控制器,两个一起销毁。
这也是一套很清楚的取舍:窗负责信息密度,球负责不打扰。
两个能力的硬约束
先把限制列全,能省掉很多无效调试。
- 权限:闪控窗要
ohos.permission.FLOAT_VIEW,闪控球要ohos.permission.USE_FLOAT_BALL,绑定要两个都有。 - 权限级别:两个都是
system_basic的受限开放权限。闪控球是系统授权,批下来安装即生效;闪控窗是用户授权,批下来之后还要走一次用户同意。 - 场景白名单:闪控球只对题目搜索、账单记录、商品比价、抢单、翻译、金融类实时盯盘开放;闪控窗目前只对直播类应用的游戏直播和金融类实时盯盘开放。审核大约 3 个工作日。
- 数量:同一个应用只能有一个闪控窗,重复启动报
1300033;同一个应用只能有一个闪控球,同一台设备最多同时存在两个,超出时新的会替换最早的那个。 - 互斥:应用已经起了闪控球或画中画时,不能再起闪控窗,要先停掉。
- 前台:两个能力都只允许应用在前台时启动。
- 模型:只支持 Stage 模型,依赖
SystemCapability.Window.SessionManager。 - 尺寸:闪控窗的尺寸和宽高比要先用
getFloatViewLimits()取范围,设置超出范围时系统会自己修正,真实尺寸靠onRectChange()回调拿。
还有一条容易被忽略的申请条件:闪控球要求应用内有一个独立的开关,默认关闭,由用户主动打开;球里显示的内容要和 App 内实际功能对得上,不能拿来做营销、广告或引导付费。这不是文档里的客套话,审核会看。
开发步骤:从能力判断到窗口启动
第一步:判断系统能力和设备支持
不要一上来就 create()。先看系统能力,再看设备支持,两层都过了再往下。
import { floatView, floatingBall } from '@kit.ArkUI';
if (!canIUse('SystemCapability.Window.SessionManager')) {
// 这台设备没有窗口管理能力,直接走替代方案
}
const canFloatView: boolean = floatView.isFloatViewEnabled();
const canFloatBall: boolean = floatingBall.isFloatingBallEnabled();
console.info(`floatView=${canFloatView}, floatBall=${canFloatBall}`);
isFloatViewEnabled() 和 isFloatingBallEnabled() 是纯查询,不需要权限。设备支持、权限没批的情况下,这两个值也会是 true——所以它只能证明“这台机器有这个能力”,不能证明“你的应用能用”。
第二步:把权限场景对上
这一步不在代码里,在 AGC 里。先在 AGC 创建应用,提交受限权限申请,写清楚使用场景。闪控球和闪控窗的场景清单是固定的,不在清单里的应用,材料写得再漂亮也过不了。
审批通过后,用带 ACL 的调试 Profile 签名,或者用“试用调试 Profile”先跑起来——后者在提交申请后就能创建,有效期 5 天,适合赶在审核期间把代码调通。
这里有个容易误判的点:自动签名只支持部分 ACL 权限。我这次用默认自动签名,Profile 里 acls 是空的,所以安装阶段直接被拒。要调这两个能力,签名方式得先换掉。
第三步:声明权限,版本要分清
module.json5 里两个权限都要声明,原因写清楚。闪控窗是用户授权,reason 会出现在弹窗里;闪控球是系统授权,但 reason 和 usedScene 仍然要写,审核会看。
"requestPermissions": [
{
"name": "ohos.permission.FLOAT_VIEW",
"reason": "$string:reason_float_view",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.USE_FLOAT_BALL",
"reason": "$string:reason_float_ball",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
]
when 用 inuse,不要图省事写 always。悬浮窗是典型的“用的时候才需要”,写 always 只会在审核时多一条解释。
第四步:创建控制器,先设页面再设尺寸
闪控窗的页面不是普通路由页,它由控制器加载。页面路径写进 setUIContext(),尺寸从能力接口取,别自己拍一个宽高。
import { floatView, window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
const config: floatView.FloatViewConfiguration = {
context: ctx,
templateType: floatView.FloatViewTemplateType.ROUNDED_RECTANGLE
};
const controller: floatView.FloatViewController = await floatView.create(config);
controller.onStateChange((info: floatView.FloatViewStateChangeInfo) => {
if (info.state === floatView.FloatViewState.STOPPED) {
// 用户关了,或者被系统收回,记得把引用清掉
}
});
await controller.setUIContext('pages/FloatViewPage');
const limits: floatView.FloatViewLimits =
floatView.getFloatViewLimits(floatView.FloatViewTemplateType.ROUNDED_RECTANGLE);
const size: window.Size = {
width: limits.maxSize.width,
height: limits.maxSize.height
};
await controller.setWindowSize(size);
闪控窗只有两种模板:ROUNDED_RECTANGLE(圆角矩形)和 HORIZONTAL_BAR(水平条状)。盯盘用前者,一行状态用后者。
窗口里的页面要注意避让区。setUIContext() 加载的页面,落在避让区里的组件不响应手势。状态栏、导航条附近不要把可点的东西贴边放。
第五步:启动,并接住状态
await controller.start();
启动之后,用户可能拖它、关它、把它收进侧边栏,也可能切到别的应用让它被系统收回。这些都会走 onStateChange。收到 STOPPED 就把控制器置空,下次启动重新创建;否则很容易拿着一个已经失效的控制器继续调接口。
尺寸也可能变:分屏、折叠、横竖屏切换都会触发 onLimitsChange。建议在里面重新取一次 getFloatViewLimits(),需要的话再 setWindowSize()。
第六步:把窗和球绑在一起
只做“挂着看”,到这里就够了。想做“点一下缩成球”,再创建一个闪控球控制器,然后绑定。
const ballController = await floatingBall.create({ context: ctx });
await floatView.bind(controller, ballController, {
template: floatingBall.FloatingBallTemplate.EMPHATIC,
title: '沪深 300',
content: '+1.28%'
});
绑定之后有三条规则要记住:
- 启动任意一个,两个窗口都会创建,但只显示先启动的那个;
- 用户点击可以在窗和球之间切换,这时候点球不会触发
click回调; - 停止任意一个,两个一起销毁。
球的文字不要写太长。标题最多两行,内容一行,超出会被截断。球的标准高度只有 40vp,塞不下第二行正文。
第七步:停止和清理
await controller.stop();
await ballController.stopFloatingBall();
两个控制器都要停。绑定状态下停一个会带走另一个,但代码里显式写两次更稳,尤其是绑定失败、只起了一个的时候。
页面销毁时把 offStateChange()、offLimitsChange() 一起解掉,别让回调持有已经销毁的页面。
这套方案不适合什么
不是所有“想浮在上面”的需求都该走闪控窗。下面这几类,先想清楚再动手:
- 普通工具类应用:不在直播或盯盘场景里,ACL 基本过不了。别为了一个悬浮按钮去申请受限权限。
- 只想起一个悬浮按钮做快捷入口:用应用内的悬浮组件或桌面卡片更合适,不需要跨应用。
- 视频画中画:那是画中画,不是闪控窗。两者互斥,选一个。
- 需要在后台长期运行的任务:闪控窗只允许前台启动,后台保活是另一个问题,别混在一起。
- 必须覆盖所有应用、随时置顶:这属于系统级窗口,三方应用拿不到。
闪控球、闪控窗、画中画、实况窗怎么选
四种能力都能“把东西留在屏幕上”,差别在权限和形态。
| 能力 | 形态 | 权限门槛 | 适合 |
|---|---|---|---|
| 闪控窗 | 有内容的悬浮小窗 | FLOAT_VIEW,受限 + 用户授权 | 盯盘、直播互动面板 |
| 闪控球 | 40vp 高的悬浮条 | USE_FLOAT_BALL,受限 + 系统授权 | 搜题、比价、抢单、翻译 |
| 画中画 | 视频小窗 | 开放能力 | 视频播放、通话 |
| 实况窗 | 通知/锁屏/胶囊上的进度 | 开放能力 | 外卖、打车、计时 |
一句话:要跨应用持续显示“内容”,才轮到闪控窗或球;只是想让用户知道进度,实况窗更轻。
我的五条建议
- 先查能力,再查权限,最后写窗口。 顺序反了,你会在一个装不上的包上改三天布局。
- 把 9568289 和 201 分开看。 前者是签名 Profile 缺 ACL,后者是运行时没授权。两个错误码对应两个完全不同的修法。
- 场景先对上再写材料。 闪控球六个场景、闪控窗两个场景,写申请前先逐条对照自己是不是在里面。
- 调试用试用 Profile,别等发布。 提交 ACL 申请后就能建,5 天有效期,够把创建、绑定、切换、销毁跑一遍。
- 球和窗的取舍按“打扰程度”定。 用户在看内容时给窗,用户在做别的事时给球;能缩就别一直挂。
验证到哪一步
- 已验证:本机具备窗口管理能力,
isFloatViewEnabled()和isFloatingBallEnabled()都返回 true;声明受限权限后装包被签名检查拦下,报9568289;去掉声明后应用可以安装,运行时权限查询返回[2, 2];按下启动按钮,create()和startFloatingBall()都返回201 Permission verification failed。 - 未验证:ACL 审批通过后的权限授予;闪控窗真正创建、拖动、避让和切换;闪控球实际浮在屏幕上以及球内内容展示。
- 原因:受限权限要先进 AGC 审批,再写进签名 Profile,这次没有拿到 ACL。
按文章的规矩,能跑的写实测,没跑的写“没验证”。
总结
闪控窗和闪控球是鸿蒙 7 里很实用的两个窗口能力:一个负责信息密度,一个负责不打扰,绑在一起还能互相切换。但它们的门槛不在 API,在身份:受限权限、场景白名单、签名里的 ACL,三道都过了,示例代码才跑得起来。
我这次把流程走通到了“能准确说出卡在哪”,窗口本身没浮起来。等 ACL 批下来,第四步到第七步的代码可以直接接上;在那之前,把能力判断和降级路径写好,比硬凑一个跑不起来的悬浮窗更实际。
参考地址
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/float-view-guide
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/floatingball-guide
- https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-floatview
- https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-floatingball
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/restricted-permissions
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/declare-permissions-in-acl
- https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-appgallery-78
更多推荐




所有评论(0)