ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter 三方库 screen_state 的 OpenHarmony 适配实战

Flutter 三方库 screen_state 的 OpenHarmony 适配实战 Flutter 三方库 screen_state 的 OpenHarmony 适配实战本文记录了将开源 Flutter 三方库screen_statev5.0.2适配到 OpenHarmony / HarmonyOS 平台的完整过程包含适配思路、代码改动对照、关键决策和踩坑复盘。一、背景1.1 三方库简介screen_state 是 Flutter 社区Copenhagen Center for Health TechnologyCACHET / DTU开发的一款屏幕状态监听插件提供以下能力亮屏事件SCREEN_ON— 设备屏幕点亮时推送事件熄屏事件SCREEN_OFF— 设备屏幕熄灭时推送事件解锁事件SCREEN_UNLOCKED— 用户解锁设备时推送事件后台运行监听— 应用退到后台后仍持续接收屏幕状态事件统一事件流 API— 通过StreamScreenStateEvent消费所有事件该三方库最初支持Android / iOS两个平台本次任务将其适配到OpenHarmony / HarmonyOS平台。项目地址https://atomgit.com/oh-flutter/screen_state1.2 适配目标维度要求功能一致性亮屏/熄屏/解锁三类事件与 Android、iOS 行为一致Dart 层零改动API 形态screenStateStream、ScreenStateEvent保持不变Dart 层仅补平台判断性能事件订阅按需创建onListen、及时释放onCancel避免资源泄漏工程规范遵循 Flutter OHOS 插件标准结构ohos/HAR 模块 pubspec.yaml注册 example 工程二、适配路线图整个适配分为 4 个阶段第 1 阶段适配评估 ── 必要性评估、阅读 Android/iOS 原生实现、确认鸿蒙侧等价 API 第 2 阶段原生实现 ── 创建 ohos/ 目录编写 ArkTS ScreenStatePluginEventChannel 第 3 阶段插件注册 ── pubspec.yaml 注册 ohos 平台Dart 层补 Platform.isOhos 第 4 阶段示例验证 ── flutter create 生成 ohos 示例工程构建 HAP 验证三、逐步适配过程第 1 阶段适配评估1.1 必要性评估适配前先回答三个问题检查项结果插件是否含平台原生代码✅ 是Android Kotlin iOS Swift是否使用平台通道MethodChannel / EventChannel✅ 是EventChannel(screenStateEvents)仓库中是否已有ohos/目录❌ 否结论需要鸿蒙化。插件通过原生EventChannel推送屏幕事件OHOS 无现成支持必须实现 ArkTS 原生层。1.2 阅读原生实现提取通信契约Dart 侧契约lib/screen_state.dartenumScreenStateEvent{screenUnlocked,screenOn,screenOff;// Android 返回 intent actioniOS/OHOS 返回短名称Stringgetname{...}// 两种名称形式都可解析staticScreenStateEventfromName(Stringname){switch(name){caseSCREEN_UNLOCKED:caseandroid.intent.action.USER_PRESENT:returnScreenStateEvent.screenUnlocked;// ...}}}StreamScreenStateEventgetscreenStateStream_screenStateStream??Platform.isAndroid||Platform.isIOS// ← 需加 Platform.isOhos?_screenStateStream??_eventChannel.receiveBroadcastStream().map((event)ScreenStateEvent.fromName(event)):StreamScreenStateEvent.empty();Android 侧实现Kotlin// ScreenStatePlugin.ktpublicclassScreenStatePlugin:FlutterPlugin,EventChannel.StreamHandler{overridefunonAttachedToEngine(binding:FlutterPlugin.FlutterPluginBinding){eventChannelEventChannel(binding.binaryMessenger,screenStateEvents)contextbinding.applicationContext eventChannel.setStreamHandler(this)}overridefunonListen(arguments:Any?,events:EventChannel.EventSink?){screenReceiverScreenReceiver(events)valfilterIntentFilter()filter.addAction(Intent.ACTION_SCREEN_ON)// 亮屏filter.addAction(Intent.ACTION_SCREEN_OFF)// 熄屏filter.addAction(Intent.ACTION_USER_PRESENT)// 解锁context!!.registerReceiver(screenReceiver,filter)}overridefunonCancel(arguments:Any?){context!!.unregisterReceiver(screenReceiver)}}// ScreenReceiver.kt —— 收到广播后直接把 intent.action 字符串推给 DartclassScreenReceiver(privatevaleventSink:EventSink?):BroadcastReceiver(){overridefunonReceive(context:Context,intent:Intent){eventSink?.success(intent.action)}}契约总结契约项值通道类型EventChannel非 MethodChannel通道名screenStateEvents事件负载事件名字符串Android 为 intent actioniOS 为短名称生命周期onListen注册 /onCancel注销关键认知screen_state 用的是EventChannel StreamHandler而不是常见的 MethodChannel MethodCallHandler这是本次适配与普通插件最大的不同点。第 2 阶段原生实现核心2.1 整体架构对比Android (Kotlin) OHOS (ArkTS) ──────────────────── ──────────────────── class ScreenStatePlugin class ScreenStatePlugin implements FlutterPlugin, implements FlutterPlugin, EventChannel.StreamHandler StreamHandler import io.flutter... import { FlutterPlugin, import android.content... FlutterPluginBinding, EventChannel, EventSink, StreamHandler } from ohos/flutter_ohos BroadcastReceiver(系统广播) commonEventManager(公共事件订阅)2.2 事件通道注册平台代码AndroidEventChannel(binding.binaryMessenger, screenStateEvents)OHOSnew EventChannel(binding.getBinaryMessenger(), screenStateEvents)差异OHOS 使用getBinaryMessenger()与 Android 的binaryMessenger属性等价均从FlutterPluginBinding获取。2.3 事件源对照平台事件源注册时机注销时机AndroidBroadcastReceiver监听ACTION_SCREEN_ON/OFF/USER_PRESENTonListenonCancelOHOScommonEventManager订阅COMMON_EVENT_SCREEN_ON/OFF/SCREEN_UNLOCKED/USER_PRESENTonListenonCancel2.4 ArkTS 核心实现ScreenStatePlugin.etsimport{EventChannel,EventSink,FlutterPlugin,FlutterPluginBinding,Log,StreamHandler,}fromohos/flutter_ohos;importcommonEventManagerfromohos.commonEventManager;import{BusinessError}fromkit.BasicServicesKit;constCHANNEL_NAME:stringscreenStateEvents;// 将系统公共事件 id 映射为 Dart 层可识别的事件名functiontoScreenStateEvent(eventId:string):string{switch(eventId){casecommonEventManager.Support.COMMON_EVENT_SCREEN_ON:returnSCREEN_ON;casecommonEventManager.Support.COMMON_EVENT_SCREEN_OFF:returnSCREEN_OFF;casecommonEventManager.Support.COMMON_EVENT_SCREEN_UNLOCKED:casecommonEventManager.Support.COMMON_EVENT_USER_PRESENT:// 兼容旧版本returnSCREEN_UNLOCKED;default:returneventId;}}exportdefaultclassScreenStatePluginimplementsFlutterPlugin,StreamHandler{privateeventChannel:EventChannel|nullnull;privateeventSink:EventSink|nullnull;privatesubscriber:commonEventManager.CommonEventSubscriber|nullnull;getUniqueClassName():string{returnScreenStatePlugin;// 必须与 pubspec.yaml 的 pluginClass 一致}onAttachedToEngine(binding:FlutterPluginBinding):void{this.eventChannelnewEventChannel(binding.getBinaryMessenger(),CHANNEL_NAME);this.eventChannel.setStreamHandler(this);}onDetachedFromEngine(binding:FlutterPluginBinding):void{this.eventChannel?.setStreamHandler(null);this.eventChannelnull;this.unsubscribeScreenEvents();}onListen(args:Object,events:EventSink):void{this.eventSinkevents;this.subscribeScreenEvents();}onCancel(args:Object):void{this.eventSinknull;this.unsubscribeScreenEvents();}privateasyncsubscribeScreenEvents():Promisevoid{if(this.subscriber!null)return;letsubscribeInfo:commonEventManager.CommonEventSubscribeInfo{events:[commonEventManager.Support.COMMON_EVENT_SCREEN_ON,commonEventManager.Support.COMMON_EVENT_SCREEN_OFF,commonEventManager.Support.COMMON_EVENT_SCREEN_UNLOCKED,commonEventManager.Support.COMMON_EVENT_USER_PRESENT,],};try{this.subscriberawaitcommonEventManager.createSubscriber(subscribeInfo);commonEventManager.subscribe(this.subscriber,(err:BusinessError,data:commonEventManager.CommonEventData){if(err||this.eventSinknull)return;leteventName:stringtoScreenStateEvent(data.event);this.eventSink.success(eventName);});}catch(error){Log.e(TAG,createSubscriber error: JSON.stringify(error));}}privateunsubscribeScreenEvents():void{if(this.subscribernull)return;commonEventManager.unsubscribe(this.subscriber);this.subscribernull;}}2.5 实现差异详解适配中遇到的最大差异是事件名的归一化Android 直接推送intent.action如android.intent.action.SCREEN_ON而 OHOS 的系统公共事件 id 是usual.event.SCREEN_ON形式。好在 Dart 层ScreenStateEvent.fromName只认识SCREEN_ON/SCREEN_OFF/SCREEN_UNLOCKED与android.intent.action.*两种形式因此选择在原生层统一转换为短名称。方案优点缺点原生层映射为短名称✅Dart 层零改动与 iOS 行为一致需要维护一张映射表Dart 层扩展解析usual.event.*原生层逻辑简单需改 Dart 公共 API破坏零改动目标第 3 阶段插件注册3.1 pubspec.yaml 注册 ohos 平台flutter:plugin:platforms:android:package:dk.cachet.screen_statepluginClass:ScreenStatePluginios:pluginClass:ScreenStatePluginohos:# ← 新增pluginClass:ScreenStatePlugin# ← 与 getUniqueClassName() 一致3.2 Dart 层补平台判断lib/screen_state.dart中screenStateStream的平台守卫增加Platform.isOhosStreamScreenStateEventgetscreenStateStream_screenStateStream??Platform.isAndroid||Platform.isIOS||Platform.isOhos?_screenStateStream??_eventChannel.receiveBroadcastStream().map((event)ScreenStateEvent.fromName(event)):StreamScreenStateEvent.empty();第 4 阶段示例验证4.1 生成 OHOS 示例工程在example/目录下执行 Flutter 官方命令生成 OHOS 宿主工程flutter create.--platformsohos该命令自动生成example/ohos/目录50 个文件包含example/ohos/ ├── AppScope/app.json5 # 应用配置 ├── build-profile.json5 # 项目构建配置含 signingConfigs、SDK 版本 ├── hvigor/hvigor-config.json5 # 构建工具配置 ├── oh-package.json5 # 顶层包配置 ├── hvigorfile.ts # 构建入口 └── entry/ ├── build-profile.json5 ├── oh-package.json5 └── src/main/ ├── module.json5 # entry 模块配置 ├── ets/ │ ├── entryability/ │ │ └── EntryAbility.ets # Ability 生命周期 │ ├── pages/ │ │ └── Index.ets # UI 页面Flutter 容器 │ └── plugins/ │ └── GeneratedPluginRegistrant.ets # 自动注册 ScreenStatePlugin └── resources/rawfile/flutter_assets/ # Flutter 运行时资源GeneratedPluginRegistrant.ets由 Flutter 工具自动生成并注册插件import{FlutterEngine,Log}fromohos/flutter_ohos;importScreenStatePluginfromscreen_state;// ← 从插件包导入exportclassGeneratedPluginRegistrant{staticregisterWith(flutterEngine:FlutterEngine){try{flutterEngine.getPlugins()?.add(newScreenStatePlugin());}catch(e){...}}}4.2 构建验证使用 DevEco Studio 的 hvigor 命令行构建 HAPnodehvigorw.js--modemodule-pmoduleentrydefault-pproductdefault\-prequiredDeviceTypephone assembleHap--analyzenormal--parallel--incremental--daemon产物entry/build/default/outputs/default/entry-default-unsigned.hap四、完整代码对照4.1 Android vs OHOS 完整实现对照维度Android (Kotlin)OHOS (ArkTS)语言KotlinArkTS (TypeScript 语法)插件接口FlutterPlugin, EventChannel.StreamHandlerFlutterPlugin, StreamHandler类注册Override注解 Flutter 自动发现getUniqueClassName()返回类名通道获取binding.binaryMessengerbinding.getBinaryMessenger()事件源BroadcastReceiverIntentFiltercommonEventManager.createSubscribersubscribe事件发送eventSink?.success(intent.action)eventSink.success(eventName)注销时机onCancel→unregisterReceiveronCancel→unsubscribe4.2 关键 ArkTS 语法差异Android 语法ArkTS 语法备注import io.flutter.plugin.common.EventChannelimport { EventChannel } from ohos/flutter_ohosOHOS 使用模块化导入override fun onListen(arguments: Any?, events: EventSink?)onListen(args: Object, events: EventSink): void参数类型Any?→Objectcontext.registerReceiver(receiver, filter)commonEventManager.subscribe(subscriber, cb)广播 → 公共事件intent.actiondata.event注意字段名不同OHOS 是event而非eventId五、关键决策说明决策 1保持通道名不变Dart 层EventChannel(screenStateEvents)已固定OHOS 原生侧必须使用完全相同的通道名。通道名是 Dart 与原生之间的通信契约改变会导致 Dart 端收不到任何事件。维护策略通道名集中定义在常量CHANNEL_NAME中后续修改只需动一处。决策 2事件名归一化到短名称与 iOS 一致Android 原生推送intent.action如android.intent.action.SCREEN_ON而 OHOS 系统事件 id 是usual.event.SCREEN_ON。Dart 层fromName只认识SCREEN_ON/SCREEN_UNLOCKED两种形式因此选择在原生层将事件 id 映射为短名称。维护策略映射函数toScreenStateEvent()独立成纯函数新增事件类型时只需补充 switch 分支。决策 3不声明任何权限适配初版在module.json5声明了ohos.permission.RECEIVE_SCREEN_EVENTS构建时发现该权限不存在于 SDK 预定义列表00303221 Configuration Error。查证官方文档确认COMMON_EVENT_SCREEN_ON/OFF/USER_PRESENT的订阅者所需权限无三方应用可直接订阅。维护策略遵循官方文档订阅者所需权限无的结论module.json5 不再声明权限。决策 4双事件源兼容解锁事件COMMON_EVENT_USER_PRESENT在新版 HarmonyOS 中已标记弃用useinstead COMMON_EVENT_SCREEN_UNLOCKED但为兼容旧版本系统同时订阅两个事件并映射到SCREEN_UNLOCKED。维护策略保留两个订阅避免老设备上解锁事件丢失。决策 5Dart 层零改动仅补平台判断Dart 公共 APIScreen、ScreenStateEvent、screenStateStream完全保持原样仅将平台守卫从Platform.isAndroid || Platform.isIOS扩展为|| Platform.isOhos。示例工程则补充TargetPlatform.ohos判断——这是排查鸿蒙不生效问题时的关键发现example 的_isSupportedPlatform未含 ohos导致 UI 层从未调用startListening()。维护策略所有平台相关判断集中在_isSupportedPlatform/screenStateStream守卫中便于统一维护。六、测试与验证测试环境项目版本Flutter3.41.10-ohos-1.0.0Dart3.11.5HarmonyOS SDK26.0.0API 26IDEDevEco Studio 26.0.0设备 ROMALN-AL00 7.0.0.105(SP6C00E105R4P3)验证要点静态分析—flutter analyze通过无警告无错误。编译验证— hvigorassembleHap构建成功43 tasks产出entry-default-unsigned.hap。插件注册—GeneratedPluginRegistrant.ets正确导入并注册ScreenStatePluginimport ScreenStatePlugin from screen_state。事件通道契约— OHOS 侧EventChannel(screenStateEvents)与 Dart 侧EventChannel(screenStateEvents)通道名一致。真机行为待实测— 配置签名后连真机锁屏/解锁/熄屏时 UI 日志应输出SCREEN_OFF/SCREEN_ON/SCREEN_UNLOCKED事件。七、运行效果适配完成并通过构建验证。运行截图需在真机签名安装后通过以下命令获取真机 ALN-AL00 已连接flutter screenshot-ddevice_ip:port八、遗留问题与改进方向已知问题进程存活依赖— 屏幕事件仅在应用进程存活时可达用户强杀应用后公共事件订阅失效与 Android 行为一致。USER_PRESENT 弃用— 新版 HarmonyOS 弃用COMMON_EVENT_USER_PRESENT已通过同时订阅COMMON_EVENT_SCREEN_UNLOCKED兼容但长期建议移除旧事件源。签名依赖— 示例工程需配置签名DevEco 自动签名或build-profile.json5后才能安装到真机。未来优化真机截图验证— 安装签名 HAP 后补充flutter screenshot运行截图与实测日志。README 双语文档— 已生成README.OpenHarmony.md/README.OpenHarmony_CN.md后续随版本更新同步维护。九、总结将一个 Flutter 三方库适配到 OHOS 平台核心路径可以概括为三步走1. 找对应 ── 找到 OHOS 对每个 Android 原生 API 的等价实现BroadcastReceiver → commonEventManager 2. 保契约 ── 确保方法通道名、方法名、返回值结构完全一致screenStateEvents 通道 事件名字符串 3. 补缺口 ── 对于 OHOS 不提供的 API用合理方案弥补事件 id 归一化、双事件源兼容对于screen_state三方库适配涉及61 个文件的新增/修改插件 ohos 实现 10 个 示例 ohos 工程 50 个 Dart 层 1 个提交347a4d271174 / -25。Dart 公共 API 和其他平台的代码完全不受影响——这正是 Flutter 跨平台三方库生态的魅力所在。回顾整个适配过程三个坑值得记录踩坑点现象根因与解法module.json5 权限 schema 校验失败reason字段必须为$string:资源引用或含{}占位符权限 reason 需引用 string 资源权限不在 SDK 预定义RECEIVE_SCREEN_EVENTS声明报 00303221屏幕公共事件订阅无需权限直接移除声明鸿蒙上不生效example 从未收到事件根因在example 的_isSupportedPlatform未含 ohosUI 层从未订阅同时修复插件补订SCREEN_UNLOCKED事件参考文档screen_state 官方仓库本适配仓库AtomGitHarmonyOS Flutter 适配指南ohos.commonEventManager API 参考
返回列表