news 2026/9/12 19:11:51

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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:隐私模式开启后,系统抓图自动遮黑应用窗口内容。

为了排除黑屏或渲染失败,我又对比了同一应用窗口的系统属性。调用前后,isPrivacyModefalse变为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
上游基线TAGv1.1.2,提交1700cf3880ecc4e3c63008caa91da79c48ede78a
开源许可证MIT
适配仓库oh-flutter/screen_security
适配分支ohos-adaptation,属于统一命名规范实施前创建的历史分支
适配 TAG尚未发布
适配提交477879990d220cb517986f85a0f8e9e7fd985399feat: 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';finalscreenSecurity=ScreenSecurity();Future<void>protectSensitiveContent()async{awaitscreenSecurity.enable();}Future<void>restoreNormalCapture()async{awaitscreenSecurity.disable();}

再往下看,默认实现通过名为kidpech_screen_securityMethodChannel调用原生端,方法名分别是enableScreenSecuritydisableScreenSecurity。所以鸿蒙端真正要补的是三件事:注册同名通道、接住这两个方法、把开关状态交给鸿蒙窗口 API。

四、本次实测环境

项目本次使用情况
电脑Apple Silicon Mac,arm64
Flutter OH3.41.10-ohos-1.0.1
Dart3.11.5
DevEco Studio26.0.0 Release
HarmonyOS SDKAPI 26
测试手机华为畅享 90 Pro Max,HarmonyOS 7.0.0.105
插件screen_security 1.1.2

这里需要区分“版本号最新”和“稳定版最新”。活动表推荐 Flutterv3.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,TAGv1.1.2指向提交1700cf3880ecc4e3c63008caa91da79c48ede78a。该版本保持enable()disable()两个公开接口稳定,许可证为 MIT,允许保留版权与许可声明后进行修改和再发布,因此我选择它作为适配起点,而不是更早版本或未发布代码。

我先核对待适配、适配中、已适配清单和oh-flutter组织仓库,再把保留上游历史的代码同步到 AtomGit。最终适配提交47787999的直接父提交就是上述上游基线,当前可复现入口为:

gitclone https://atomgit.com/oh-flutter/screen_security.gitcdscreen_securitygitcheckout 477879990d220cb517986f85a0f8e9e7fd985399gitrev-parse HEADgitshow-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_securitypluginClass:KidpechScreenSecurityPluginios:pluginClass:KidpechScreenSecurityPluginohos:pluginClass:ScreenSecurityPlugin

这里的pluginClass必须和 ArkTS 导出的类名一致。少写这一段时,Dart 代码照样能通过静态检查,但运行到鸿蒙设备后找不到插件实现,调用通道就会失败。这类问题看起来像业务方法没写对,实际是插件根本没有注册进引擎。

OHOS 插件还需要自己的工程入口。我新建了ohos/index.ets,只负责导出插件类:

importScreenSecurityPluginfrom'./src/main/ets/components/plugin/ScreenSecurityPlugin';exportdefaultScreenSecurityPlugin;

同时补齐oh-package.json5hvigorfile.tsbuild-profile.json5src/main/module.json5。这些文件看起来零碎,但职责很清楚:它们告诉 OHOS 构建系统这是一个 HAR 模块、入口在哪里、由哪个构建插件处理,以及需要什么系统权限。

七、ArkTS 端怎么接住两个方法

核心代码在ScreenSecurityPlugin.ets。这个类同时实现FlutterPluginMethodCallHandlerAbilityAware

