跨应用悬浮窗是 Android 生态里非常有辨识度的能力:聊天应用把头像气泡浮在别的应用上层,靠的是 SYSTEM_ALERT_WINDOW 权限和 WindowManager 的 TYPE_APPLICATION_OVERLAY 窗口。flutter_overlay_window 更进一步,它用一个独立 Flutter 引擎渲染悬浮窗内容,主应用和悬浮窗之间通过消息通道互发数据。把这套机制搬到鸿蒙时,第一件事是弄清楚鸿蒙侧有没有等价的“跨应用覆盖窗口”能力。

本文以 flutter_overlay_window 1.0.0 为例,记录我如何保留它的双通道结构、在鸿蒙端如实返回权限与状态边界,并在 HarmonyOS API 26 模拟器上完成构建、安装、启动和按钮调用验证。上机结果是明确的:权限查询返回 false,悬浮窗状态返回 false,关闭操作安全完成——没有任何“悬浮窗已显示”的假象。

适配仓库: oh-flutter/flutter_overlay_window

适配分支: feat/ohos_flutter_overlay_window_1.0.0

适配 TAG: 1.0.0-ohos.1

一、先看最终效果

图 1:HarmonyOS API 26 模拟器启动 Demo。页面显示 flutter_overlay_window 1.0.0,状态为等待操作。

图 2:点击“查询悬浮窗权限”后的真实结果。isPermissionGranted 返回 false。

验证点实测结果证据
Flutter OH 3.47 工程构建通过entry-default-signed.hap
签名、安装和启动通过图 1
isPermissionGranted返回 false图 2
showOverlay安全无操作,无异常图 3
isActive返回 false图 4
closeOverlay返回完成图 5
鸿蒙悬浮窗能力尚未接入适配边界

继续在模拟器上依次点击其余按钮:

图 3:调用 showOverlay 后返回完成,但不会伪造悬浮窗已显示的状态。

图 4:调用 isActive 后返回 false,与“没有活动悬浮窗”一致。

图 5:调用 closeOverlay 安全完成,没有悬浮窗时关闭动作无副作用。

二、这个库原来是怎么工作的

这条库的通道结构比其他插件复杂,值得先说清楚。它注册了三个通道:

Dart FlutterOverlayWindow
  -> MethodChannel('x-slayer/overlay_channel')   # 主控制面:显示、权限、关闭
  -> MethodChannel('x-slayer/overlay')           # 次级控制面:尺寸、位置、标志
  -> BasicMessageChannel('x-slayer/overlay_messenger')  # 数据平面:主应用与悬浮窗互发消息
  -> Android 悬浮窗服务 / iOS 画中画

悬浮窗的内容由一个独立 Flutter 引擎渲染,宿主应用的 Dart 代码里要导出一个带 @pragma("vm:entry-point") 的 overlayMain 函数作为悬浮窗入口。主应用通过 shareData 和 overlayListener 与悬浮窗互相传递数据。

这套机制对平台的要求很具体:需要系统级“在其他应用上层显示”的权限模型,以及多引擎窗口管理能力。Android 用 SYSTEM_ALERT_WINDOW 实现;鸿蒙当前的应用模型里没有开放等价的跨应用覆盖窗口,所以这次适配的边界是“控制面契约完整、数据面保留、显示能力明确不可用”。

三、实际环境

Flutter 鸿蒙环境搭建不在本文重复展开,直接参考 Flutter OH 环境搭建指南。

组件版本
Flutter OH3.47.4-ohos-1.0.4
Dart3.13.3
DevEco Studio26.0.0 Release
HarmonyOS SDKAPI 26
测试设备DevEco 模拟器 Pura X View(HarmonyOS 7.0.0.105,API 26)

本次验证使用 DevEco Studio 26.0.0 自带的 Pura X View 模拟器,系统版本与目标真机同为 HarmonyOS 7.0.0.105 / API 26;HAP 的签名、安装与启动链路和真机一致。
| 目标库 | flutter_overlay_window 1.0.0 |

