Flutter 三方库 screen_state 的 OpenHarmony 适配实战
2026/9/6 4:47:39 网站建设 项目流程

Flutter 三方库 screen_state 的 OpenHarmony 适配实战

本文记录了将开源 Flutter 三方库screen_state(v5.0.2)适配到 OpenHarmony / HarmonyOS 平台的完整过程,
包含适配思路、代码改动对照、关键决策和踩坑复盘。


一、背景

1.1 三方库简介

screen_state 是 Flutter 社区(Copenhagen Center for Health Technology,CACHET / DTU)开发的一款屏幕状态监听插件,提供以下能力:

  • 亮屏事件(SCREEN_ON)— 设备屏幕点亮时推送事件
  • 熄屏事件(SCREEN_OFF)— 设备屏幕熄灭时推送事件
  • 解锁事件(SCREEN_UNLOCKED)— 用户解锁设备时推送事件
  • 后台运行监听— 应用退到后台后仍持续接收屏幕状态事件
  • 统一事件流 API— 通过Stream<ScreenStateEvent>消费所有事件

该三方库最初支持Android / iOS两个平台,本次任务将其适配到OpenHarmony / HarmonyOS平台。

项目地址:https://atomgit.com/oh-flutter/screen_state

1.2 适配目标

维度要求
功能一致性亮屏/熄屏/解锁三类事件与 Android、iOS 行为一致
Dart 层零改动API 形态(screenStateStreamScreenStateEvent)保持不变,Dart 层仅补平台判断
性能事件订阅按需创建(onListen)、及时释放(onCancel),避免资源泄漏
工程规范遵循 Flutter OHOS 插件标准结构(ohos/HAR 模块 +pubspec.yaml注册 + example 工程)

二、适配路线图

整个适配分为 4 个阶段:

第 1 阶段:适配评估 ── 必要性评估、阅读 Android/iOS 原生实现、确认鸿蒙侧等价 API 第 2 阶段:原生实现 ── 创建 ohos/ 目录,编写 ArkTS ScreenStatePlugin(EventChannel) 第 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.dart):

