Flutter 鸿蒙插件适配实战:给 screen_security 补上截图与录屏防护
欢迎加入 CPF-Flutter 社区,一起交流 Flutter 鸿蒙开发和三方库适配经验。
适配后的仓库地址: https://atomgit.com/oh-flutter/screen_security/tree/ohos-adaptation
我适配的是 screen_security。它原本已经支持 Android 和 iOS,作用也很好理解:进入登录、支付、证件展示这类敏感页面后,应用可以暂时禁止常规截图和录屏;离开页面时再恢复。Android 端用的是 FLAG_SECURE,iOS 端使用安全文本渲染层,但项目里没有鸿蒙实现。
我没有另起炉灶改 Dart API,而是保留原来的 enable() 和 disable(),只在插件底层增加 OHOS 平台代码。最终结果是:关闭防护时页面可以正常被抓取,开启后系统抓到的应用区域会变成黑色;窗口服务里的 isPrivacyMode 也会从 false 变成 true。这两个结果放在一起,才能说明不是按钮只改了页面文案,而是系统窗口的隐私模式真的生效了。

图 1:Dart API、MethodChannel、ArkTS 插件和系统窗口隐私模式之间的调用关系。
一、先确认这个库值得适配
动手之前,我先在 Flutter 鸿蒙三方库适配清单中做了去重。检查时,screen_security 只出现在待适配清单里,没有出现在“适配中”和“已适配”清单;oh-flutter 组织下也没有同名仓库。这一步很重要,因为功能实现完才发现别人已经提交,前面的时间基本就白花了。
原项目的 Dart 层已经把接口封装好了,调用方只需要写:
final screenSecurity = ScreenSecurity();
await screenSecurity.enable();
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 |
项目代码已经放到 AtomGit:
https://atomgit.com/oh-flutter/screen_security/tree/ohos-adaptation
三、让 Flutter 识别 OHOS 插件
第一处改动在 pubspec.yaml。原来只注册了 Android 和 iOS,我增加了 OHOS 平台入口:
flutter:
plugin:
platforms:
android:
package: com.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 把错误码和信息传回去,调用方才有机会弹提示或做降级处理。

图 2:核心实现只有一条主线:获取当前窗口,再按参数打开或关闭隐私模式。
六、权限别漏掉
只写 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 都已经合并进去。这样比只看源码更可靠,因为源码里写了权限,不等于最终安装包里一定有。

图 3:从构建产物中检查权限,确认 PRIVACY_WINDOW 已进入最终模块配置。
七、先过静态检查和自动化测试
代码写完后,我先在插件根目录执行:
flutter analyze
flutter test
flutter analyze 返回 No issues found,Dart 测试共 26 项,全部通过。测试覆盖了通道名称、开启与关闭的方法调用、无参数调用、原生异常向 Dart 端传递,以及多次开关的调用顺序。

图 4:静态检查无报错,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 构建成功”直接写成“真机验证完成”,这是两回事。

图 5:OHOS 示例工程完成构建,Hvigor 正常输出 unsigned HAP。
八、真机上怎么判断防护真的生效
我把签名后的调试 HAP 安装到华为畅享 90 Pro Max。应用启动后,红色卡片显示 Screen security is OFF,按钮文字是 Enable Security。这时系统窗口信息中的 isPrivacyMode 为 false,抓取页面也能看到完整内容。

图 6:防护关闭时,示例页面可以正常被系统截图工具抓取。
点击 Enable Security 后,Flutter 页面内部会切换到开启状态,同时原生插件把窗口隐私模式设置为 true。我用窗口服务再次查询,得到:
WindowName: flutter_oh_demo0
isPrivacyMode: true
这时候再次通过系统抓图,应用内容区域已经变成黑色。状态栏仍然可见,是因为它属于系统窗口,不是当前 Flutter 应用窗口的一部分。

图 7:isPrivacyMode: true 后,应用窗口内容被系统抓图自动遮黑。
为了避免只拿一张黑图下结论,我把关闭和开启两次窗口查询放在一起对比:

图 8:同一窗口在调用前后由 false 变为 true,证明开关已经落到系统窗口层。
测试结束后,我又调用了一次 disable(),确认 isPrivacyMode 回到 false。这个收尾不能省,因为真实业务通常只需要在敏感页面临时开启。如果退出页面后没有恢复,用户在应用其他页面也无法截图,体验会很差。实际接入时可以在进入敏感流程时调用 enable(),离开时在合适的生命周期里调用 disable(),同时注意异常和页面跳转。
九、这次适配里最容易踩的几个坑
1. 通道名和方法名必须完全一致
kidpech_screen_security、enableScreenSecurity 和 disableScreenSecurity 都沿用原项目定义。这里哪怕只差一个大小写,Flutter 和 ArkTS 也接不上。
2. 不要在拿到 Ability 之前操作窗口
Flutter 引擎注册和 Ability 挂载不是一回事。保存 UIAbilityContext,并在调用前做空值检查,能把启动阶段的偶发问题变成明确错误。
3. 权限要检查最终产物
插件模块声明权限后,还要确认它确实被合并进应用 HAP。只检查 ohos/src/main/module.json5 不够,我更愿意再看一眼构建产物。
4. 开启状态的黑图不是应用崩了
当隐私模式生效后,系统截图看不到应用内容正是预期结果。为了和黑屏、渲染失败区分开,应该同时保留三份证据:开启前的正常页面、应用内部状态变化、窗口服务的 isPrivacyMode: true。
5. 它不是万能防泄漏方案
窗口隐私模式可以限制常规系统截图和录屏,但挡不住另一部手机对着屏幕拍,也不能代替权限控制、身份认证、敏感字段脱敏和服务端鉴权。screen_security 更适合作为敏感页面的补充保护,不应该被当成 DRM 或完整的数据防泄漏系统。
十、总结
这次适配没有改动 screen_security 的上层用法,原有 Flutter 代码继续调用 enable() 和 disable()。新增工作集中在 OHOS 插件注册、Ability 生命周期、MethodChannel 方法分发、窗口隐私模式调用和权限声明几个地方。
最后我用四层结果做了确认:Dart 静态检查通过、26 项自动化测试通过、OHOS HAP 构建通过、HarmonyOS API 26 真机上的窗口隐私模式可以在 false 和 true 之间切换。开启后系统抓图中的应用区域变黑,关闭后恢复正常,达到了这次适配的目标。
完整代码:
https://atomgit.com/oh-flutter/screen_security/tree/ohos-adaptation
欢迎加入 CPF-Flutter 社区,查看更多 Flutter 鸿蒙源码、示例和三方库适配进展。
更多推荐

所有评论(0)