Flutter 鸿蒙插件适配实战:给 screen_security 1.1.2 补上截图与录屏防护

适配仓库: https://atomgit.com/oh-flutter/screen_security
适配分支:
ohos-adaptation
这次我适配的是 screen_security。它原本已经支持 Android 和 iOS,作用也很好理解:进入登录、支付、证件展示这类敏感页面后,应用可以暂时禁止常规截图和录屏;离开页面时再恢复。Android 端用的是 FLAG_SECURE,iOS 端使用安全文本渲染层,但项目里没有鸿蒙实现。
我没有另起炉灶改 Dart API,而是保留原来的 enable() 和 disable(),只在插件底层增加 OHOS 平台代码。最终结果是:关闭防护时页面可以正常被抓取,开启后系统抓到的应用区域会变成黑色;窗口服务里的 isPrivacyMode 也会从 false 变成 true。这两个结果放在一起,才能说明不是按钮只改了页面文案,而是系统窗口的隐私模式真的生效了。
本文的真机证据覆盖系统截图和窗口隐私状态。OHOS 实现使用系统窗口隐私模式承接屏幕捕获防护,但本次没有单独留存系统录屏文件,因此不把“录屏已通过真机验证”写进结论。
一、最终运行效果
先看真机结果。防护关闭时,系统截图可以完整抓取 Flutter 页面:

图 1:华为畅享 90 Pro Max、HarmonyOS 7.0.0.105 上,防护关闭时可以正常抓取示例页面。
点击 Enable Security 后,系统抓图中的应用窗口变为黑色;状态栏仍然可见,因为它属于系统窗口,不属于当前 Flutter 应用窗口:

图 2:隐私模式开启后,系统抓图自动遮黑应用窗口内容。
为了排除黑屏或渲染失败,我又对比了同一应用窗口的系统属性。调用前后,isPrivacyMode 由 false 变为 true;调用 disable() 后再恢复为 false。

图 3:同一窗口在调用前后发生 false -> true 的状态变化,关闭防护后恢复为 false。
| 验证点 | 实测结果 | 证据 |
|---|---|---|
| 签名 HAP 安装与启动 | 通过,Demo 在真机前台正常运行 | 图 1 |
enable() | isPrivacyMode: false -> true,系统抓图中应用区域变黑 | 图 2、图 3 |
disable() | isPrivacyMode: true -> false,页面恢复正常抓取 | 图 1、图 3 |
| Dart 通道与异常透传 | 26 项测试通过 | 图 7 |
| 系统录屏 | 本次未单独留存录屏文件,不列为实测通过项 | 验证边界 |
二、成果速览
| 项目 | 内容 |
|---|---|
| 库名称与版本 | screen_security 1.1.2 |
| 上游基线 | TAG v1.1.2,提交 1700cf3880ecc4e3c63008caa91da79c48ede78a |
| 开源许可证 | MIT |
| 适配仓库 | oh-flutter/screen_security |
| 适配分支 | ohos-adaptation,属于统一命名规范实施前创建的历史分支 |
| 适配 TAG | 尚未发布 |
| 适配提交 | 477879990d220cb517986f85a0f8e9e7fd985399,feat: add OpenHarmony screen security support |
| 新增 OHOS 能力 | 插件注册、Ability 生命周期、窗口隐私模式和权限声明 |
| 保持不变的接口 | ScreenSecurity.enable()、disable() 和 kidpech_screen_security 通道 |
| Flutter OH 实测版本 | 3.41.10-ohos-1.0.1,核对时的最新稳定标签,不是版本号最大的预览标签 |
| 自动化验证 | flutter analyze 无问题,26 项 Dart 测试通过;暂无覆盖插件核心行为的 ArkTS 自动化测试 |
| 构建验证 | unsigned HAP 构建成功 |
| 真机结论 | 截图遮黑与 isPrivacyMode: false -> true -> false 通过;录屏未单独留证 |

