我从一个真实需求聊起:团队接到任务,要给 OpenHarmony 设备做一款“移动数据使用监管助手”,App 需要实时展示流量消耗、按应用排行、设置预警阈值,还要在后台保持统计。选型讨论阶段,团队内部吵了好几轮——有人坚持用 ArkTS/ArkUI 写原生,有人提议 C++ 造轮子,最后我们定了 Flutter for OpenHarmony。这个决定意味着主框架要重新设计、工程要重新初始化、一堆 Flutter 生态组件要逐个验证鸿蒙兼容性。这篇文章就记录我们落地“主框架”的全过程,包括为什么这么选型、目录怎么分、代码怎么搭、哪些坑是网上搜不到答案的,给正在用 Flutter 做 OpenHarmony 应用的同学一个可以直接参考的工程样板。
1. 这个App为什么要用Flutter来做OpenHarmony端
1.1 项目背景与“移动数据监管”的真实业务需求
“移动数据使用监管助手”听起来像个流量监控工具,实际做起来涉及的东西比想象中多。它要干的不只是显示“本月已用多少 GB”,而是四块硬需求。
第一,数据采集。系统层要持续统计蜂窝网络(SIM 卡)产生的上传下载流量,以及各应用各自消耗的流量。这个数据在 Android 上通常来自NetworkStatsManager,在 OpenHarmony 上则需要走原生侧的网络管理/统计相关接口(各版本 API 有差异,我们后面细说)。第二,预警机制。用户设置“本月 30GB 封顶”,到达 80%、100% 时要有提醒,这要求 App 能常驻或定期唤醒检查。第三,明细展示。按应用排序、按日维度折线图、套餐剩余天数推算,UI 交互量大。第四,权限与合规。涉及 SIM 卡信息、用量数据读取,在 OpenHarmony 的权限模型下要逐个申请、并在隐私政策里说明。
这些需求决定了主框架必须具备三个能力:稳定的后台任务通道(数据采集与预警)、灵活的 UI 容器(大量列表、图表、状态切换)、跨平台的可复用逻辑层(后续可能要出 Android 版)。Flutter 正好卡在这个点上:UI 层开发效率高,业务逻辑能在 Dart 侧下沉,原生侧只要提供一个稳定的平台通道即可。我们不需要为每个页面写两套代码。
1.2 为什么不是ArkTS/ArkUI,也不是纯C++
团队里最先被否掉的是纯 C++ 方案。不是说 C++ 不好,而是这个 App 的页面量太大,统计明细、图表、设置页、预警通知页,全用 C++ 和声明式 UI 手搓,人力成本直接翻倍,后续迭代也劝退新人。ArkTS/ArkUI 被否的原因更实际:我们团队 Dart/Flutter 技术储备更厚,而且这个 App 明确规划了 Android 版本——OpenHarmony 和 Android 两个平台用同一套 Flutter 代码,至少能省下 60% 的双端开发量。更重要的是,OpenHarmony 的 ArkUI 虽然已经相当成熟,但生态里的第三方图表库、日期选择器、存储封装,远没有 Flutter 这么丰富,我们不想在框架基建上耗尽时间。
这里也顺便回应一个社区高频问题——“Flutter 和其他前端框架的优缺点在鸿蒙场景下怎么选”。如果只做 OpenHarmony 单端、团队熟悉 ArkTS、交互特别依赖鸿蒙分布式能力,ArkUI 确实更顺。但如果你是多端团队、要快速出产品、需要现成的第三方组件生态,Flutter for OpenHarmony 就是更务实的选择。它确实有老生常谈的缺点:包体积偏大、Web 渲染性能不如原生,可对“工具类 App”来说这些不是核心矛盾。核心矛盾是需求变更速度 vs 开发产能,Flutter 的优势正在于此。
1.3 主框架阶段的目标范围
我们把这个 App 的落地拆成三个阶段。第一阶段(本篇)只做主框架:工程初始化、目录分层、底部导航、路由、状态管理、主题、平台通道骨架、基础权限申请流程。第二阶段做数据采集与统计:对接 OpenHarmony 原生侧 API 拿到真实流量数据,设计订阅/查询机制。第三阶段做交互与优化:图表、排行、预警通知、XTS 认证、性能调优。这样拆的好处是每一篇都可以独立复现,读者不用等到三个月后看到全部代码才能跑起来。主框架拿到手里,先跑通壳子,后面往里面填业务就顺了。
2. 主框架设计:先把骨头搭对
2.1 分层架构与目录结构
我见过太多 Flutter 项目从“全部塞在 lib/page”开始,三个月后重构成本高到崩溃。这个项目我们从第一行代码就定了分层思路,核心是四层结构 + 单向依赖。
- core 层:完全与业务无关的基础设施,包括网络封装、本地存储封装、日志、常量、主题、通用组件。这层不允许 import 任何页面代码。
- data 层:一切数据来源,包括平台通道的封装(
ChannelManager)、数据模型、仓库(Repository)接口与实现。页面不知道数据到底是来自原生通道还是本地缓存,它只面向 repository 编程。 - domain 层:轻量业务逻辑,如额度计算(把套餐总量、已用量、剩余天数换算成百分比和日均限额)、预警判断。该层保持纯 Dart,不依赖 Flutter SDK 和底层数据源,方便单测。
- presentation 层:页面、状态管理、路由。每个功能模块一个目录,比如
monitor/(监控首页)、detail/(明细)、ranking/(排行)、settings/(设置)。
目录结构长这样:
lib/ ├── main.dart ├── app.dart ├── core/ │ ├── constants/ │ ├── theme/ │ ├── widgets/ │ ├── storage/ │ └── network/ ├── data/ │ ├── models/ │ ├── datasource/ │ │ └── platform_channel/ │ └── repository/ ├── domain/ │ ├── entities/ │ ├── usecases/ │ └── repository_interface.dart └── presentation/ ├── routes/ ├── pages/ │ ├── monitor/ │ ├── detail/ │ ├── ranking/ │ └── settings/ └── state/单看目录是枯燥的,但当你写TrafficRepository的时候,如果发现它既要调网络又要调本地缓存还要调平台通道,就知道这个分层能救命。我们的硬规矩是:domain层不允许出现import 'package:flutter/...',这样月度额度计算逻辑可以直接用纯 Dart 单测覆盖,不用起模拟器就能验证边界条件。
2.2 核心依赖选型与版本策略
OpenHarmony 的 Flutter 生态和 Android/iOS 有个最大区别:pub.dev 上的绝大多数包没有 OpenHarmony 适配验证。我的决策原则是“尽量少、尽量稳、适配优先”。主框架阶段只引入五类依赖:
| 类型 | 选型 | 理由 |
|---|---|---|
| 状态管理 | Provider(+ 少量 ChangeNotifier) | 依赖极小、无代码生成、OpenHarmony 上兼容风险低 |
| 路由 | go_router | 声明式路由、支持 deep link,社区活跃 |
| 本地存储 | shared_preferences | 有官方 ohos 适配的 fork 可用,Key-Value 够用 |
| 图表 | fl_chart(后续第二篇引入) | 纯 Dart 绘制,无原生依赖,天然跨平台 |
| 日志 | 自研轻量 Logger | 避免引入过重依赖,OpenHarmony 上的原生日志输出要单独接 |
这里要踩一个很关键的坑:不要在 OpenHarmony 上盲信 pub.dev 的最新版本。Flutter for OpenHarmony 的分支一般落后官方主干几个小版本,有时候某个包在 Flutter 3.22 上正常,但 OpenHarmony 的移植版用的引擎可能卡在 3.7 或 3.10(和你拉的分支有关),导致二进制不兼容。我的做法是:先锁 Flutter 移植版版本(比如3.22.x-ohos),再看每个 pub 包的历史版本,找该 Flutter 版本发版时期的稳定版,而不是直接flutter pub add拿 latest。这一步能帮你避开 80% 莫名其妙的编译错误。
2.3 数据监管类 App 独有的模块预留
除了通用分层,我还专门为“移动数据监管”这个业务预埋了三个模块接口,这些在普通 App 主框架里往往被忽略,但对这个 App 是刚性需求。
第一个是常驻统计服务接口。流量统计不能只靠 App 在前台的时候拉取,用户切到后台、锁屏,SIM 卡的数据仍然在走。所以data层要预留一个TrafficCollector抽象,将来实现可能有两种:前台轮询(简单但不准)和原生侧后台任务上报(复杂但精确)。主框架阶段先把抽象定死:方法只有start(SubscriptionConfig config)、stop()、onData(Function callback),具体实现后面再说。
第二个是预警调度接口。流量到阈值要弹通知,这在 Flutter 端做不到负一屏常驻,得依赖 OpenHarmony 的公共通知能力。所以我们不在主框架里做具体实现,但domain层定义了ThresholdEvaluator,输入已用量和套餐配置,输出预警等级(无、80% 警告、100% 封顶提醒)。这个纯 Dart 类现在就写好,后面接通知只是响应它的输出。
第三个是权限状态门面。一个工具类 App 的首次体验极其重要,用户装完打开,第一屏就应该是“本月流量概览”,而不是权限弹窗连环 call。我们做了一个PermissionGate,集中管理所有权限的申请、拒绝回调、跳转设置页逻辑,主框架里保留它的空实现和页面占位,第二阶段往里填 OpenHarmony 的原生权限申请即可。
3. 环境准备与工程初始化(这步卡过的人最多)
3.1 Flutter for OpenHarmony 该拉哪个分支
先说结论,省得大家走弯路。OpenHarmony 的 Flutter 适配目前主要由社区和华为共建维护,核心仓库是flutter_flutter、flutter_engine和flutter_packages。你需要的是它们的ohos 分支,而不是 Flutter 官方主分支。
具体做法是分仓库拉取再手动指定版本:
git clone -b 3.22.x-ohos https://gitee.com/openharmony-sig/flutter_flutter.git git clone -b 3.22.x-ohos https://gitee.com/openharmony-sig/flutter_engine.git git clone -b 3.22.x-ohos https://gitee.com/openharmony-sig/flutter_packages.git这里有个非常重要的环境变量配置。OpenHarmony 的 Flutter SDK 需要知道 DevEco Studio 的 SDK 路径,同时为了避免 pub 从公网拉包失败,还要配置本地镜像。我实测稳定的组合是:
# 设置 OpenHarmony SDK 路径(换成你自己的 DevEco SDK 目录) export DEVECO_SDK_HOME=/path/to/DevEcoStudio/sdk export OHOS_SDK_HOME=$DEVECO_SDK_HOME/default/openharmony # 防止 pub 访问公网超时 export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn强调一句:OpenHarmony 移植版 Flutter 对本地环境非常敏感。如果你的机器之前装过标准 Flutter,PATH 里同时出现两个 flutter 会在创建工程时引发各种诡异问题(比如生成出android/目录但缺少ohos/目录)。我自己的做法是配置别名区分:标准 Flutter 单独保留在开发机上,OpenHarmony 专用 Flutter 放到另一个目录,用的时候用绝对路径调用。
3.2 创建带 ohos 目录的工程
配好环境后创建工程,不能直接flutter create my_app,你可能会得到没有ohos/目录的标准工程。正确姿势是(以 OpenHarmony 移植版 Flutter 3.22 为例):
# 先生成标准工程结构 /path/to/flutter_ohos/bin/flutter create --org com.example --project-name traffic_guard traffic_guard_app # 进入工程后添加 ohos 平台支持 cd traffic_guard_app /path/to/flutter_ohos/bin/flutter create --platforms ohos .执行完你应该看到工程里多出ohos/目录,里面包含entry/src/main/ets这类 OpenHarmony Stage 模型工程文件。这才是正确的起点。如果你发现flutter create没有--platforms ohos选项,大概率是环境变量配错了,检查flutter --version是否包含ohos字样的版本标识。
创建完成后,用 DevEco Studio 打开ohos/目录,它会自动识别 Stage 模型工程。首次同步 Gradle 依赖会比较慢,建议把 DevEco 的 SDK 源切换为华为镜像源。这个阶段你不需要写任何 OpenHarmony 原生代码,只要确认两点:一是默认 MainAbility 能启动并弹出 Flutter 页面;二是 DevEco 里能看到entry模块的构建产物配置。主框架阶段我们只和 Flutter 侧打交道,原生侧保持“最小可运行”就好。
3.3 构建产物 har/hap 与后续打包链路
社区里经常看到有人问“Flutter 能不能像原生那样直接出 hap”,答案是能,但要走两条链路。你可以在 DevEco Studio 里直接构建entry模块得到.hap包,这是最终可安装到 OpenHarmony 设备上的产物;也可以先把 Flutter 侧能力打成.har(OpenHarmony 的静态共享包),再集成到别的鸿蒙工程中。这和 Android 上 Flutter 可以打.aar给原生工程用是同一个思路。
我实测中最常遇到的坑是har 包的依赖重复。如果 Flutter 侧工程编译出的.har里已经包含了libflutter.so和引擎相关资源,宿主工程再引入一份就会资源冲突。解决办法是把宿主工程对 Flutter engine 的依赖设为provided或者直接在构建配置里排除重复的 so 文件。在 OpenHarmony 上出现这类问题的报错通常是“duplicate resource”或“multiple libs contain same symbol”。我们在主框架阶段就刻意验证了这个链路,避免后面接广告 SDK、埋点 SDK 时再来排查。
另外一个提示:打包要区分 Debug 和 Release。Debug 包用flutter run+ DevEco 的组合可以热重载,但性能差、包大。Release 包必须走flutter build hap或者 DevEco 的 release 构建,它会做 tree shaking。这个 App 后续涉及图表库和动画,包体积会明显放大,早点确认 release 构建流程能少踩很多发布前的坑。
4. 主框架代码落地:从壳子到能跑
4.1 底部导航与页面容器
移动数据监管助手的主界面用四 Tab 最合理:监控首页(本月概览 + 预警卡片)、流量明细(按日/月查询)、应用排行(各 App 消耗排序)、设置(套餐配置 + 关于)。
用 go_router 做底部导航时要注意一个细节:不要直接用ShellRoute套四个子路由,因为每次切换页面都会重建 IndexedStack 之外的页面状态。正确做法是在HomePage内部用IndexedStack保存四个子页面状态,go_router 只负责/home这一层路由。
class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int _currentIndex = 0; static const pages = [ MonitorPage(), DetailPage(), RankingPage(), SettingsPage(), ]; @override Widget build(BuildContext context) { return Scaffold( body: IndexedStack(index: _currentIndex, children: pages), bottomNavigationBar: NavigationBar( selectedIndex: _currentIndex, destinations: const [ NavigationDestination(icon: Icon(Icons.dashboard), label: '监控'), NavigationDestination(icon: Icon(Icons.list), label: '明细'), NavigationDestination(icon: Icon(Icons.leaderboard), label: '排行'), NavigationDestination(icon: Icon(Icons.settings), label: '设置'), ], onDestinationSelected: (index) => setState(() => _currentIndex = index), ), ); } }这段代码是你第一次跑通 OpenHarmony 真机的关键验证点——如果四个 Tab 能来回切换不崩溃、不闪退,说明 Flutter 渲染层和 OpenHarmony 的适配没问题。我建议你在写任何业务之前先跑一下这个壳子。
4.2 路由与状态管理
状态管理我们选了 Provider,理由前面说过:简单、稳。主框架里统一建立三个全局ChangeNotifier:
SettingsState:保存套餐总量、计费周期起始日、预警开关,落库到 shared_preferences。TrafficState:当前已用流量、今日增量、各应用排行,数据源暂时用 mock,第二篇换成真实通道。PermissionState:记录每个权限的授权状态,驱动首次进入的引导页。
必须注意的一点是 Provider 的初始化时机。OpenHarmony 的 Stage 模型下,Flutter 页面挂在 MainAbility 上,启动流程比 Android 慢,如果你在main()里同步初始化所有 Provider 依赖,很可能遇到“在 Build 阶段读取尚未初始化完成的存储”的异常。我们用的方案是提前await初始化:
void main() async { WidgetsFlutterBinding.ensureInitialized(); // 先初始化本地存储和用户配置 await StorageService.init(); runApp(const AppRoot()); }不要小看这个await,OpenHarmony 的shared_preferences插件在不同版本上的初始化延迟差异极大,极端情况下能差出几百毫秒。提前await可以保证SettingsState在页面构建时一定有数据可用。
4.3 平台通道的基础骨架:Flutter侧如何拿到“移动数据使用量”
移动数据使用量在 OpenHarmony 上不是一个 Flutter 侧能直接读到的数据,必须走平台通道。主框架阶段我们不实现真实采集,但要先把通道协议定好,否则后面写原生侧会到处补洞。
通道设计如下(MethodChannel):
class TrafficChannel { static const _channel = MethodChannel('traffic_guard/channel'); /// 获取当前 SIM 卡信息 static Future<Map<String, dynamic>?> getSimInfo() { return _channel.invokeMapMethod('getSimInfo'); } /// 获取今日总流量(bytes) static Future<int> getTodayTotalBytes() async { final value = await _channel.invokeMethod<int>('getTodayTotalBytes'); return value ?? 0; } /// 注册后台统计回调 static void registerTrafficCallback(void Function(dynamic event) onEvent) { _channel.setMethodCallHandler((call) async { if (call.method == 'onTrafficUpdate') { onEvent(call.arguments); } }); } }为什么通信协议要在主框架阶段就定?因为涉及原生侧权限申请、后台任务注册,后面改协议意味着两端联动,成本远高于现在。还有一点,MethodChannel 的 channel 名要有项目前缀。多个 Flutter 插件共存时,channel名重复会导致消息串线、回调没人处理。我用traffic_guard/channel而不是traffic,就是为了避免和社区通用插件撞名。
关于“Flutter 组件通信”这个高频问题,我不妨多说一句:组件通信和原生间通信不同,它主要靠InheritedWidget、Provider、Callback、Stream。在 OpenHarmony 上最容易踩的坑不是这些,而是 MethodChannel 回调里的线程问题——原生侧返回结果时不一定在主线程,Flutter 侧默认帮你切回主线程,没问题;但如果你用 EventChannel 做事件流,必须确认原生侧线程模型,否则你会遇到“数据到了但 UI 不刷新”的诡异 bug。
4.4 主题、深色模式和统一的卡片组件
工具类 App 最忌讳花里胡哨,但也不能毫无设计感。我们定义了一套基础设计令牌(Design Token),集中在core/theme里。颜色以数据可视化场景为主:主色用深蓝(传达可信赖感),预警色用琥珀、危险色用红色,背景用中性灰阶。
class AppColors { static const primary = Color(0xFF1B4B91); static const warning = Color(0xFFF5A623); static const danger = Color(0xFFE94F37); static const backgroundLight = Color(0xFFF7F9FC); static const backgroundDark = Color(0xFF17191C); }深色模式在主框架阶段就要做,原因很简单:这个 App 的监控首页很可能长时间亮屏,用户对暗色模式的需求极高。实现上用ThemeData.brightness+ColorScheme.fromSeed做双主题,并跟随系统设置。在 OpenHarmony 上跟系统设置有个前提——要先确认MediaQuery.platformBrightnessOf(context)能正确拿到系统亮度模式,有的移植版在早期版本里这里返回的值不准。稳妥做法是在SettingsState里加一个手动切换开关,默认跟随系统,用户可强制覆盖。
通用组件我们只做了三个,够用就好:TrafficCard(显示大数字流量的圆角卡片)、ProgressBarWithThreshold(带预警线的进度条)、EmptyPlaceholder(空数据占位)。这三个组件在 OpenHarmony 上一定要用RepaintBoundary包一层,尤其是ProgressBarWithThreshold——实测在低端设备上连续重绘会掉帧,包上之后好很多。
5. 实测中的崩溃与修复,附排查思路
5.1 e/flutter (31173): 未处理异常到底从哪来
很多同学第一次在 OpenHarmony 上跑 Flutter 工程,看到类似下面这行日志就慌了:
E/flutter (31173): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method getTodayTotalBytes on channel traffic_guard/channel)我第一次看到也以为是自己代码 bug,排查了半天才发现这行日志本质上是 Flutter 框架层的通用输出——任何 Dart 层的未捕获异常都会在dart_vm_initializer.cc这个位置打出来,后面的Unhandled Exception才是真实原因。上面的例子是典型的MissingPluginException:你调用了getTodayTotalBytes,但 OpenHarmony 原生侧还没有注册对应的 MethodChannel 实现。
排查思路不要乱:第一,看异常类型。MissingPluginException说明原生没实现;PlatformException说明原生实现了但抛了业务错误;TypeError说明返回 JSON 转模型失败。第二,看是首次启动必现还是偶发。必现的多是原生侧注册时机问题,偶发的要查异步回调。第三,如果日志只有flutter前缀而没给 Dart 堆栈,可以把debugPrint和FlutterError.onError接上,拿到完整 Dart 侧调用链:
void main() { FlutterError.onError = (details) { FlutterError.present(details); }; PlatformDispatcher.instance.onError = (error, stack) { debugPrint('uncaught: $error\n$stack'); return true; }; // ... }记住一点:在 OpenHarmony 上,Flutter 侧的异常日志格式和 Android 是一脉相承的,但真实堆栈往往在 device log 的另一段。用 DevEco 的 Log 面板过滤flutter和ohos两个 tag,能看到的线索比只看dart_vm_initializer.cc那行多得多。
5.2 新建项目后跑不起来的常见原因盘点
我统计了下团队和社区上反馈的“新建项目跑不起来”案例,集中在三个原因。
第一是Java/Gradle 环境残留冲突。如果你之前在本机跑过 Android 工程,Gradle 环境和 OpenHarmony 的 hvigor 环境可能互相干扰。典型报错是Could not find an installed version of Gradle或者SDK location not found。解决方法是清理~/.gradle缓存里的 OpenHarmony 相关模块,或者用 DevEco 自带的 JDK 重新配置JAVA_HOME。
第二是工程目录层级不对。很多人直接用 DevEco 打开了整个 Flutter 工程根目录,而它应该打开ohos/子目录。DevEco 靠build-profile.json5识别工程,根目录没有这个文件,于是构建任务找不到entry模块。
第三是签名配置缺失。OpenHarmony 应用安装到真机上必须有签名。新建工程的ohos/目录默认是 debug 签名配置,能跑模拟器;换真机时必须到 AppGallery Connect 申请调试证书,并更新build-profile.json5。这个环节最容易在“模拟器能跑,真机跑不起来”时被漏掉。
我推荐一个排查顺序:先flutter doctor确认 Flutter 侧健康;再在 DevEco 里手动构建entry,看 hvigor 输出;最后命令行安装hvigorw clean排除增量缓存问题。不要一上来就怀疑 Flutter 移植版本身,大多数“跑不起来”都是工程配置问题,不是引擎问题。
5.3 下拉刷新与列表滚动的性能优化
主框架里监控首页和排行页都用到了下拉刷新,这块在 OpenHarmony 上比 Android 更容易暴露问题。官方RefreshIndicator在 Flutter for OpenHarmony 上能跑,但低端设备下拉时会出现明显的阴影撕裂感。
一个有效的优化是把刷新指示器和列表分割开,而不是直接包整个 ListView:
RefreshIndicator( onRefresh: _loadData, child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverToBoxAdapter(child: _HeaderSection()), SliverList.builder(...), ], ), )把刷新和内容结构拆成 Sliver,刷新的触发区域更可控。另外,列表项尽量做成 const 构造,减少重建。实测在 RK3568 这类 OpenHarmony 开发板上,用const+SliverList组合,滚动帧率可以从 40fps 提升到接近 60fps。
还有一个小技巧:排行页的应用图标不要直接用Image.network或Image.asset裸加载,在 OpenHarmony 上图片解码是 CPU 密集操作。统一用precacheImage预加载 +cacheWidth限制解码尺寸,内存和帧率都会有明显改善。
5.4 Impeller 渲染引擎:OpenHarmony 上要不要开
Flutter 3.10 之后官方推动 Impeller 渲染引擎替代 Skia,但 OpenHarmony 移植版主框架阶段不建议开启 Impeller。原因很简单:Impeller 的 OpenGLES/Vulkan 后端适配需要大量图形栈验证,OpenHarmony 上各设备的 GPU 驱动差异又大,移植版默认走 Skia 有更充足的兼容性保障。我在开发板上尝试开启 Impeller 后,第一个页面能正常渲染,但切换到深色模式再切回浅色模式时出现过渲染花屏。
如果你的应用对动画渲染要求很高,可以等后续版本把 Impeller 的 OpenHarmony 后端稳定了再切。现阶段主框架用 Skia 是务实选择。这个取舍不需要纠结,追求稳定大于追求一点图形性能。
6. 上线前必须处理的资质与兼容问题
6.1 XTS 认证对主框架的影响
OpenHarmony 应用想上架到官方应用市场,要通过 XTS(X Test Suite)认证。它是一套兼容性测试集,会检查应用是否遵循 OpenHarmony 的接口规范和权限模型。主框架阶段如果不注意,后面认证会被一堆小问题卡住。
在主框架里就要做对的三件事:一是所有权限申请必须动态申请,不要在module.json5里声明完就完事,运行时要用abilityAccessCtrl走用户授权流程。二是有完整的隐私声明入口,设置页必须能跳到隐私政策页面。三是不能有测试残留,比如 debug 模式的入口、mock 数据开关,Release 包要确保全部关闭。
XTS 报告的失败项通常很直接,比如“权限声明与运行时行为不一致”“存在非公开 API 调用”。我们的处理办法是预留一个PermissionTrace工具,把所有权限申请的路径打日志,方便认证失败时回溯。
6.2 第三方组件的 ohos 适配检查清单
主框架依赖的包不多,但每个都要过一遍适配检查。我写了一个检查清单,每次引入新包都对照执行:
- 是否纯 Dart 实现。纯 Dart 的包(如 fl_chart)可以直接用;依赖原生代码的包要查它是否适配 ohos。
- 是否用了
dart:io中 OpenHarmony 不支持的 API。比如Process、部分 socket 实现可能受限。 - 是否有版本锁定。先看它的 pubspec 约束的 Flutter SDK 范围,再对照移植版 Flutter 支持范围。
- 是否存在资源冲突。打包后检查资源文件是否和
entry模块重复。
被卡住最多的通常是带原生插件的包,比如shared_preferences。社区里有专门的 ohos fork,你直接 pub 官方包在 Android 上没问题,在 OpenHarmony 上会报MissingPluginException。处理办法是把 pub 源切到 OpenHarmony SIG 的 fork 地址,或者直接把 fork 代码放到本地third_party目录。
6.3 从主框架到完整功能:后续迭代规划
主框架阶段结束的时候,项目应该具备:能跑通四个 Tab 页、有主题系统、有路由与状态管理、有平台通道协议、有权限申请占位、能打出 release hap 并且通过基础的设备验证。到这里,壳子是完整的、可演示的。
后续第二篇我会写数据采集层的真实实现:OpenHarmony 原生侧如何查询 SIM 信息、如何统计蜂窝流量、如何通过后台任务持续上报,以及 Flutter 侧 repository 层的完整封装。第三篇写交互进阶:图表、排行、预警通知、关于页的合规信息,再做一轮性能优化和 XTS 提交。这个规划的明确意图是让任何人都能按系列文章的节奏逐步完成整个 App,而不是只得到一个孤零零的工程模板。
开个玩笑说,主框架就像房子的承重墙和管线预埋,水电走对了,后面装什么功能区都顺。水电没走对,后面三天两头返工。我们在 Flutter for OpenHarmony 上踩过、填过的这些坑,希望你不用挨个再踩一遍。如果你在搭主框架时也遇到奇葩问题(尤其是 OpenHarmony 移植版的引擎级问题),欢迎在我博客评论区留个运行日志片段,我看到会恢复到具体报错。