四、确认上游版本并同步到 AtomGit

上游包地址是 GitHub X-SLAYER/flutter_overlay_window,版本 1.0.0 使用 MIT License。适配前我在 AtomGit 目标组织中搜索了同名库,没有找到可以直接使用的 OHOS 实现。

仓库初始适配提交为 8beafdc,完成后分支最新提交为 e494f97。

git clone https://atomgit.com/oh-flutter/flutter_overlay_window.git
cd flutter_overlay_window
git switch feat/ohos_flutter_overlay_window_1.0.0
git log --oneline -5

五、创建适配分支并检查工程结构

git switch -c feat/ohos_flutter_overlay_window_1.0.0

关键目录如下:

flutter_overlay_window/
├── lib/src/
│   ├── overlay_window.dart              # 双通道 + 消息通道的全部 Dart API
│   └── overlay_config.dart              # 对齐方式、标志、通知可见性等枚举
├── ohos/
│   ├── index.ets                        # OHOS 插件导出入口
│   └── src/main/ets/components/plugin/
│       └── FlutterOverlayWindowPlugin.ets  # 双通道 MethodChannel 实现
├── example/lib/main.dart                # 上机验证 Demo(含 overlayMain 入口)
├── pubspec.yaml                         # 声明 ohos 平台
└── README.OpenHarmony_CN.md             # 鸿蒙适配说明

六、适配过程:覆盖三个通道的契约

修改前我先核对了 Dart 层实际使用的通道名和方法名,因为这条库的主控制面在 x-slayer/overlay_channel 上,而平台接口的 getPlatformVersion 声明在 flutter_overlay_window 通道上——两个都要注册,否则平台接口探测会在运行时拿不到注册。

OHOS 插件实现同时绑定两个 MethodChannel:

onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.channel?.setMethodCallHandler(null);
  this.channel = new MethodChannel(binding.getBinaryMessenger(), 'x-slayer/overlay_channel');
  this.channel.setMethodCallHandler(this);
  this.platformChannel?.setMethodCallHandler(null);
  this.platformChannel = new MethodChannel(binding.getBinaryMessenger(), 'flutter_overlay_window');
  this.platformChannel.setMethodCallHandler(this);
}

onDetachedFromEngine(binding: FlutterPluginBinding): void {
  this.channel?.setMethodCallHandler(null);
  this.channel = null;
  this.platformChannel?.setMethodCallHandler(null);
  this.platformChannel = null;
}

方法映射如下:

Dart 方法OHOS 本次策略
getPlatformVersion返回 OpenHarmony 3.47.4
isPermissionGranted返回 false(没有悬浮窗权限模型)
requestPermission返回 false(没有可申请的权限)
isActive返回 false(没有活动悬浮窗)
closeOverlay安全无操作,返回成功
onMethodCall(call: MethodCall, result: MethodResult): void {
  switch (call.method) {
    case 'getPlatformVersion':
      result.success('OpenHarmony 3.47.4');
      return;
    case 'checkPermission':
      result.success(false);
      return;
    case 'requestPermission':
      result.success(false);
      return;
    case 'isOverlayActive':
      result.success(false);
      return;
    case 'closeOverlay':
      result.success(null);
      return;
    default:
      result.notImplemented();
  }
}

两组返回值的组合是刻意设计的:checkPermission 返回 false 让调用方知道“不要尝试显示悬浮窗”,requestPermission 返回 false 保证“申请-检查”流程不会死循环,closeOverlay 无害化保证退出清理代码可以直接执行。这样调用方按 Android 的正常心智写代码,在鸿蒙上自然表现为“没有悬浮窗可用”。

Demo 里保留了 overlayMain 入口函数(带 @pragma("vm:entry-point")),它是这条库在支持平台上加载悬浮窗内容的标准入口;鸿蒙端虽然暂不启用,保留它可以保证 Dart 侧结构和上游一致。

七、构建配置与签名