图 4:Dart API、MethodChannel、ArkTS 插件和系统窗口隐私模式之间的调用关系。
三、先确认这个库值得适配
动手之前,我先在 Flutter 鸿蒙三方库适配清单中做了去重。2026 年 9 月 7 日检查时,screen_security 只出现在待适配清单里,没有出现在“适配中”和“已适配”清单;oh-flutter 组织下也没有同名仓库。这一步很重要,因为功能实现完才发现别人已经提交,前面的时间基本就白花了。
原项目的 Dart 层已经把接口封装好了,调用方只需要写:
import 'package:screen_security/screen_security.dart';
final screenSecurity = ScreenSecurity();
Future<void> protectSensitiveContent() async {
await screenSecurity.enable();
}
Future<void> restoreNormalCapture() async {
await screenSecurity.disable();
}
再往下看,默认实现通过名为 kidpech_screen_security 的 MethodChannel 调用原生端,方法名分别是 enableScreenSecurity 和 disableScreenSecurity。所以鸿蒙端真正要补的是三件事:注册同名通道、接住这两个方法、把开关状态交给鸿蒙窗口 API。
四、本次实测环境
| 项目 | 本次使用情况 |
|---|---|
| 电脑 | Apple Silicon Mac,arm64 |
| Flutter OH | 3.41.10-ohos-1.0.1 |
| Dart | 3.11.5 |
| DevEco Studio | 26.0.0 Release |
| HarmonyOS SDK | API 26 |
| 测试手机 | 华为畅享 90 Pro Max,HarmonyOS 7.0.0.105 |
| 插件 | screen_security 1.1.2 |
这里需要区分“版本号最新”和“稳定版最新”。活动表推荐 Flutter v3.44.9;截至 2026 年 9 月 11 日,Flutter OH 仓库中版本号最大的标签是 3.44.9+ohos-0.0.1-canary1,但它带有 canary1,属于预览版本。当前最新正式稳定标签仍是 3.41.10-ohos-1.0.1,也是本文实际完成静态检查、测试、HAP 构建和真机验证的版本。
因此这里不能只把表格改成 3.44.9,否则版本与证据不一致。如果活动最终强制要求 v3.44.9,应先用上述 canary SDK 重新完成构建和真机回归,再替换环境信息与测试证据;后续出现 3.44.9 正式稳定标签时也要重新验证。
五、代码仓库是怎么来的
上游基线是最新稳定版 screen_security 1.1.2,TAG v1.1.2 指向提交 1700cf3880ecc4e3c63008caa91da79c48ede78a。该版本保持 enable() 和 disable() 两个公开接口稳定,许可证为 MIT,允许保留版权与许可声明后进行修改和再发布,因此我选择它作为适配起点,而不是更早版本或未发布代码。
我先核对待适配、适配中、已适配清单和 oh-flutter 组织仓库,再把保留上游历史的代码同步到 AtomGit。最终适配提交 47787999 的直接父提交就是上述上游基线,当前可复现入口为:
git clone https://atomgit.com/oh-flutter/screen_security.git
cd screen_security
git checkout 477879990d220cb517986f85a0f8e9e7fd985399
git rev-parse HEAD
git show -s --format='%P'
这个仓库早于统一分支规范,远端实际分支是 ohos-adaptation,所以本文不把它改写成并不存在的 feat/ohos_screen_security_1.1.2。后续库统一使用 feat/ohos_<库名>_<版本>。在干净副本中补全 OHOS 插件骨架可使用:
flutter create --template=plugin --platforms=ohos --no-pub .
命令只负责生成 ohos/ 工程骨架;通道名、窗口能力、权限和错误处理仍要按原项目接口实现。适配仓库、分支链接和可复现命令已经同时保留,后续代码图、构建图与真机图分别验证实现和结果。
六、让 Flutter 识别 OHOS 插件
第一处改动在 pubspec.yaml。原来只注册了 Android 和 iOS,我增加了 OHOS 平台入口:
flutter:
plugin:
platforms:
android:
package: dev.kidpech.screen_security
pluginClass: KidpechScreenSecurityPlugin
ios:
pluginClass: KidpechScreenSecurityPlugin
ohos:
pluginClass: ScreenSecurityPlugin
这里的 pluginClass 必须和 ArkTS 导出的类名一致。少写这一段时,Dart 代码照样能通过静态检查,但运行到鸿蒙设备后找不到插件实现,调用通道就会失败。这类问题看起来像业务方法没写对,实际是插件根本没有注册进引擎。
OHOS 插件还需要自己的工程入口。我新建了 ohos/index.ets,只负责导出插件类:
import ScreenSecurityPlugin from './src/main/ets/components/plugin/ScreenSecurityPlugin';
export default ScreenSecurityPlugin;
同时补齐 oh-package.json5、hvigorfile.ts、build-profile.json5 和 src/main/module.json5。这些文件看起来零碎,但职责很清楚:它们告诉 OHOS 构建系统这是一个 HAR 模块、入口在哪里、由哪个构建插件处理,以及需要什么系统权限。
七、ArkTS 端怎么接住两个方法
核心代码在 ScreenSecurityPlugin.ets。这个类同时实现 FlutterPlugin、MethodCallHandler 和 AbilityAware。
export default class ScreenSecurityPlugin
implements FlutterPlugin, MethodCallHandler, AbilityAware {
private channel: MethodChannel | null = null;
private abilityContext: common.UIAbilityContext | null = null;
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(
binding.getBinaryMessenger(),
'kidpech_screen_security',
);
this.channel.setMethodCallHandler(this);
}
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.abilityContext =
binding.getAbility().context as common.UIAbilityContext;
}
}
onAttachedToEngine 负责建立通道,通道名称必须和 Dart 端一字不差。onAttachedToAbility 则保存当前 UIAbilityContext。之所以需要这个上下文,是因为后面要通过它找到应用当前正在显示的窗口。
生命周期也不能只管“连上”,还要管“断开”。插件离开 Flutter 引擎时要取消方法处理器,离开 Ability 时要清空上下文。否则插件被重新挂载后,旧对象还可能留在内存里,问题不一定马上出现,但调试起来很麻烦。
onDetachedFromEngine(binding: FlutterPluginBinding): void {
this.channel?.setMethodCallHandler(null);
this.channel = null;
}
onDetachedFromAbility(): void {
this.abilityContext = null;
}
两个 Dart 方法来到 ArkTS 后,用一个 switch 分发即可:
onMethodCall(call: MethodCall, result: MethodResult): void {
switch (call.method) {
case 'enableScreenSecurity':
this.setWindowPrivacyMode(true, result);
break;
case 'disableScreenSecurity':
this.setWindowPrivacyMode(false, result);
break;
default:
result.notImplemented();
break;
}
}
这里我没有复制两套开关逻辑,而是统一交给 setWindowPrivacyMode。开启和关闭只差一个布尔值,放在一起更不容易出现一边修了、另一边忘记改的情况。
八、真正打开鸿蒙窗口隐私模式
鸿蒙端的关键 API 是 setWindowPrivacyMode。调用前先使用 window.getLastWindow(context) 拿到当前窗口:
private async setWindowPrivacyMode(
enabled: boolean,
result: MethodResult,
): Promise<void> {
const context = this.abilityContext;
if (context === null) {
result.error('NO_ABILITY', 'UIAbility is not available', null);
return;
}
try {
const currentWindow = await window.getLastWindow(context);
await currentWindow.setWindowPrivacyMode(enabled);
result.success(null);
} catch (exception) {
const error = exception as BusinessError;
result.error(
error.code.toString(),
'Failed to update window privacy mode',
error.message,
);
}
}
这段代码里有两个容易忽略的地方。第一,Ability 还没准备好时不能硬调窗口 API,所以我先判断上下文是否为空,并把 NO_ABILITY 返回给 Dart。第二,系统 API 是异步调用,失败后也不能只在 ArkTS 控制台打印一行日志,否则 Flutter 页面不知道发生了什么。通过 result.error 把错误码和信息传回去,调用方才有机会弹提示或做降级处理。

