开源鸿蒙和 Flutter 的组合在当前跨端开发圈子里讨论度很高。这两年我在几个实际项目里做过鸿蒙原生开发,也折腾过 Flutter 的跨端方案,算是摸过不少坑。这篇笔记我整理了从零搭建开源鸿蒙 Flutter 开发环境,到完成工程初始化、编译运行,再到用 git 管理代码提交的完整过程,覆盖 Day1 和 Day2 两天的实测内容。
如果你正准备入坑多端开发,或者在跨平台技术选型上犹豫,需要同时兼顾鸿蒙系统和其他移动平台,这篇笔记可以直接帮你在环境层面少走弯路。
1. 环境搭建细节与版本选型逻辑
1.1 起因和整体规划
这次动手的起因,是手头一个社区服务类的模拟项目需要覆盖 Android、iOS 以及开源鸿蒙系统。原有团队维护着两套原生代码,人力吃紧,每次多端对齐需求都像打仗。我本身对 Flutter 的跨端渲染和统一逻辑层有基础,加上开源鸿蒙在 Flutter 生态上的支持已经比两年前成熟很多,就决定做一个技术验证:用 Flutter 写一套业务代码,同时跑在移动双端和鸿蒙上。
整个验证计划分了三天:
- Day1 搭建基础开发环境,完成 Flutter 工程初始化,并编译开源鸿蒙版本的应用。
- Day2 编写一个相对完整的可交互页面,在鸿蒙模拟器和真机上跑通,通过 git 管理这套代码。
- Day3 做插件适配、性能摸底和结论梳理。
实际跑下来,Day1 和 Day2 遇到的问题比预想多,但都没有不可逾越的坎。整理出来就是下面这篇。
1.2 核心工具链与版本组合
开源鸿蒙的 Flutter 开发,本质上是使用 开源鸿蒙 官方维护的 OpenHarmony Flutter SDK,这给了开发者一个在 Flutter 框架下调用鸿蒙能力的路径。整体工具链涉及:
- 开源鸿蒙 SDK / DevEco Studio:负责鸿蒙原生侧的编译、打包、签名和应用运行。
- OpenHarmony Flutter SDK:某个版本的 Flutter SDK 提供鸿蒙和 Flutter 框架之间的桥接层。
- Flutter 引擎代码:如果用默认的 flutter SDK 打包,原生的 ArkUI 组件与 Skia 渲染层的衔接可能出现兼容问题,所以需要从开放原子开源基金会代码仓库拉取支持鸿蒙的 Flutter 引擎。
- 第三方依赖管理:通过 pub 仓库解析,同时可能在本地通过 git 依赖挂载,以锁定某个未正式发布的 SDK 分支。
我用到的版本组合可以参考这个表格:
| 组件 | 版本/来源 | 说明 |
|---|---|---|
| DevEco Studio | 5.x 及以上版本系列 | 内置 开源鸿蒙 SDK,对应 API 12及以上版本 的编译目标比较稳妥 |
| OpenHarmony SDK | 跟随 DevEco Studio 自动下载 | API 依赖、编译目标是鸿蒙子系统原生的 |
| Flutter SDK | 从开源基金会 fork 分支同步,当前使用 3.x 主线 | 重点关注对鸿蒙平台相关适配的提交是否已落在此分支 |
| Dart SDK | 由 Flutter SDK 自动关联 | 无需单独安装,避免环境 PATH 冲突 |
| git | 2.x 以上 | 用于版本管理,建议配置 Git 全局用户名和邮箱 |
有个点必须提醒:这里说的 Flutter SDK 和官方 Flutter 并不是同一个分支。官方 Flutter 目前还没有把鸿蒙作为一级平台目标,所以直接从官网下载的 Flutter SDK 是编译不出来 鸿蒙 应用的。必须切换到开源鸿蒙社区维护的 fork 分支或者用他们发布的二进制构建版本。这个版本选型搞错了,后面所有步骤全白搭。
1.3 为什么选这个方案而不是其他思路
当前在开源鸿蒙上做 Flutter 跨端,大概有三条路:
- 第一条:使用 ArkUI 原生开发,这是官方推荐的方式,稳定性和性能都是最好的,但代价是所有页面、交互、状态管理都要从头写一套,和原生移动端代码无法复用。
- 第二条:直接用 Flutter 最新版主线,做最基础的跨端。这条路在 Android / iOS 上没问题,但到鸿蒙端基本跑不起来,因为鸿蒙的图形栈、输入事件链和 Android 差异太大,Flutter 官方并没有做系统级适配。
- 第三条:使用支持鸿蒙的 Flutter SDK,业务层仍用 Dart,通过鸿蒙平台通道调用底层能力。跨端代码直接复用,鸿蒙原生能力通过 channel 暴露给 Dart 层。这套方案的核心收益是业务代码一份,双端对齐的维护成本大幅降低。
我最终选择了第三条路线,也是目前社区验证下来完成度最高的方案。实际写代码时,你会发现 UI 层面的复用度可以做到 90% 以上,因为大部分布局、交互、动画都是 Flutter 自己渲染的,和底层系统 UI 没什么关系。真正需要调用鸿蒙 API 的场景,比如权限申请、设备信息、推送、文件系统,会走 channel 或者插件机制。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念拆解
2.1 理解 Flutter 与开源鸿蒙的桥接原理
在实践开始前,先把桥接层的工作方式捋清楚,这会直接影响你后续排错。
Flutter 在鸿蒙上运行的架构,其实与在 Android / iOS 上并没有本质区别。底层是一套 C++ 实现的 Flutter 引擎,负责 UI 渲染、文字排版、事件处理、Dart 隔离区调度等。引擎之上是 Dart 层,你的业务代码都在这里。引擎之下是一层嵌入器(Flutter Engine Embedder),嵌入器负责把 Flutter 引擎挂载到宿主系统上,告诉引擎“你现在是在一个鸿蒙窗口里画东西”。
OpenHarmony Flutter SDK 的工作,就是在 OpenHarmony 系统上用 C++ 和 ArkTS 实现这个嵌入器,把 Flutter 生成的帧缓冲输出到鸿蒙的渲染链路中,同时把触摸事件、生命周期事件、平台消息管道接起来。
可以这样理解:如果把 Flutter 引擎比作一台游戏主机,Dart 代码就是游戏卡带,那么鸿蒙嵌入器就是主机电源线、显示线、手柄接收器。没有嵌入器,卡带插上也无法工作;嵌入器适配做不好,游戏卡带即使能运行也会出现画面异常、输入延迟、内存泄漏等问题。
这个架构决定了几个特性:
- UI 层的代码是跨端一致的,因为渲染依赖的是 Flutter 自己的 Skia / Impeller 引擎,不是系统控件。
- 鸿蒙系统能力需要通过 channel 调用,你不能直接像原生鸿蒙那样操作分布式软总线、元服务卡片,但理论上任何系统 API 都可以通过原生插件暴露给 Dart。
- 引擎版本升级时,嵌入器适配也需要跟随升级。不要轻易跨大版本升级 SDK,否则可能出现无法运行或渲染异常。
2.2 开源鸿蒙 Flutter SDK 的坑位和落地情况
网络上关于开源鸿蒙 Flutter SDK 的讨论非常多,这里我不展开说编译细节(这部分也确实不够稳定),重点说落地情况。
我实测下来的结论是:现阶段已经能支持你在一个 Demo 级别甚至中型商业项目里使用 Flutter 完成鸿蒙端功能验证。我测试过的基础组件包括:
- Text、Image、Container、Stack、Row、Column 等布局组件,适配良好。
- ListView、GridView 在长列表场景下能流畅滚动。
- 动画体系可以正常工作,包括隐式动画和显式动画控制器。
- HTTP 网络请求可以通过 dart:io 在鸿蒙端正常发起。
- 基础的 SharedPreferences 通过官方适配的插件可正常工作。
但目前还不能算是完全无缝:
- 部分依赖系统能力的高频插件(如视频播放器、相机)需要鸿蒙端原生实现,纯 Dart 实现的行为与 Android 可能不一致。
- 如果你在 Flutter 里使用了 PlatformView 加载原生地图或 WebView,在鸿蒙上的接入成本,比 Android 上直接使用成熟的插件要高不少。
- 字体渲染细节在鸿蒙侧和 Android 侧可能略有差异,碰到文字基准线问题需要微调布局。
这些差异并不致命,但如果前期没有评估,中后期返工成本会很高。建议在立项时列一个插件依赖清单,逐个确认鸿蒙端的支持状态。
2.3 Dart 层代码复用边界在哪里
很多刚接触的人会有一个误解:既然是跨端框架,那我是不是可以写同一套代码,所有端行为完全一致?
实际上,跨端复用的是业务逻辑和 UI 结构,而不是系统行为。比如你调用一个系统分享服务,Android 上可能调用的是系统级分享面板,鸿蒙上则可能需要通过元服务分享组件实现。这两段逻辑不可能完全相同,甚至无法完全封装成同一个 Dart API。
因此,写代码时要时刻区分两层:
- 平台无关层:UI 布局、状态管理、数据模型、网络请求、路由跳转。这些应该用纯 Dart 实现,尽量不依赖任何系统特性。
- 平台相关层:权限、文件路径、通知渠道、分享跳转、传感器等。这些必须封装成 adapter 接口,在不同平台注入不同的实现。
代码里我会写一个 PlatformAdapter 抽象类,分别提供 Android / iOS / 鸿蒙 三套实现。业务逻辑只面向抽象接口编程,这样后续扩展新平台时不需要改动页面代码层。这个模式在跨端项目中非常重要,因为随着平台增加,直接裸调系统 API 的后果就是到处是平台分支判断、没法维护。
3. 实操过程与核心环节实现
3.1 完整的环境搭建步骤记录
下面这部分就是 Day1 实际操作的完整记录,我会把每一步的关键动作和注意点都写清楚。
第一步,安装 DevEco Studio。下载对应系统和版本。安装时先不急着打开,我遇到过一个情况:直接在安装过程中选择了自定义 SDK 路径,然后因为路径里带中文和空格,导致后续命令行工具定位 SDK 失败。如果你也遇到类似问题,建议把路径设置为纯英文且不带空格,比如 D:\DevTools\DevEcoStudio。
第二步,初始化 Flutter SDK。因为官方 Flutter SDK 还不支持鸿蒙,所以需要从 openharmony 相关的代码仓库拉取支持鸿蒙的 fork 版本。我当时是直接从 git 仓库 clone 的:
bash复制git clone https://gitee.com/openharmony-sig/flutter_flutter.git -b master
记住这个仓库是 flutter 官方仓库的分叉改造版,和官方 flutter SDK 的版本基准存在差异,建议不要用 flutter upgrade 强制拉新。
第三步,配置环境变量。这一步很关键,因为整个工具链涉及多套 SDK,很容易出现 PATH 混乱。
- ANDROID_HOME 指向 Android SDK 目录(如果同时搞移动端需要)。
- DEVECO_SDK_HOME 指向 DevEco Studio 自带的 HarmonyOS SDK 目录。
- FLUTTER_ROOT 指向你 clone 的 flutter 仓库目录。
- PATH 添加 flutter/bin 目录。
- PUB_CACHE 可以设置到一个独立目录,推荐设置,不然后面容易遇到 pub 缓存权限问题。
我用的是 PowerShell 会话级配置,方便随时重置。重要提醒:不要轻易把 Dart SDK 单独安装到系统并加入 PATH。Flutter 自带 Dart SDK,和独立安装的 Dart SDK 版本一旦不一致,pub 和 dart 命令会出现版本错乱,这种问题是社区提问里比较高频的求助类型。
第四步,在 DevEco Studio 里创建一个空的鸿蒙工程,作为 Flutter 的宿主 App。这个空工程主要负责管理鸿蒙应用的生命周期、权限声明、签名配置和打包入口,真正页面内容由 Flutter 侧模块承载。工程创建时选择 Empty Ability 模板,语言选择 ArkTS。
第五步,把 Flutter 模块集成到鸿蒙工程。有三种常见方式:
- 直接用现有 Flutter 工程改造,在工程里加鸿蒙壳工程目录。
- 创建 Flutter module,然后以模块方式集成进鸿蒙主工程。
- 用命令行工具创建 Flutter 工程后,再用模板工具生成鸿蒙运行壳。
我选择的是第三种,最贴近 Flutter 原生体验。整个流程大概是:
bash复制flutter create --platforms ohos simapp
需要说明,这里的 --platforms 参数,只有在你使用支持鸿蒙的 Flutter SDK 时才存在。如果你发现 flutter create 不能识别 ohos 平台,说明 SDK 分支选错了。
第六步,把创建出来的 Flutter 工程目录中的 ohos 子工程,导入到 DevEco Studio。这一步要注意选择导入已有工程,而不是新建工程。导入时 DevEco 会自动同步 Gradle,开始下载鸿蒙侧依赖,这个过程在网络状态不好的时候耗时很长,要耐心等待。
第七步,在 DevEco Studio 中完成签名配置。如果是模拟器运行,一般不需要真机签名;如果是真机调试,必须配置自动签名。我在这一步遇到了 Authenticating 失败,解决办法是在 DevEco 的设置页面登录或注册一个华为账号,并在项目配置里选择自动签名同步。
第八步,把 Flutter 工程目录和鸿蒙壳工程关联好之后,在 Flutter 侧执行:
bash复制flutter pub get
flutter build hap --debug
如果一切正常,会在 build 目录下生成 hap 包,然后可以从 DevEco 中直接运行预览器或真机。
3.2 从零到运行:一次常见的演示代码验证
拿到新环境后,我习惯做一次最短链路的验证。写一个 Hello 页面,包含一个按钮,点击后从鸿蒙原生侧读取设备型号,然后回显到界面上。这个验证的目标不是看 UI 多漂亮,而是确认三件事:
- Flutter 业务代码能在鸿蒙端正常渲染;
- platform channel 的通道能通到鸿蒙原生代码并返回结果;
- 异步交互下没有明显的内存泄漏或崩溃。
实现步骤可以拆成这么几块:
在 Dart 侧定义 MethodChannel:
dart复制const platformChannel = MethodChannel('com.example.simapp/device');
final String model = await platformChannel.invokeMethod('getDeviceModel');
在鸿蒙原生侧的 EntryAbility 或 Application 中注册对应 handler。这里核心代码点在于使用 Flutter 的 FlutterEngine 实例绑定 MethodChannel 处理器。如果一个项目里注册了多个模块的 channel,建议统一放置到一个 ChannelManager 里管理,避免散落各处。
然后运行到模拟器,点击按钮,就能看到回显的设备信息。如果显示设备型号,说明工具链整体可用,Day1 的目标达成。
我自己在这个阶段踩过一个大坑:第一次运行到真机时,界面渲染出来了,但点击按钮程序直接闪退。随后排查打印日志,发现报错是 channel 那边未匹配到处理器。原因是在鸿蒙壳工程里注册 Flutter 引擎的位置不对,壳工程加载 Flutter 页面的时机早于通道注册的时机。
解决方法是把通道注册逻辑移到 Flutter 引擎加载完成回调中,确保先监听,再渲染页面。这类原生桥接的问题,如果没在最早期做验证,等你后面业务越写越多,一旦通道瓶颈出现,问题会积累成很难拆解的隐患。
3.3 鸿蒙端真机运行和调试
真机调试和模拟器有一处明显不同:DevEco 的自动签名依赖开发者账号,没有正确的签名和权限,应用装不上或者无法调试。遇到设备连接后无反应的情况,先检查 hdc 命令是否可用:
bash复制hdc list targets
如果没有输出,通常是 DevEco 的 hdc 工具路径没有加入 PATH,或者手机上的 USB 调试授权弹窗没有确认,数据线本身不支持数据传输的情况也要留意。排查的顺序先物理链路,再服务,再授权。
真机上运行 Flutter 应用,性能整体是流畅的。我特意试过包含大量图片的列表滑动,帧率可以保持在可接受范围内,说明渲染链路已经比较成熟。不过我也发现如果把调试模式(debug)和开发者模式的热重载功能同时打开,长列表滑动时会有偶发丢帧,这是预期内的,release 包会好很多。
3.4 代码管理:从首次提交到分支管理
代码写到这里,下一步就是 git 管理。这是 Day2 的重头戏。
首先初始化仓库。我在项目根目录执行:
bash复制git init
git add .
git commit -m "init: scaffold openharmony flutter project"
但这里有个细节:默认 add 全部文件,会将 DevEco 的本地配置目录、构建中间产物等都提交进去。这些文件包含签名信息、本地路径配置,直接提交到版本库是项目管理的坏习惯。我在初始化时先配置 .gitignore 文件,把以下目录排除在外:
- /build:构建输出目录
- /.hvigor:编译缓存
- /oh_modules:鸿蒙依赖目录
- /.idea、/.fleet:IDE 配置
- /local.properties:本地 SDK 路径,不同开发者机器上差异很大
- .DS_Store、Thumbs.db:系统垃圾文件
.gitignore 文件在跨端项目里作用明显,团队协作时如果忽略规则不一致,会出现“在我电脑上好好的,拉下来却编译不过去”之类的问题。
然后是分支策略。单人的学习项目用单分支其实没问题,但如果你要验证 Flutter 在鸿蒙和 Android 运行时二者差异,建议直接分三支:
- main:稳定的、可发布的版本
- feature/harmony-flutter:鸿蒙适配侧的工作分支
- feature/common-ui:跨端公共组件的工作分支
分支提交建议遵循简单约定:提交信息用动词开头,说明这一条改动解决的问题。比如:
bash复制git commit -m "feat: add device info channel to harmony side"
git commit -m "fix: register channel after engine loaded"
这套规范看起来简单,但日后回看历史记录时能省下大量时间。项目里做过一次可持续性复盘,发现语义化提交信息和后续代码审查速度高度相关。
3.5 git 仓库代码提交的完整流程
因为初始需求包含 git 仓库代码提交,我把一套带远程仓库的完整流程记录放在这。
如果还没有远程仓库,先在代码托管平台创建一个空仓库,不要勾选自动生成 README 或 .gitignore,否则本地和远程会出现不相关的历史冲突。
关联远程仓库并推送:
bash复制git remote add origin git@gitee.com:somegroup/simapp.git
git branch -M main
git push -u origin main
如果你是 https 方式,每次推送都会要求验证凭证。我建议配置 SSH key,不是复杂操作,但可以免去反复输入凭据的烦恼。
在第一次推送后,后续正常提交流程就是标准的四步:
bash复制git add 相关文件
git commit -m "feat: ..."
git pull --rebase origin main
git push origin main
关于 pull 使用 --rebase 我特别说明一下:如果不用 rebase 而直接 pull,合并产生的 Merge Commit 会在你个人分支历史中形成大量无意义节点;用 rebase 可以把本地提交放到远程最新节点的后面,历史记录更干净。
提交代码前的检查动作很重要,建议在 IDE 里开 Diff 工具再确认一遍。有一次我提交前没有核对,把一份包含本地绝对路径的配置文件传上了远程分支,其他同事拉取后直接编译失败。这类问题很隐蔽,本地跑得通,拉下来挂掉,所以提交前检查以及 git diff 翻一遍是个必须养成的习惯。
4. 常见问题与排查技巧实录
开发过程中整理了一份高频问题速查,这里挑重点说。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| flutter create 无法识别 ohos 平台 | 使用的不是 fork 版 Flutter SDK | 检查 FLUTTER_ROOT 是否指向开源鸿蒙分支 |
| DevEco 导入工程后大量依赖报红 | Gradle 同步失败、ohpm 网络异常 | 检查网络和镜像源配置,必要时设置镜像仓库 |
| 应用安装到真机失败 | 签名未配置/开发者账号未登录 | 打开自动签名,重新生成证书并下载 |
| channel 调用闪退 | channel 注册时机早于引擎加载完成 | 在 FlutterEngine 加载完成回调中初始化通道 |
| 无法连接设备 | hdc 工具路径未配置 | 运行 hdc list targets 检查链路 |
| 界面渲染正常但字体变小 | 鸿蒙字体度量体系和 Android 不同 | 排查 Text 的缩放因子配置 |
| 官方插件无法使用 | 插件只实现了 Android / iOS 端 | 在鸿蒙侧补写桥接,或在 API 设计中做平台兜底 |
但比问题本身更重要的是排查思路。如果你在鸿蒙侧开发遇到 Flutter 相关疑难问题,第一个排查动作不应该是改代码,而是确认日志。控制台、DevEco Log、以及 Flutter 侧的日志都要看。我习惯在 Dart 层关键路径加 debugPrint,在原生侧加 Hilog,然后对照时间轴看事件先后顺序。之前项目里遇到的很多延迟性问题,几乎都是靠日志时间轴定位找出根因的。
第二个排查意识是把问题拆开来看。如果 UI 没渲染,先排除 Dart 层编译错误;再排除引擎没有启动;最后才怀疑渲染适配层。永远不要同时动多处代码去试错,那样只会让问题变得更难排查,出错可能性更高。
另外有个从实际项目中得到的经验:如果 Flutter 工程既要在鸿蒙上运行,又要上架到应用市场,提前把签名证书管理和多渠道打包配置跑通非常重要。这一步在本地单人开发时常常被忽略,但等要出正式包时,配置不全会让你卡在平台审核环节。建议 Day1 就把 debug 签名和 release 签名的配置占位做好。
5. 一些实际经验总结
写到这里,Day1 到 Day2 的完整过程基本整理完了。从环境搭建到第一行 Dart 代码跑在开源鸿蒙上,再到 git 仓库完成首次提交,实际操作下来大概要花一个完整的白天加一个晚上。如果你之前已经熟悉 Flutter 和移动开发,这个过程可以压缩到半天。
我个人在实操中的体会有几条:
第一,工具链版本锁定是开源鸿蒙 Flutter 开发的定心丸。不要今天看到某个 SDK 更新了就立刻升级,先确认升级影响面,再决定是否跟进。尤其在跨端适配期,稳定压倒一切。
第二,多平台开发的调试效率提升,靠的是自动化脚本和文档沉淀。我给这个项目写了一份环境初始化脚本,同时维护了一个 Markdown 格式的踩坑记录文档,后续同事加入时能直接上手。
第三,开源鸿蒙的 Flutter 生态发展很快,主线的接口也在持续迭代。做技术选型时保持跟踪,但不要过度追逐新特性。关注社区活跃度、issue 解决速度、以及核心维护者对计划的说明,比关注版本号本身更重要。
如果后续要做深度适配,可以继续深入以下方向:用 PlatformView 在鸿蒙端复用原有原生地图组件、接入系统分享与支付能力、处理分布式场景下的跨端控制等。这个领域目前可参考的资料还很分散,实操性的内容不多,所以每一次验证和总结都值得沉淀。
这套折腾的经验,希望对正在观望的你有帮助。后面 Day3 的内容更新了我会再来分享。
