ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Flutter适配鸿蒙全流程:从环境搭建到HAP打包的工程实践

2026/10/8 15:15:40 拓冰建站 浏览量
Flutter适配鸿蒙全流程:从环境搭建到HAP打包的工程实践 说实话接到这个任务的时候我第一反应是“又来了”因为圈子里聊鸿蒙开发翻来覆去就是ArkTS、ArkUI、元服务这些词。但我手上这个项目偏偏是从Flutter跨过去的。团队里没人写过一行ArkTS但产品上线时间卡得死死的客户只给了一句话现有日记APP要能在鸿蒙设备上跑起来。这个项目做完我最大的感受是Flutter跑鸿蒙不是“能不能跑”的问题而是“怎么跑得舒服”的问题。这篇文章把整个开发流程、踩过的坑、最后沉淀下来的方案从头到尾写一遍给准备走同样路的人一个参考。先说清楚我的项目背景这是一个面向个人用户的每日日记APP功能不算复杂——日记列表、搜索、富文本编辑、图片附件、标签分类、数据导出。原本是基于Flutter开发目标平台是Android和iOS。后来客户要求适配鸿蒙于是我从零开始把整个工程迁移到HarmonyOS环境下完整走了一遍环境搭建、代码适配、打包上架的流程。1. 为什么是Flutter而不是ArkTS跨平台项目选型的真实考量很多人一听到“鸿蒙开发”第一反应就是必须用ArkTS、ArkUI那一套。但我们的情况不太一样已经有了一套完整的Flutter代码库日记APP的核心业务逻辑、UI组件、数据层都已经跑了好几个版本如果为了鸿蒙单独用ArkTS重写一遍等于把之前的工作全部推倒重来。1.1 鸿蒙生态的现状与Flutter的适配进度先科普一下背景。鸿蒙系统目前的开发框架主要分两层一层是HarmonyOS自家的ArkTS/ArkUI另一层是OpenHarmony底层的原生能力接口。而Flutter能跑在鸿蒙上靠的是OpenHarmony社区维护的flutter_flutter分支也就是俗称的“Flutter for OpenHarmony”。这个适配分支的进度说实话比我预想的要成熟。基础组件、路由、http请求、SharedPreferences这些常用能力都有对应的实现官方文档里也给出了明确的安装和配置方式。但有一点必须说在前面不要把它当成Android Flutter的完全替代品。它更像是一个“能跑但还需要磨合”的状态——基础功能没问题涉及系统级能力比如推送、定位、特定传感器时就得自己去翻鸿蒙原生接口。1.2 团队技术栈复用比技术选型更现实的成本问题我们团队全员Flutter背景没人系统学过ArkTS。如果用ArkTS重写日记APP保守估计需要两个月还不算学习和踩坑的时间。而基于Flutter适配鸿蒙核心工作量集中在环境配置和差异适配两部分两周左右就完成了主体内容。这是一个典型的成本决策问题。技术选型不能只看“哪个技术更正统”要看团队现有能力和项目时间节点。鸿蒙原生开发很好但对于一个已经有成熟Flutter产品的团队来说跨平台复用的价值远大于盲目追新。1.3 鸿蒙Flutter与Android Flutter的差异概览我把整个适配过程遇到的核心差异列了个表方便直观对比维度Android Flutter鸿蒙FlutterSDK来源Flutter官方stable分支OpenHarmony社区flutter_flutter分支构建工具Gradlehvigor包格式APK/AABHAP权限模型Android权限组HarmonyOS权限声明文件路径/data/data/package沙箱路径/storage插件兼容pub.dev直接可用需检查ohos版本支持这个表看起来简单但每一项在实操里都是坑。比如“插件兼容”这一条团队里有个同事直接把pub.dev上的插件全加进来结果编译直接报错——后来才发现很多插件压根没有ohos平台的实现必须逐个替换。2. 环境搭建与工程初始化最容易被文档带偏的一段路这个环节我栽了不少跟头而且都是那种“看起来很简单、一操作就崩”的问题。如果你准备在鸿蒙上跑Flutter环境这块千万不要跳过直接照着我这个流程走能省很多时间。2.1 SDK下载与版本对齐千万不能用错了分支Flutter官方stable分支目前不支持鸿蒙你必须使用OpenHarmony社区维护的flutter_flutter分支。具体操作# clone对应的分支代码 git clone https://gitee.com/openharmony-sig/flutter_flutter.git cd flutter_flutter git checkout master然后配置Flutter环境变量这里有个细节鸿蒙版Flutter SDK的版本号必须和OpenHarmony SDK版本匹配。我一开始用的是最新版的flutter_flutter但DevEco Studio里的OpenHarmony SDK是旧版结果编译时各种头文件找不到。建议直接对照OpenHarmony发布说明里的版本兼容表比如OpenHarmony 4.0对应哪个flutter_flutter版本3.2又对应哪个一定要一一对应别想着“用最新版肯定没毛病”。2.2 创建支持鸿蒙的Flutter工程SDK配好之后创建工程的方式跟常规Flutter不太一样。Flutter为了支持鸿蒙在create命令里加了模板参数flutter create --org com.example --platforms ohos daily_diary注意--platforms ohos这一步会生成一个ohos/目录里面包含了鸿蒙的工程骨架。生成完之后用DevEco Studio打开ohos目录会自动识别出鸿蒙工程结构。目录结构大概是这样的daily_diary/ ├── lib/ # Dart代码跨平台共用 ├── ohos/ # 鸿蒙平台工程 │ ├── entry/ │ │ ├── src/main/ │ │ │ ├── ets/ # ArkTS入口与桥接代码 │ │ │ └── resources/ # 资源文件 │ │ └── build-profile.json5 │ └── build-profile.json5 └── pubspec.yaml这里面最关键的对比在entry/src/main/ets。鸿蒙的Flutter应用入口不是纯Dart启动的而是先在ArkTS侧创建一个Ability然后在里面加载Flutter引擎。如果你打开这个文件会看到一个类似MainAbility的类里面把Flutter容器挂到了鸿蒙的页面生命周期上。2.3 首次构建的常见错误与处理我第一批次构建就遇到两个典型错误这里直接列出来你大概率也会碰到。错误一ohos directory not found这个通常是因为创建工程时忘了加--platforms ohos或者flutter_flutter分支没有切到最新。解决方案是删掉工程重新创建别手动补目录容易漏掉配置。错误二hvigor构建时提示找不到SDK路径DevEco Studio的SDK路径默认不会自动同步到flutter的配置里。需要在ohos/目录下找到local.properties手动指定SDK路径sdk.dir/path/to/ohos-sdk我第一次没设这个构建到一半直接挂掉排查了半天才发现就是这么个配置问题。3. 日记APP的需求拆解与状态管理设计环境跑通之后真正的业务实现才开始。日记APP的需求看起来简单但拆开来看数据模型、状态流转、持久化方案每一块都要提前想清楚。3.1 一个日记APP的核心需求清单我接手的产品需求可以收敛成几个核心点日记列表按日期倒序展示支持搜索和标签筛选日记编辑富文本编辑支持插入图片数据存储本地优先自动保存标签系统自定义标签用于分类和检索导出功能将日记导出为Markdown或纯文本这五个点里最难的不是编辑器的实现而是状态管理。因为编辑页和列表页之间的数据同步、自动保存的时机控制、标签筛选的联动都需要一个清晰的数据流设计。3.2 Provider的使用逻辑与数据流设计状态管理我最终选了Provider原因很简单团队最熟、社区资料最多、和鸿蒙的适配问题最少。Riverpod虽然更现代但在ohos插件支持上还不够稳没必要在项目里引入额外风险。Provider的核心用法其实是围绕着ChangeNotifier展开的。我把日记数据设计成三个层class DiaryModel extends ChangeNotifier { ListDiaryEntry _entries []; ListDiaryEntry get entries _entries; void addEntry(DiaryEntry entry) { _entries.add(entry); notifyListeners(); } void updateEntry(DiaryEntry entry) { final index _entries.indexWhere((e) e.id entry.id); if (index ! -1) { _entries[index] entry; notifyListeners(); } } }然后在顶层用ChangeNotifierProvider注入void main() { runApp( ChangeNotifierProvider( create: (_) DiaryModel(), child: const DailyDiaryApp(), ), ); }这里有一个很容易忽略的点notifyListeners()并不区分“新增”还是“更新”任何一次调用都会通知所有监听者刷新。如果你在列表页监听了整个DiaryModel那么编辑页里每敲一个字列表页都会重建一次。性能问题就是这么来的。所以我在实际项目里做了一个优化把“日记列表数据”和“当前正在编辑的日记”拆成两个独立的ChangeNotifier。列表页只监听前者编辑页只监听后者互不干扰。3.3 数据持久化的几种路子Flutter在鸿蒙上的持久化方案我踩了一圈最后稳定下来的是两条线简单的配置数据用SharedPreferences的ohos实现。这个插件在OpenHarmony上已经有对应版本存用户设置、最近一次打开时间之类的小字段够用。日记内容本体我用的是sqflite的ohos移植版。鸿蒙上SQLite底层支持是有的但插件接口跟Android版本有一些差异主要是在数据库文件路径的获取方式上。// Android下通常这样拿路径 final dbPath join(await getDatabasesPath(), diary.db); // 鸿蒙下需要自己拼沙箱路径 final dbPath join(/storage/Users/currentUser/, diary.db);后面这个路径问题我在第5章会详细展开。这里想提醒的是不要以为插件写的一样就万事大吉存储路径这种细节分分钟给你不一样的答案。4. 日记列表、编辑与组件通信的实战细节这个部分是整个App的灵魂所在也是踩坑最多的地方。我把它拆成两块来讲页面之间的数据通信以及列表内部组件的状态同步。4.1 列表页与编辑页的导航与数据回传日记APP最常见的交互是列表页点击一条记录进入编辑页编辑完保存返回列表页此时列表要刷新。我用的方案是Navigator.push配合await从编辑页返回一个“是否发生了变化”的标记// 列表页跳转编辑页 final changed await Navigator.pushbool( context, MaterialPageRoute( builder: (_) DiaryEditorPage(diaryId: entry.id), ), ); if (changed true) { // 重新加载数据 model.loadEntries(); }这个方案的优点在于意图明确编辑页只需要返回一个布尔值不需要把整个编辑后的对象传回来。列表页收到true之后统一从数据库拉最新数据避免出现“列表数据是旧的、局部改动没同步”这种状态不一致的问题。4.2 组件间通信的几种方式日记编辑页内部的组件很多标题输入框、正文编辑器、图片选择器、标签选择器、自动保存状态提示条。这些组件之间需要频繁同步状态。我在项目里用了三种通信方式各有各的适用场景第一种通过Provider共享状态。这是主力。编辑页的当前日记内容、自动保存状态都在一个EditorModel里子组件通过context.watchEditorModel()读取。这种方式适合“很多组件都要读同一份数据”的场景。第二种通过构造函数传回调。当一个子组件的状态变化只影响它自己时没必要升级到全局模型。比如图片选择器里的“当前已选图片数量”只在图片选择器内部用到直接定义一个内部回调处理即可。第三种通过GlobalKey调用子组件方法。这种用得少但有一种场景很实用列表页的下拉刷新和上拉加载我封装了一个DiaryListView外部通过GlobalKeyDiaryListViewState来触发刷新操作。final _listKey GlobalKeyDiaryListViewState(); // 外部调用 _listKey.currentState?.refresh();要注意的是GlobalKey方式适合低频的操作触发不适合高频的状态同步。如果你发现代码里到处都是GlobalKey.currentState大概率是设计出了问题。4.3 图片与富文本的处理富文本编辑我在Flutter生态里选的是flutter_quill它在鸿蒙上的表现比预期好基本能用。但图片插入这部分需要自己扩展——flutter_quill默认的图片加载方式是把图片转成base64塞进文档里小图没问题大图会导致文档急剧膨胀加载卡顿。我替换成自定义embed图片上传到应用沙箱目录后在文档里只存一个相对路径的引用渲染时再通过Image.file读取。这样日记文档保持轻量图片也不会丢失。图片选择用的是image_picker的ohos实现但实测在鸿蒙上部分版本有兼容问题主要表现为调用相机时闪退。我最后的方案是直接调鸿蒙的PhotoViewPicker原生接口通过MethodChannel暴露给Dart侧调用。这个方法绕开了插件兼容问题但需要写一些ArkTS桥接代码后面有机会单独写一篇展开。5. 鸿蒙适配的独有难点权限、文件与生命周期如果说前四章的内容在Android Flutter上也能成立那么这一章就是真正体现“鸿蒙”二字的地方。权限模型、文件路径、生命周期这三个差异直接把我的工期拖了三天。5.1 权限模型的差异鸿蒙的权限声明在module.json5里跟Android的AndroidManifest.xml完全是两个体系。日记APP用到的权限不多但有一个让我纠结了很久保存图片到相册需要什么权限Android下需要WRITE_EXTERNAL_STORAGE鸿蒙下则要看module.json5里的requestPermissions配置{ module: { requestPermissions: [ { name: ohos.permission.WRITE_IMAGEVIDEO } ] } }这里的坑是鸿蒙部分权限需要用户动态授权部分权限是系统自动授予的而且不同系统版本还有差异。我的经验是每用到一项系统能力都去翻一下官方权限列表别照搬Android的权限方案。5.2 文件路径的差异这是整个项目里最折腾人的一个环节。同样一段代码在Android上运行正常到鸿蒙上就报文件找不到。原因在于两者沙箱设计完全不同。Android的App私有目录是/data/data/packageName/而鸿蒙的私有目录是/storage/Users/currentUser/appId/。flutter的path_provider插件在ohos上有适配但某些接口返回的路径拼接起来会出问题。我踩的坑是这样的getApplicationDocumentsDirectory()在鸿蒙上返回的是一个URI格式的路径比如storage:///...直接和join()合成之后操作文件时系统不认识。解决办法是拿到路径后先转成标准路径final rawDir await getApplicationDocumentsDirectory(); final realPath rawDir.path.contains(storage://) ? rawDir.path.replaceFirst(storage://, /storage/) : rawDir.path;这个转换逻辑不一定适用于所有版本但思路是对的鸿蒙上的路径不要想当然打印出来亲眼看一遍再决定怎么处理。5.3 生命周期与后台运行的注意点Flutter应用在鸿蒙上的生命周期由ArkTS侧管理。我在实际测试中发现应用退到后台一段时间后Flutter引擎可能被系统回收再切回来时如果直接恢复原有页面会出现白屏或者状态丢失。这个问题有两个层面的处理方案第一在ArkTS侧设置对应的生命周期回调在onBackground时保存编辑状态在onForeground时重建Flutter容器onPageShow() { if (this.isBackground) { this.flutterController.rebuild(); this.isBackground false; } }第二在Dart侧把日记草稿自动保存的时机和后台切换绑定每次AppLifecycleState.paused时立即写入数据库而不是依赖用户手动点保存。这两条线同时做保险系数高很多。6. 打包发布与性能优化实录最后这部分是纯实战总结。从编译到上架中间有很多环节是Android开发经验覆盖不到的。6.1 打包流程hvigor构建与hap包鸿蒙Flutter应用的最终产物是.hap包。构建流程不是flutter build apk而是在ohos目录下用hvigor命令构建cd ohos hvigorw assembleHap如果你在DevEco Studio里操作直接选择构建HAP即可。这里有个很容易踩的坑flutter build的产物需要先被拷贝到鸿蒙工程中hvigor构建时才会把Flutter的so库和资源打进去。整个流程是flutter build flutter_assets生成Flutter资源hvigor构建时读取这些资源生成最终的HAP包6.2 启动速度优化日记APP测试时发现首次启动要2秒多对纯本地应用来说偏慢。定位后发现主要瓶颈在两个方面一个是Flutter引擎初始化。鸿蒙上引擎初始化比Android慢这是客观情况。优化方式是把启动页的渲染提前到ArkTS侧的原生页面让用户先看到一个纯原生界面同时后台初始化Flutter引擎两个步骤并行。另一个是首屏数据加载。原本我在首页initState里去数据库拉列表串行执行导致首帧出来要几百毫秒。优化为先渲染空列表页面然后异步加载数据插入这样视觉上启动会快很多。override void initState() { super.initState(); // 先让页面出来 WidgetsBinding.instance.addPostFrameCallback((_) { _loadEntries(); }); }6.3 发布到应用市场前的检查清单发布前的检查项我从Android惯例里挑出和鸿蒙相关的几点HAP包是否包含对应ABI的so库检查libs目录下是否只有arm64-v8a还是包含了x86_64模拟器可能需要权限声明是否最小化只声明实际用到的权限不要图省事全加上应用市场上审核很严格隐私弹窗是否合规注册账号、读取相册等场景需要显式声明版本号格式是否符合鸿蒙规范build-profile.json5里的版本号配置要跟应用市场的页面版本一致这些检查项不难但漏掉任何一个都可能导致审核回退来回折腾时间成本很高。最后聊两句这套流程走下来我个人最大的感触是Flutter跨平台开发鸿蒙现阶段确实不是零成本迁移但也没有想象中那么可怕。环境配置是最难熬的一段咬着牙过去之后后面的大部分业务代码都是直接复用真香的时刻在后面。如果团队里已经有一套成熟的Flutter代码库我建议先做试点功能适配把环境、插件替换、路径处理这些底层问题摸清楚再全量迁移。如果你的产品本身就是新立项团队也没有历史包袱那直接学ArkTS做原生也完全可以。技术选型没有标准答案适合自己的就是最好的。最后再分享一个小经验鸿蒙Flutter的在社区更新速度很快隔一个月再看可能就有新变化写代码的时候尽量把一些桥接逻辑集中封装方便以后适配新版本。至少在我这个项目里这个决定让我后期少改了很多代码。