图 5:核心实现只有一条主线:获取当前窗口,再按参数打开或关闭隐私模式。
九、权限别漏掉
只写 API 还不够,模块需要声明 ohos.permission.PRIVACY_WINDOW:
{
"module": {
"name": "screen_security",
"type": "har",
"deviceTypes": ["default", "tablet"],
"requestPermissions": [
{
"name": "ohos.permission.PRIVACY_WINDOW"
}
]
}
}
插件最终会以 HAR 的形式进入示例应用。构建完成后,我直接检查 HAP 内的 module.json,可以看到 ohos.permission.INTERNET 和 ohos.permission.PRIVACY_WINDOW 都已经合并进去。这样比只看源码更可靠,因为源码里写了权限,不等于最终安装包里一定有。

图 6:从构建产物中检查权限,确认 PRIVACY_WINDOW 已进入最终模块配置。
十、补齐交付文件并完成自动化检查
除 OHOS 源码外,我还核对了 README.md、CHANGELOG.md、许可证、pubspec.yaml 平台声明、example/ohos/ 和示例截图。交付文件必须反映真实仓库状态;目标仓库没有要求的文件不为凑清单虚构,后续若按社区准入规则提交,再以当时规则补齐 README.OpenSource 等材料。
代码写完后,我先在插件根目录执行:
flutter analyze
flutter test
flutter analyze 返回 No issues found,插件根目录的 Dart 测试共 26 项,全部通过。测试覆盖了通道名称、开启与关闭的方法调用、无参数调用、原生异常向 Dart 端传递,以及多次开关的调用顺序。仓库虽然带有 example/ohos/entry/src/ohosTest/ 模板测试骨架,但它没有断言插件的窗口隐私行为,因此本文不把它计入 ArkTS 功能测试。