enumScreenStateEvent{screenUnlocked,screenOn,screenOff;// Android 返回 intent action;iOS/OHOS 返回短名称Stringgetname{...}// 两种名称形式都可解析staticScreenStateEventfromName(Stringname){switch(name){case'SCREEN_UNLOCKED':case'android.intent.action.USER_PRESENT':returnScreenStateEvent.screenUnlocked;// ...}}}Stream<ScreenStateEvent>getscreenStateStream=>_screenStateStream??=Platform.isAndroid||Platform.isIOS// ← 需加 Platform.isOhos?_screenStateStream??=_eventChannel.receiveBroadcastStream().map((event)=>ScreenStateEvent.fromName(event)):Stream<ScreenStateEvent>.empty();

Android 侧实现(Kotlin):

// ScreenStatePlugin.ktpublicclassScreenStatePlugin:FlutterPlugin,EventChannel.StreamHandler{overridefunonAttachedToEngine(binding:FlutterPlugin.FlutterPluginBinding){eventChannel=EventChannel(binding.binaryMessenger,"screenStateEvents")context=binding.applicationContext eventChannel.setStreamHandler(this)}overridefunonListen(arguments:Any?,events:EventChannel.EventSink?){screenReceiver=ScreenReceiver(events)valfilter=IntentFilter()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 action,iOS 为短名称)
生命周期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_PRESENTonListenonCancel
OHOScommonEventManager订阅COMMON_EVENT_SCREEN_ON/OFF/SCREEN_UNLOCKED/USER_PRESENTonListenonCancel
2.4 ArkTS 核心实现(ScreenStatePlugin.ets)
import{EventChannel,EventSink,FlutterPlugin,FlutterPluginBinding,Log,StreamHandler,}from'@ohos/flutter_ohos';importcommonEventManagerfrom'@ohos.commonEventManager';import{BusinessError}from'@kit.BasicServicesKit';constCHANNEL_NAME:string='screenStateEvents';// 将系统公共事件 id 映射为 Dart 层可识别的事件名functiontoScreenStateEvent(eventId:string):string{switch(eventId){casecommonEventManager.Support.COMMON_EVENT_SCREEN_ON:return'SCREEN_ON';casecommonEventManager.Support.COMMON_EVENT_SCREEN_OFF:return'SCREEN_OFF';casecommonEventManager.Support.COMMON_EVENT_SCREEN_UNLOCKED:casecommonEventManager.Support.COMMON_EVENT_USER_PRESENT:// 兼容旧版本return'SCREEN_UNLOCKED';default:returneventId;}}exportdefaultclassScreenStatePluginimplementsFlutterPlugin,StreamHandler{privateeventChannel:EventChannel|null=null;privateeventSink:EventSink|null=null;privatesubscriber:commonEventManager.CommonEventSubscriber|null=null;getUniqueClassName():string{return'ScreenStatePlugin';// 必须与 pubspec.yaml 的 pluginClass 一致}onAttachedToEngine(binding:FlutterPluginBinding):void{this.eventChannel=newEventChannel(binding.getBinaryMessenger(),CHANNEL_NAME);this.eventChannel.setStreamHandler(this);}onDetachedFromEngine(binding:FlutterPluginBinding):void{this.eventChannel?.setStreamHandler(null);this.eventChannel=null;this.unsubscribeScreenEvents();}onListen(args:Object,events:EventSink):void{this.eventSink=events;this.subscribeScreenEvents();}onCancel(args:Object):void{this.eventSink=null;this.unsubscribeScreenEvents();}privateasyncsubscribeScreenEvents():Promise<void>{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.subscriber=awaitcommonEventManager.createSubscriber(subscribeInfo);commonEventManager.subscribe(this.subscriber,(err:BusinessError,data:commonEventManager.CommonEventData)=>{if(err||this.eventSink==null)return;leteventName:string=toScreenStateEvent(data.event);this.eventSink.success(eventName);});}catch(error){Log.e(TAG,'createSubscriber error: '+JSON.stringify(error));}}privateunsubscribeScreenEvents():void{if(this.subscriber==null)return;commonEventManager.unsubscribe(this.subscriber);this.subscriber=null;}}
2.5 实现差异详解

适配中遇到的最大差异是事件名的归一化:Android 直接推送intent.action(如android.intent.action.SCREEN_ON),而 OHOS 的系统公共事件 id 是usual.event.SCREEN_ON形式。好在 Dart 层ScreenStateEvent.fromName只认识SCREEN_ON/SCREEN_OFF/SCREEN_UNLOCKEDandroid.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.dartscreenStateStream的平台守卫增加Platform.isOhos

Stream<ScreenStateEvent>getscreenStateStream=>_screenStateStream??=Platform.isAndroid||Platform.isIOS||Platform.isOhos?_screenStateStream??=_eventChannel.receiveBroadcastStream().map((event)=>ScreenStateEvent.fromName(event)):Stream<ScreenStateEvent>.empty();

第 4 阶段:示例验证

4.1 生成 OHOS 示例工程

example/目录下执行 Flutter 官方命令生成 OHOS 宿主工程:

flutter create.--platforms=ohos

该命令自动生成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}from'@ohos/flutter_ohos';importScreenStatePluginfrom'screen_state';// ← 从插件包导入exportclassGeneratedPluginRegistrant{staticregisterWith(flutterEngine:FlutterEngine){try{flutterEngine.getPlugins()?.add(newScreenStatePlugin());}catch(e){...}}}
4.2 构建验证

使用 DevEco Studio 的 hvigor 命令行构建 HAP:

nodehvigorw.js--modemodule-pmodule=entry@default-pproduct=default\-prequiredDeviceType=phone assembleHap--analyze=normal--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()
事件源BroadcastReceiver+IntentFiltercommonEventManager.createSubscriber+subscribe
事件发送eventSink?.success(intent.action)eventSink.success(eventName)
注销时机onCancelunregisterReceiveronCancelunsubscribe

4.2 关键 ArkTS 语法差异

Android 语法ArkTS 语法备注
import io.flutter.plugin.common.EventChannelimport { EventChannel } from '@ohos/flutter_ohos'OHOS 使用模块化导入
override fun onListen(arguments: Any?, events: EventSink?)onListen(args: Object, events: EventSink): void参数类型Any?Object
context.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

维护策略:保留两个订阅,避免老设备上解锁事件丢失。

决策 5:Dart 层零改动(仅补平台判断)

Dart 公共 API(ScreenScreenStateEventscreenStateStream)完全保持原样,仅将平台守卫从Platform.isAndroid || Platform.isIOS扩展为|| Platform.isOhos。示例工程则补充TargetPlatform.ohos判断——这是排查"鸿蒙不生效"问题时的关键发现:example 的_isSupportedPlatform未含 ohos,导致 UI 层从未调用startListening()

维护策略:所有平台相关判断集中在_isSupportedPlatform/screenStateStream守卫中,便于统一维护。


六、测试与验证

测试环境
项目版本
Flutter3.41.10-ohos-1.0.0
Dart3.11.5
HarmonyOS SDK26.0.0(API 26)
IDEDevEco Studio 26.0.0
设备 ROMALN-AL00 7.0.0.105(SP6C00E105R4P3)
验证要点
  1. 静态分析flutter analyze通过,无警告无错误。
  2. 编译验证— hvigorassembleHap构建成功(43 tasks),产出entry-default-unsigned.hap
  3. 插件注册GeneratedPluginRegistrant.ets正确导入并注册ScreenStatePluginimport ScreenStatePlugin from 'screen_state')。
  4. 事件通道契约— OHOS 侧EventChannel('screenStateEvents')与 Dart 侧EventChannel('screenStateEvents')通道名一致。
  5. 真机行为(待实测)— 配置签名后连真机,锁屏/解锁/熄屏时 UI 日志应输出SCREEN_OFF/SCREEN_ON/SCREEN_UNLOCKED事件。

七、运行效果

适配完成并通过构建验证。运行截图需在真机签名安装后通过以下命令获取(真机 ALN-AL00 已连接):

flutter screenshot-d<device_ip>:<port>


八、遗留问题与改进方向

已知问题
  1. 进程存活依赖— 屏幕事件仅在应用进程存活时可达;用户强杀应用后公共事件订阅失效(与 Android 行为一致)。
  2. USER_PRESENT 弃用— 新版 HarmonyOS 弃用COMMON_EVENT_USER_PRESENT,已通过同时订阅COMMON_EVENT_SCREEN_UNLOCKED兼容,但长期建议移除旧事件源。
  3. 签名依赖— 示例工程需配置签名(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 个),提交347a4d27(+1174 / -25)。Dart 公共 API 和其他平台的代码完全不受影响——这正是 Flutter 跨平台三方库生态的魅力所在。

回顾整个适配过程,三个"坑"值得记录:

踩坑点现象根因与解法
module.json5 权限 schema 校验失败reason字段必须为$string:资源引用或含{}占位符权限 reason 需引用 string 资源
权限不在 SDK 预定义RECEIVE_SCREEN_EVENTS声明报 00303221屏幕公共事件订阅无需权限,直接移除声明
鸿蒙上"不生效"example 从未收到事件根因在example 的_isSupportedPlatform未含 ohos,UI 层从未订阅;同时修复插件补订SCREEN_UNLOCKED事件

参考文档

  • screen_state 官方仓库
  • 本适配仓库(AtomGit)
  • HarmonyOS Flutter 适配指南
  • @ohos.commonEventManager API 参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询