exportdefaultclassScreenSecurityPluginimplementsFlutterPlugin,MethodCallHandler,AbilityAware{privatechannel:MethodChannel|null=null;privateabilityContext:common.UIAbilityContext|null=null;onAttachedToEngine(binding:FlutterPluginBinding):void{this.channel=newMethodChannel(binding.getBinaryMessenger(),'kidpech_screen_security',);this.channel.setMethodCallHandler(this);}onAttachedToAbility(binding:AbilityPluginBinding):void{this.abilityContext=binding.getAbility().contextascommon.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)拿到当前窗口:

privateasyncsetWindowPrivacyMode(enabled:boolean,result:MethodResult,):Promise<void>{constcontext=this.abilityContext;if(context===null){result.error('NO_ABILITY','UIAbility is not available',null);return;}try{constcurrentWindow=awaitwindow.getLastWindow(context);awaitcurrentWindow.setWindowPrivacyMode(enabled);result.success(null);}catch(exception){consterror=exceptionasBusinessError;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.INTERNETohos.permission.PRIVACY_WINDOW都已经合并进去。这样比只看源码更可靠,因为源码里写了权限,不等于最终安装包里一定有。

图 6:从构建产物中检查权限,确认PRIVACY_WINDOW已进入最终模块配置。

十、补齐交付文件并完成自动化检查

除 OHOS 源码外,我还核对了README.mdCHANGELOG.md、许可证、pubspec.yaml平台声明、example/ohos/和示例截图。交付文件必须反映真实仓库状态;目标仓库没有要求的文件不为凑清单虚构,后续若按社区准入规则提交,再以当时规则补齐README.OpenSource等材料。

代码写完后,我先在插件根目录执行:

flutter analyze fluttertest

flutter analyze返回No issues found,插件根目录的 Dart 测试共 26 项,全部通过。测试覆盖了通道名称、开启与关闭的方法调用、无参数调用、原生异常向 Dart 端传递,以及多次开关的调用顺序。仓库虽然带有example/ohos/entry/src/ohosTest/模板测试骨架,但它没有断言插件的窗口隐私行为,因此本文不把它计入 ArkTS 功能测试。

图 7:静态检查无报错,26 项 Dart 测试全部通过。

接着进入example构建 OHOS 调试包:

cdexample 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.gitref:477879990d220cb517986f85a0f8e9e7fd985399

执行flutter pub get后,应在pubspec.lockresolved-ref中确认解析结果仍是477879990d220cb517986f85a0f8e9e7fd985399。分支链接适合查看最新代码,TAG 或完整 commit 才适合作为可复现依赖;当前没有适配 TAG,所以这里锁定 commit。

下面是包含导入、调用、状态展示和错误反馈的最小页面:

import'package:flutter/material.dart';import'package:screen_security/screen_security.dart';voidmain(){runApp(constMaterialApp(home:Scaffold(body:SafeArea(child:ScreenSecurityDemo()),),),);}classScreenSecurityDemoextendsStatefulWidget{constScreenSecurityDemo({super.key});@overrideState<ScreenSecurityDemo>createState()=>_ScreenSecurityDemoState();}class_ScreenSecurityDemoStateextendsState<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());}}@overrideWidgetbuild(BuildContextcontext){returnPadding(padding:constEdgeInsets.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:constText('Enable Security'),),OutlinedButton(onPressed:_enabled?()=>_setEnabled(false):null,child:constText('Disable Security'),),],),);}}

这个插件在 Dart 层不创建订阅或控制器,因此没有额外对象需要dispose()。但是隐私模式属于窗口状态,退出敏感流程前仍要显式await screenSecurity.disable();不要只在无法等待异步结果的dispose()中恢复。

十二、真机上怎么判断防护真的生效

我把签名后的调试 HAP 安装到华为畅享 90 Pro Max。应用启动后,红色卡片显示Screen security is OFF,按钮文字是Enable Security。这时系统窗口信息中的isPrivacyModefalse,系统抓图可以看到完整内容,对应图 1。

点击Enable Security后,Flutter 页面内部会切换到开启状态,同时原生插件把窗口隐私模式设置为true。我用窗口服务再次查询,得到:

WindowName: flutter_oh_demo0 isPrivacyMode: true

这时候再次通过系统抓图,应用内容区域已经变成黑色,对应图 2。为了避免只拿一张黑图下结论,我把关闭和开启两次窗口查询放在一起对比,图 3 证明状态变化确实落到了系统窗口层。