图 7:静态检查无报错,26 项 Dart 测试全部通过。
接着进入 example 构建 OHOS 调试包:
cd example
flutter build hap --debug --no-codesign
Hvigor 构建成功,生成了 build/ohos/hap/entry-default-unsigned.hap。--no-codesign 适合检查代码和工程配置能不能正常编译;真机安装仍然要使用 DevEco Studio 配置过签名的 HAP。不要把“unsigned HAP 构建成功”直接写成“真机验证完成”,这是两回事。

图 8:OHOS 示例工程完成构建,Hvigor 正常输出 unsigned HAP。
十一、Demo 通过固定提交接入
另一个应用要复用这次适配,依赖必须指向 AtomGit 上经过验证的完整提交,不能落回 pub.dev 上尚未包含 OHOS 实现的版本,也不要长期依赖会继续移动的分支:
dependencies:
screen_security:
git:
url: https://atomgit.com/oh-flutter/screen_security.git
ref: 477879990d220cb517986f85a0f8e9e7fd985399
执行 flutter pub get 后,应在 pubspec.lock 的 resolved-ref 中确认解析结果仍是 477879990d220cb517986f85a0f8e9e7fd985399。分支链接适合查看最新代码,TAG 或完整 commit 才适合作为可复现依赖;当前没有适配 TAG,所以这里锁定 commit。
下面是包含导入、调用、状态展示和错误反馈的最小页面:
import 'package:flutter/material.dart';
import 'package:screen_security/screen_security.dart';
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: SafeArea(child: ScreenSecurityDemo()),
),
),
);
}
class ScreenSecurityDemo extends StatefulWidget {
const ScreenSecurityDemo({super.key});
State<ScreenSecurityDemo> createState() => _ScreenSecurityDemoState();
}
class _ScreenSecurityDemoState extends State<ScreenSecurityDemo> {
final _screenSecurity = ScreenSecurity();
bool _enabled = false;
String? _error;
Future<void> _setEnabled(bool enabled) async {
try {
if (enabled) {
await _screenSecurity.enable();
} else {
await _screenSecurity.disable();
}
if (!mounted) return;
setState(() {
_enabled = enabled;
_error = null;
});
} catch (error) {
if (!mounted) return;
setState(() => _error = error.toString());
}
}
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_enabled ? 'Screen security is ON' : 'Screen security is OFF'),
if (_error != null) Text('Error: $_error'),
FilledButton(
onPressed: _enabled ? null : () => _setEnabled(true),
child: const Text('Enable Security'),
),
OutlinedButton(
onPressed: _enabled ? () => _setEnabled(false) : null,
child: const Text('Disable Security'),
),
],
),
);
}
}
这个插件在 Dart 层不创建订阅或控制器,因此没有额外对象需要 dispose()。但是隐私模式属于窗口状态,退出敏感流程前仍要显式 await screenSecurity.disable();不要只在无法等待异步结果的 dispose() 中恢复。
十二、真机上怎么判断防护真的生效
我把签名后的调试 HAP 安装到华为畅享 90 Pro Max。应用启动后,红色卡片显示 Screen security is OFF,按钮文字是 Enable Security。这时系统窗口信息中的 isPrivacyMode 为 false,系统抓图可以看到完整内容,对应图 1。
点击 Enable Security 后,Flutter 页面内部会切换到开启状态,同时原生插件把窗口隐私模式设置为 true。我用窗口服务再次查询,得到:
WindowName: flutter_oh_demo0
isPrivacyMode: true
这时候再次通过系统抓图,应用内容区域已经变成黑色,对应图 2。为了避免只拿一张黑图下结论,我把关闭和开启两次窗口查询放在一起对比,图 3 证明状态变化确实落到了系统窗口层。
测试结束后,我又调用了一次 disable(),确认 isPrivacyMode 回到 false。这个收尾不能省,因为真实业务通常只需要在敏感页面临时开启。如果退出页面后没有恢复,用户在应用其他页面也无法截图,体验会很差。实际接入时可以在进入敏感流程时调用 enable(),离开时在合适的生命周期里调用 disable(),同时注意异常和页面跳转。
这轮没有单独留存系统录屏文件。实现采用的窗口隐私模式面向屏幕捕获保护,但当前真机结论只覆盖截图路径;录屏效果需要在目标系统版本上另行开始录制、切换开关并回看成片后才能标记为通过。
十三、提交适配分支
推送前先排除签名材料、本机 SDK 路径和构建产物,再提交实际文件:
git status --short
git diff --check
git add .metadata CHANGELOG.md README.md pubspec.yaml lib ohos example
git commit -s -m "feat: add OpenHarmony screen security support"
git push -u origin ohos-adaptation
截至 2026 年 9 月 10 日,远端分支可读取,HEAD 为 477879990d220cb517986f85a0f8e9e7fd985399。
十四、FAQ:这次适配里最容易踩的几个坑
Q1:调用后为什么没有进入 OHOS 实现?
- 现象:
enable()或disable()没有到达 ArkTS 方法分支,调用可能表现为插件未注册或方法未实现。 - 原因:
pubspec.yaml平台入口、pluginClass、通道名或方法名没有与原接口保持一致。 - 解决方法: 注册
ScreenSecurityPlugin,并逐字核对kidpech_screen_security、enableScreenSecurity和disableScreenSecurity。 - 验证结果: 26 项 Dart 测试覆盖通道名称、两个方法名和调用顺序,真机开关可以改变窗口隐私状态。
Q2:为什么要返回 NO_ABILITY?
- 现象: 插件已经连接 Flutter 引擎,但当前还拿不到可用于查询窗口的
UIAbilityContext。 - 原因: 引擎注册和 Ability 挂载是两个生命周期阶段,不能假设它们同时完成。
- 解决方法: 在
onAttachedToAbility保存上下文,在调用窗口 API 前判空,并在解绑时清空。 - 验证结果: 最终源码会在上下文缺失时返回明确的
NO_ABILITY;Dart 测试确认平台异常不会被吞掉。
Q3:源码声明了权限,为什么还要检查 HAP?
- 现象: 源码中已经写入
ohos.permission.PRIVACY_WINDOW,但仅凭源码无法证明宿主最终获得了该声明。 - 原因: 插件会先构建为 HAR,再由宿主合并配置;中间的工程配置或依赖解析可能影响最终产物。
- 解决方法: 构建 HAP 后解包检查最终
module.json,不要只检查插件目录。 - 验证结果: 图 6 显示
PRIVACY_WINDOW已进入本次受测 HAP。
Q4:开启后截图为什么只剩黑色?
- 现象: 系统截图只保留状态栏,应用内容区域变成黑色。
- 原因: 窗口隐私模式阻止系统抓图暴露受保护的应用窗口,这不是 Flutter 渲染失败。
- 解决方法: 同时保留开启前页面、开启后抓图和窗口属性,避免只凭黑图下结论。
- 验证结果: 图 1、图 2 和图 3 分别证明页面原本正常、抓图被遮黑以及
isPrivacyMode已开启。
Q5:能否把它当成完整的数据防泄漏方案?
- 现象: 业务容易把“窗口进入隐私模式”理解成所有复制途径都被阻断。
- 原因: 窗口级保护挡不住另一部手机拍摄、已被修改的设备或数据在进入受保护界面之前泄漏。
- 解决方法: 把插件作为纵深防护的一层,同时保留身份认证、最小权限、敏感字段脱敏和服务端鉴权。
- 验证结果: 本次只确认系统截图遮黑和窗口状态切换;录屏未单独留证,外部拍摄也不在插件能力范围内。
十五、总结
这次适配没有改动 screen_security 的上层用法,原有 Flutter 代码继续调用 enable() 和 disable()。新增工作集中在 OHOS 插件注册、Ability 生命周期、MethodChannel 方法分发、窗口隐私模式调用和权限声明几个地方。
最后我用四层结果做了确认:Dart 静态检查通过、26 项自动化测试通过、OHOS HAP 构建通过、HarmonyOS API 26 真机上的窗口隐私模式可以在 false 和 true 之间切换。开启后系统抓图中的应用区域变黑,关闭后恢复正常。当前仍缺少插件核心行为的 ArkTS 自动化测试和独立录屏证据,因此这两项没有写成已经通过。
十六、参考链接
欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter
更多推荐



所有评论(0)