
1. 先把核心链路说清楚Flutter 是怎么跑到 OpenHarmony 上的如果你和我一样第一次在 Flutter 工程里执行flutter build hap时盯着屏幕等了几分钟最后拿到一个.hap文件第一反应多半是这个文件到底是怎么从那一堆 dart 和 ets 文件里变出来的我在把项目迁移到 OpenHarmony 平台之前也以为只是“加一个构建目标”而已真正动手才发现这背后的工程目录结构、构建工具链和产物组织形式跟 Android/iOS 都有不少微妙的差异。这篇就是把我从环境搭建到编译打包、再到踩坑排错的完整过程整理出来给准备用 Flutter 编译开发 OpenHarmony 工程的同学做参考。先说结论OpenHarmony 现在能跑 Flutter靠的不是原版 Flutter SDK而是 OpenHarmony SIG 维护的 flutter_flutter 分支。这个分支在 Flutter 官方工具链里加入了 ohos 这个 target用来生成 OpenHarmony 工程骨架并支持把 Dart 代码、Flutter engine 和原生插件一起打包成 HAPHarmonyOS Ability Package。换句话说你写的还是 Dart跑的还是 Flutter 那套自绘渲染引擎但宿主环境换成了 OpenHarmony 的 Ability 生命周期。1.1 一个容易误解的事实原版 Flutter SDK 编译不出 HAP很多人刚接触时会问我已经装好 Flutter 了为什么flutter create出来的工程里没有 ohos 目录原因很简单原版 Flutter SDK 根本不认识 OpenHarmony 这个平台。你需要在环境变量里把 flutter 指向 OpenHarmony SIG 发布的 flutter_flutter 分支然后执行flutter doctor时才会出现 ohos 相关的状态项创建工程时也才能带上--platforms ohos这样的参数。这个分支本质上是在 flutter_tools 层面加了 ohos 平台支持包括工程模板、构建命令、打包逻辑。所以版本对齐非常关键flutter_flutter 分支的版本要和你本地的 OpenHarmony SDK 版本配套。版本错配的时候最常见的表现就是创建工程能成功但构建时报各种“找不到 Native API”或者“so 库加载失败”的错。我后面会在常见问题里专门展开。1.2 Flutter 与 OpenHarmony 的对接层从 Dart 到 ArkTS 的桥接思路运行时链路是理解整个工程目录的关键。OpenHarmony 上的 Flutter 应用本质上是一个 OpenHarmony 应用进程里跑了一个 Flutter engine。EntryAbility加载一个 Flutter 容器页Flutter engine 以动态库的形式打进 HAPDart 代码通过 engine 执行UI 由 Flutter 自绘引擎渲染不走 ArkUI 的组件树。你在工程里写的ets文件主要负责 Ability 生命周期、系统能力接入和与 Dart 侧的信令交互而真正业务界面基本都在lib目录的 Dart 代码里。这种模型带来的直接影响是工程目录会同时存在 Flutter 和 OpenHarmony 两套原生骨架。你既要维护pubspec.yaml的依赖也要维护oh-package.json5和module.json5这些 OpenHarmony 侧的配置。很多首次接触的人就是被这个“双轨制”搞晕的。2. 环境准备版本对齐、工具链安装、初始化一个可编译的工程2.1 工具链清单与版本匹配关系我的建议是先把下面这几样东西装齐再谈创建工程组件作用我当前使用的版本区间flutter_flutterOpenHarmony 分支提供 ohos 平台构建能力跟随 SIG 发布的最新 release 分支OpenHarmony SDK提供 ArkTS 编译、SDK API、签名工具5.0 系列对应 API 12DevEco StudioIDE主要用来管理 SDK、签名和真机调试5.0 及以上ohpmOpenHarmony 包管理器安装原生依赖随 DevEco Studio 或独立安装hvigor构建工具执行 HAP 打包任务随工程模板声明版本Node.jshvigor 脚本运行依赖建议 18 以上这里要特别强调版本匹配不是只看“最新”而是看 flutter_flutter 分支的说明文档里推荐的组合。官方 README 一般会写清楚当前分支适配 OpenHarmony 的哪个 API Level。我自己就常年踩这个坑升级 OpenHarmony SDK 后忘了同步升级 flutter_flutter 分支结果构建出的 HAP 在真机上启动后直接白屏。2.2 环境变量配置与首次 create 工程环境变量方面除了把 flutter 的 bin 目录加进 PATH还需要确认 OpenHarmony SDK 的本地路径能被构建工具找到。我习惯在用户环境变量里显式声明OHOS_SDK_HOME指向 DevEco Studio 内置的 SDK 目录如果你用命令行工具链建议把 ohpm 和 hvigor 的 bin 目录也一起加进 PATH。配置完环境变量有个很常见的坑新开的终端才能生效已经在跑的终端窗口里执行flutter --version还是老版本。这不是你没配置好而是 PATH 的生效机制就是如此。重开终端后可以用下面几条命令快速验证flutter --version flutter doctor -v ohpm --versionflutter doctor -v输出里如果能看到 ohos 相关项说明分支切换成功如果没看到大概率是 flutter SDK 路径没有切到 flutter_flutter 分支。接下来创建工程flutter create --platforms ohos --org com.example my_app cd my_app创建完成后查看工程根目录你会发现多了一个ohos目录这就是 OpenHarmony 原生工程的载体。如果创建时忘了加--platforms ohos可以回到根目录补执行flutter create --platforms ohos .但注意不要覆盖已有代码。验证工程能否跑起来最直接的方式是构建一个 debug 版 HAPflutter build hap --debug第一次构建会拉取 Gradle 依赖、hvigor 依赖还有 Flutter engine 的预编译产物时间比较长是正常的。构建成功后用 DevEco Studio 连接真机或模拟器安装即可。2.3 从创建工程到跑起 Demo 的完整验证路径我建议第一次别急着写业务代码先把默认模板跑通。跑通的意义在于环境链路是通的后续出了问题可以排除“工具链没装对”这个因素专心查业务代码。跑通 Demo 的步骤拆开来是flutter create --platforms ohos创建工程。flutter build hap --debug构建出可安装 HAP。DevEco Studio 打开工程配置签名调试可以勾选自动签名。连接真机点击运行看到默认计数器页面就是成功。这一步如果失败不要继续往下写业务代码先回头排查工具链版本。我在第 5 章列了一些高频报错可以先对照看看。3. 工程目录逐层拆解从根目录到 ohos 子工程有哪些“暗桩”3.1 根目录pubspec.yaml、.flutter-plugins-dependencies 与平台目录的对应关系Flutter 工程根目录的重要性不需要多讲但在 OpenHarmony 适配场景下有几个文件需要额外关注。pubspec.yaml除了声明 Dart 依赖还决定了 Flutter 插件的加载范围。当你执行flutter pub get后工程根目录会生成.flutter-plugins-dependencies文件这个 JSON 文件里记录了所有启用的插件及其各平台实现路径。点击进去能看到ohos字段它指向插件包里的 ohos 原生实现。如果你引入了一个第三方 Flutter 插件但发现构建 HAP 时没有把对应的原生代码编译进去十有八九是这个文件里没有 ohos 实现信息——原因可能是插件本身没提供 ohos 支持或者插件版本太旧。再看平台目录。标准 Flutter 工程里android、ios目录对应各平台的原生外壳在 OpenHarmony 适配分支下多出来的ohos目录承担了类似职责。三者并列存在互不干扰。但要注意.metadata这个隐藏文件里记录了当前工程的 Flutter 版本和生成工具版本如果你切了 flutter_flutter 的不同分支建议重新执行一次flutter pub get必要时手动检查这个文件里的版本信息避免遗留旧数据。3.2 ohos 子工程HAP 的构造骨架ohos目录是整个工程里最值得花时间搞清楚的部分。它的结构跟 DevEco Studio 创建的 OpenHarmony 工程基本一致ohos/ ├── AppScope/ │ ├── app.json5 │ └── resources/ ├── entry/ │ ├── build/ │ ├── libs/ │ ├── oh-package.json5 │ ├── build-profile.json5 │ ├── hvigorfile.ts │ └── src/main/ │ ├── module.json5 │ ├── ets/ │ ├── resources/ │ └── ... ├── build-profile.json5 ├── hvigorfile.ts ├── oh-package.json5 └── local.propertiesAppScope是应用级配置app.json5里是应用包名、版本号、icon 等全局信息。entry是默认主模块对应一个可独立运行的 HAP。如果你后续要拆多个模块可以在这个层级下继续加模块目录。build-profile.json5分两个层级外层工程级的负责配置签名信息、模块列表和 product 维度entry 内层模块级的负责当前模块的编译配置。签名文件通常在ohos/entry/build-profile.json5里通过signingConfigs引用DevEco Studio 的自动签名会帮你在~/.ohos/config/下维护个人信息文件不要手动改这些 local 配置除非你知道自己在做什么。module.json5是最容易出问题的文件。它声明了 Ability、权限和 extension 信息。比如要接入相机、图库、支付这类系统能力需要在这里加requestPermissions权限声明。Flutter 插件机制在 OpenHarmony 侧也是通过这个文件里的 extension 配置来注册的插件开发者在文档里一般会注明需要在module.json5中添加什么片段漏了这一段插件编译能过但运行时调用会直接失败。3.3 lib 目录组织与原生资源如何联动Dart 侧的lib目录组织决定了后续业务扩展和原生桥接的复杂度。我自己的习惯是分成三层lib/pages/页面级代码只管 UI 和交互。lib/services/数据服务和平台通道封装比如本地数据库、后端同步、网络请求。lib/platform/平台通道的接口定义和实现分发逻辑。为什么要单独拆platform层因为 OpenHarmony 适配意味着你想调用的某些系统能力图库、支付、推送没有现成 pub 包需要自己写 platform channel。把接口隔离在platform/目录下Dart 侧业务只依赖抽象接口实现分别在ohos/entry/src/main/ets/里用原生代码完成。这样后续切换平台或者升级原生实现都不需要改业务页面。原生资源走的是 OpenHarmony 的资源管理机制而不是 Flutter 的assets。比如你要在原生侧显示一个启动图图片放到entry/src/main/resources/base/media/下string 配置放到base/element/string.json。Flutter 侧的图片等资源依然放在工程根目录的assets里通过pubspec.yaml声明。两套资源体系完全独立记住这个规则找资源时就不会满工程乱翻。3.4 Android/iOS 目录与 ohos 目录的异同做个对比方便有 Android 基础的读者快速迁移理解功能Android 目录ohos 目录应用级配置android/app/build.gradleAppScope/app.json5build-profile.json5模块清单AndroidManifest.xmlsrc/main/module.json5入口组件MainActivityEntryAbility原生代码app/src/main/java/src/main/ets/资源文件app/src/main/res/src/main/resources/签名文件keystorep12 / cer / p7b包管理器Gradleohpm hvigor结构上可以说高度对应但在构建链路细节上完全不同。Android 用 Gradle 构建 APKOpenHarmony 用 hvigor 构建 HAP。你在 Flutter 里执行的flutter build hap实际上就是 flutter_tools 调用 hvigor 的封装。理解这个关系后面看日志排错会快很多。4. 一次完整编译产物在目录间如何流转并最终打成 HAP4.1 从 flutter build hap 到 HAP 落盘的关键流程命令行敲下flutter build hap --release之后构建链路大致是这样走的flutter pub get解析 Dart 依赖生成.flutter-plugins-dependencies。Dart 代码编译。release 模式走 AOT 编译产出libapp.sodebug 模式产出kernel_blob.bin。Flutter engine 和插件原生代码参与编译。插件里ohos/目录下的代码会被 hvigor 编译成对应的.so库。hvigor 读取module.json5、build-profile.json5、资源和签名配置把所有产物按 OpenHarmony 规范打包成 HAP。最终 HAP 落盘到build/ohos/或ohos/entry/build/下。从工程目录的视角看这个流程里最关键的是第 2 步和第 3 步的产物去向。Flutter 的 AOT 编译产物libapp.so会合并进 HAP 的libs/目录插件编译出的.so也会按架构放在对应目录。如果你自定义了某个插件的原生实现改完代码却发现 HAP 里没有生效先检查插件目录下有没有ohos子目录、构建产物有没有更新。4.2 构建产物目录里到底有什么以我本地一个工程为例构建完成后主要产物分布在这几个地方build/ ├── flutter-build/ # Flutter 中间产物 │ ├── app.so # AOT 编译产物 │ └── flutter_assets/ # Dart 侧资源 ohos/entry/build/ ├── default/ │ ├── outputs/ # 最终的 HAP 包 │ ├── intermediate/ # hvigor 中间产物 │ └── ...拿到 HAP 后你可以用 DevEco Studio 自带的工具或直接改后缀为 zip 打开看结构。一个典型的 release HAP 里面会包含内容说明libs/arm64-v8a/各种.so包括 libflutter.so、libapp.so、插件 soets/编译后的 ArkTS 字节码resources/OpenHarmony 侧资源module.json编译后的模块配置pack.info打包信息看到这个结构你就明白为什么flutter build hap能一次搞定它把 Dart 运行时、Flutter 引擎和 OpenHarmony 原生外壳全部融合到了一个包体里。4.3 调试模式与 release 模式的差异调试模式下Dart 代码不会提前 AOT 编译而是以kernel_blob.bin的形式打进 HAP配合flutter attach实现热重载。因此 debug HAP 的体积比 release 大不少启动速度也会慢一些这是正常现象。有个细节值得注意OpenHarmony 上 Flutter 的热重载前提是工程里的module.json5和插件注册没有被改坏。我遇到过一次热重载失效排查了半天最后发现是module.json5里某个插件 extension 配置被 DevEco Studio 自动格式化时调整了位置重新声明后恢复正常。release 模式下则是完全 AOTFlutter 引擎执行的是机器码性能和启动速度都更接近原生应用。日常开发用 debug发版一定用 release这个习惯在 OpenHarmony 工程里同样适用。5. 编译与运行阶段的高频报错根因、排查链路与规避方案5.1 版本错配引发的“依赖下载不下来”与 Gradle 插件报错先说我遇到最多的一类问题版本错配。具体表现有两种。第一种ohpm install或flutter pub get时拉取依赖失败报网络或校验错误。OpenHarmony 生态的包管理走的是 ohpm 仓库国内网络环境下偶尔会有仓库地址不通的问题。处理方式是在~/.ohpm/.ohpmrc里配置官方推荐的镜像源然后清理本地缓存重新 install。第二种执行构建时报 Gradle 相关的错误。比如下面这类提示You are applying Flutters main Gradle plugin imperatively using the apply script这个报错一般不是 OpenHarmony 工程本身的问题而是 Flutter 分支版本和旧版 Android 缓存配置发生冲突的典型表现。升级 Flutter 分支后老的android/settings.gradle或android/build.gradle里写死了旧的插件应用方式构建时互相干扰。排查思路是检查android/settings.gradle中的 plugin 配置是否和当前 Flutter 版本匹配。如果不需要 Android 构建直接把android目录迁移或重生成一份。执行flutter clean删掉android/.gradle和build缓存重新构建。由于我们只关心 OpenHarmony 目标很多 Android 侧的构建兼容问题可以绕过不用死磕。5.2 接入鸿蒙原生能力时的配置问题以图库、IAP 为例用 Flutter 调用鸿蒙的图库是社区里问得非常多的问题。思路很明确走 platform channel。Dart 侧用MethodChannel发消息原生侧在EntryAbility或专门建的PhotoService.ets里接收消息调用 OpenHarmony 的 PhotoViewPicker API再把结果传回 Dart。工程配置上有一处非常容易遗漏module.json5里必须声明对应的权限。{ module: { requestPermissions: [ { name: ohos.permission.READ_IMAGEVIDEO } ] } }漏掉权限清单编译不会报错但点击按钮后页面无响应日志里会出现权限拒绝的信息。排查这类问题最有效的方式是把 DevEco Studio 的 HiLog 打开按进程过滤直接搜Permission关键字。IAP 支付类似。OpenHarmony 侧的支付 SDK 需要你在module.json5声明对应权限同时在oh-package.json5里引入支付 SDK 依赖。如果你只在 pub 层找了某个支付插件发现没法拉起支付先检查插件是否实现了 ohos 端再看 module 配置是否完整。很多支付插件只提供了 Android/iOS 实现在 OpenHarmony 上需要自己对接原生 SDK这种情况下插件的ohos目录里应该有原生适配代码。这类问题的通用排查顺序是确认插件有没有 ohos 实现。确认module.json5权限和 extension 声明完整。写一个最简的测试页面用一个固定 method 名调用原生逻辑验证通道通不通。用 HiLog 查看原生侧异常。5.3 资源文件改动不生效与热重载失效的处理思路有同学在社区里反馈说修改了资源文件里的 HTML 或配置构建后界面没变化怀疑是构建缓存的问题。这个现象在 OpenHarmony 工程里我遇到过几次大多数情况下确实和增量构建缓存有关。处理方法是分层排查确认改的是 Flutter 侧资源还是 OpenHarmony 侧资源。Flutter 侧资源改动后flutter clean再重新构建即可OpenHarmony 侧资源改动后需要触发 hvigor 的重新编译有时候要手动删掉ohos/entry/build下的缓存目录。确认资源文件命名是否符合规范资源名大小写或非法字符可能导致编译时资源被静默忽略。如果界面没变化但日志正常用 HAP 解包检查 resources 里内容是否更新。热重载失效的另一个常见来源是module.json5被 DevEco Studio 和 flutter_tools 两边同时维护偶尔产生冲突。我的做法是原生侧配置统一在 DevEco Studio 里改Dart 侧统一在命令行或编辑器里改避免两个工具交叉写同一个文件的时间窗口。6. 我在目录结构维护上的一些长期习惯6.1 目录分层与多模块管理的取舍OpenHarmony 工程的目录结构和 Android 类似支持多模块。但我的建议是除非你的工程确实有独立编译、独立升级的业务模块否则不要一上来就拆多个 module。多模块带来的构建链路复杂度指数上升尤其是 Flutter 插件和 hvigor 的配置交互很容易出现“模块 A 能编过模块 B 编不过”的诡异状态。我自己偏向用单模块 目录分包的方式组织原生代码ohos/entry/src/main/ets/ ├── entryability/ ├── pages/ ├── service/ └── plugin/service放系统能力封装plugin放 Flutter 插件对应的原生实现。这样既保持了职责清晰又不用承担多模块配置的额外成本。等业务规模真正到了需要独立模块的时候再按模块拆也不迟。关于本地数据库和后端同步很多 Flutter 项目会用到 sqlite 或 drift 这类方案在 OpenHarmony 上要确认插件是否支持 ohos 平台。我目前的做法是把数据访问层单独放到lib/services/database/下用 sqflite 或 drift 的抽象接口如果某个插件没有 ohos 实现就自己写一个基于 OpenHarmony 关系型数据库的适配层。目录结构的价值在这里就体现出来了适配层有明确的位置不会散落在各个页面里。6.2 给新人的上手清单与个人体会最后整理一份快速清单给第一次用 Flutter 编译开发 OpenHarmony 工程的同学确认用的是 OpenHarmony 适配版 Flutter SDK不是原版。确认 flutter_flutter 分支版本和 OpenHarmony SDK 版本匹配。flutter create --platforms ohos生成工程不要手动拼目录。先构建默认 Demo 跑通再写业务代码。原生配置改完注意检查module.json5权限和插件注册都在这。构建异常优先flutter clean 删缓存排除缓存干扰再查代码。我自己踩过最深的坑其实就是版本管理。Flutter 的 OpenHarmony 适配分支更新频率不算低团队协作时如果每个人拉的分支版本不一样很容易出现“我这边能编你那边编不过”的情况。建议在工程根目录用 git tag 或提交记录把 flutter_flutter 分支的版本锁定并在 README 里写清楚当前各工具链的版本组合。工程目录结构看着是静态的静态文件但它背后隐含的版本契约才是真正需要长期维护的东西。把这个约定做扎实后面所有编译问题都能少一半。