测试结束后,我又调用了一次disable(),确认isPrivacyMode回到false。这个收尾不能省,因为真实业务通常只需要在敏感页面临时开启。如果退出页面后没有恢复,用户在应用其他页面也无法截图,体验会很差。实际接入时可以在进入敏感流程时调用enable(),离开时在合适的生命周期里调用disable(),同时注意异常和页面跳转。

这轮没有单独留存系统录屏文件。实现采用的窗口隐私模式面向屏幕捕获保护,但当前真机结论只覆盖截图路径;录屏效果需要在目标系统版本上另行开始录制、切换开关并回看成片后才能标记为通过。

十三、提交适配分支

推送前先排除签名材料、本机 SDK 路径和构建产物,再提交实际文件:

gitstatus--shortgitdiff--checkgitadd.metadata CHANGELOG.md README.md pubspec.yaml lib ohos examplegitcommit-s-m"feat: add OpenHarmony screen security support"gitpush-uorigin ohos-adaptation

截至 2026 年 9 月 10 日,远端分支可读取,HEAD 为477879990d220cb517986f85a0f8e9e7fd985399

十四、FAQ:这次适配里最容易踩的几个坑

Q1:调用后为什么没有进入 OHOS 实现?

  • 现象:enable()disable()没有到达 ArkTS 方法分支,调用可能表现为插件未注册或方法未实现。
  • 原因:pubspec.yaml平台入口、pluginClass、通道名或方法名没有与原接口保持一致。
  • 解决方法:注册ScreenSecurityPlugin,并逐字核对kidpech_screen_securityenableScreenSecuritydisableScreenSecurity
  • 验证结果: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 真机上的窗口隐私模式可以在falsetrue之间切换。开启后系统抓图中的应用区域变黑,关闭后恢复正常。当前仍缺少插件核心行为的 ArkTS 自动化测试和独立录屏证据,因此这两项没有写成已经通过。

十六、参考链接

  • 适配仓库
  • 适配分支
  • Flutter OH 版本标签
  • Flutter OH 环境搭建指南

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 19:11:48

Python渗透测试工具开发指南:从零构建安全检测框架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 19:11:01

8款AI论文写作工具测评与本科生实用指南

1. 毕业论文写作工具测评背景作为一名经历过本科论文写作的过来人&#xff0c;我深知这个过程中的痛点。每到毕业季&#xff0c;图书馆里总是挤满了焦头烂额的学生&#xff0c;面对空白的文档和紧迫的deadline&#xff0c;那种焦虑感我至今记忆犹新。传统的论文写作方式需要花费…

作者头像 李华
网站建设 2026/9/12 19:10:12

双线性变换法设计二阶高通滤波器:从传递函数推导到 STM32/8051F 定点实现

目录 1. 连续域二阶高通滤波器传递函数 2. 双线性变换原理 3. 传递函数映射 3.1 代入双线性变换公式 3.2 化简分子和分母 3.3 通分并整理 4. 差分方程推导 5. 系数计算示例 6. 实际应用注意事项 7. 性能分析 7.1 频域响应分析 7.2 稳定性分析 7.3 计算复杂度分析 7.4 嵌入式平台…

作者头像 李华
网站建设 2026/9/12 19:08:42

COMSOL仿真在变质量注浆工程中的应用与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 19:06:46

投资穿透——央企的钱投出去之后,能看到什么?

穿透式监管系列 第11篇上一篇讲财务资金穿透&#xff0c;盯的是"钱在账上怎么动的"。这一篇往下一步&#xff0c;盯的是"钱投出去之后变成了什么"。央企是投资大户。一个集团全年投资规模动辄几百亿甚至上千亿&#xff0c;涵盖固定资产投资、股权投资、并…

作者头像 李华
网站建设 2026/9/12 19:06:35

数据中台API管理实战:从设计、发布到治理的完整指南

数据中台搭得再好&#xff0c;最终对外输出的出口还是API。我见过不少团队&#xff0c;数据仓库、数据模型、指标体系建设搞了一堆&#xff0c;到了业务方要用数据的时候&#xff0c;还是要靠“临时写接口”甚至“直接连库”来满足需求。结果就是接口遍地开花、口径乱七八糟、权…

作者头像 李华