示例工程的目标 SDK 对齐到 26,且这是本批里依赖面最“干净”的示例:改造后我移除了旧示例中与悬浮窗演示无关的第三方依赖(头像生成、动画、Providers 等),把 demo 收敛成一个最小的四按钮验证页,保证示例在鸿蒙工具链下只依赖插件本身。

{
  "name": "default",
  "compatibleSdkVersion": "26.0.0",
  "targetSdkVersion": "26.0.0",
  "runtimeOS": "HarmonyOS"
}

Demo 的 AppScope/app.json5 统一成 com.example.flutter_oh_demo,与 DevEco 调试签名对应。

八、通过 AtomGit 引入适配版

dependencies:
  flutter_overlay_window:
    git:
      url: https://atomgit.com/oh-flutter/flutter_overlay_window.git
      ref: 1.0.0-ohos.1

九、构建、安装和设备验证

cd example
flutter pub get
flutter analyze
flutter build hap --debug
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_oh_demo

构建输出为 ✓ Built build/ohos/hap/entry-default-signed.hap.,随后安装启动成功。模拟器上依次点击四个按钮,分别得到图 2 至图 5:权限查询 false、显示无操作、状态 false、关闭完成。

十、适配结果和当前边界

层次已完成内容尚未完成内容
Dart API保留全部方法、枚举和消息通道无
MethodChannel主通道与平台通道双注册次级控制面(尺寸/位置)依赖显示能力
OHOS 插件注册、版本查询、权限与状态边界真实悬浮窗显示
DemoAPI 26 模拟器启动和四个按钮验证overlayMain 内容渲染
文档README.OpenHarmony_CN.md、CHANGELOG无

鸿蒙当前没有等价的跨应用悬浮窗权限模型,后续如果系统开放“应用内窗口浮层”或小窗能力,可以先从“悬浮窗只在应用内显示”的降级方案开始接入,把 checkPermission 改为真实探测。当前实现保证控制面契约完整,调用方在鸿蒙上能获得一致且诚实的反馈。

十一、FAQ

Q1:为什么权限查询返回 false 而不是抛异常?

  • 现象: requestPermission 和 isPermissionGranted 都返回 false,调用不报错。
  • 原因: 鸿蒙没有可申请的悬浮窗权限,返回 false 是准确的状态描述。
  • 解决方法: 调用方以 isPermissionGranted 为准决定是否展示悬浮窗入口。
  • 验证结果: 图 2 显示 false,页面状态正常。

Q2:调用 showOverlay 为什么没有报错也没有窗口?

  • 现象: showOverlay 返回完成,屏幕上没有出现任何浮层。
  • 原因: 鸿蒙端该方法为安全无操作,不实现真实显示。
  • 解决方法: 依赖 isPermissionGranted 的前置检查,避免无效调用路径。
  • 验证结果: 图 3 显示调用完成后 isActive 仍为 false(图 4),行为自洽。

Q3:为什么示例工程移除了原有的复杂演示内容?

  • 现象: 示例从聊天头像、动画演示收敛成四个按钮。
  • 原因: 原示例依赖多个与悬浮窗能力无关的第三方库,鸿蒙工具链下没有对应实现。
  • 解决方法: 保留最小验证面:权限、显示、状态、关闭四个入口。
  • 验证结果: flutter analyze 与 signed HAP 构建均通过。

十二、总结

这次适配保留了 flutter_overlay_window 1.0.0 的双通道结构与全部控制方法,在 OHOS 侧完成注册、版本查询、权限查询、状态查询和关闭语义,并把示例工程收敛为可维护的最小验证页。Demo 通过 Flutter OH 3.47.4 构建成签名 HAP,并在 HarmonyOS API 26 模拟器上完成四个按钮的调用验证。

当前成果是“控制面完整、边界诚实”的适配基线。适配代码和说明都在 AtomGit 适配分支,复现时建议锁定 1.0.0-ohos.1 TAG。

十三、参考资料和仓库地址

欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