简介:这份源码面向希望上手 HarmonyOS NEXT 与 Flutter 跨平台开发的移动应用开发者,以一款食谱 App 为载体,演示如何将 Flutter 的跨平台 UI 能力与 HarmonyOS NEXT 的分布式特性结合,解决多设备、多终端下食谱查询与浏览体验不一致的问题。资源包共 36 个文件,约 113KB,以 11 个 json5 与 8 个 json 配置依赖和构建信息,7 个 ets 承载鸿蒙事件逻辑,2 个 ts 提供类型化代码,另有 4 个 png 界面素材、2 个 txt 说明及 gitignore 等辅助文件,目录涵盖 AppScope、entry、mock、ohosTest 等模块,结构清晰便于按层阅读。目前已有 307 人学习下载。读者可从中获取一套可运行的迁移示例,理解 json5 配置的灵活扩展、TypeScript 静态检查带来的可维护性,以及响应式界面与设备协作的落地思路,适合作为跨平台应用开发的学习范本与二次开发起点。
1. 从 Flutter 到 HarmonyOS NEXT:这套食谱 App 源码到底能不能跑
去年帮一个做智能厨电的朋友看项目,他们想把现有的 Flutter 食谱应用迁到 HarmonyOS NEXT 上,团队折腾了两周没跑通,最后发现卡在 hvigor 的构建配置和 oh-package 依赖解析上。这套「基于 HarmonyOS-NEXT 与 Flutter 的迁移食谱 App 设计源码」正好是同类场景的完整工程,35 个文件里 11 个 json5、8 个 json、7 个 ets、2 个 ts,结构上是一个标准的 Stage 模型工程,不是那种只丢几个页面文件的半成品。它解决的核心问题是:让你看到一个 Flutter 风格的食谱应用在 HarmonyOS NEXT 工程里怎么组织入口、怎么配依赖、怎么把资源挂进 AppScope。适合两类人——正在做 HarmonyOS NEXT 应用迁移的 Flutter 开发者,以及想拿一个真实工程练手 hvigor 构建链的鸿蒙新手。源码本身不包含 Flutter 引擎的完整嵌入方案,但工程骨架和配置链路是齐的,照着改能省掉大量试错。
2. 工程结构拆解:35 个文件里哪些是骨架,哪些是肉
2.1 顶层目录与模块划分
拿到压缩包解压后,第一眼看到的是 AppScope、entry、hvigor 三个顶层目录,加上一堆散落的 json5 和 ts 文件。这个布局是 HarmonyOS NEXT Stage 模型的标准形态,和传统 FA 模型完全不同。AppScope 放的是应用级配置和全局资源,entry 是主 HAP 模块,hvigor 是构建工具链的配置目录。
具体到文件层面,几个关键角色必须分清:
| 文件/目录 | 作用 | 能不能动 |
|---|---|---|
| AppScope/app.json5 | 应用包名、版本、图标、标签 | 包名和版本必须改 |
| AppScope/resources | 全局资源,图标和字符串 | 可替换 |
| entry/src/main | 主模块源码和资源 | 核心开发区 |
| entry/src/main/module.json5 | 模块配置,入口 ability | 按需改 |
| hvigor/hvigor-config.json5 | 构建工具版本和插件 | 版本要对齐 |
| oh-package.json5 | 项目级依赖声明 | 按需增删 |
| build-profile.json5 | 构建产物和签名配置 | 签名必须改 |
entry 下面的 src 又分了 main、mock、test、ohosTest 四个子目录。main 是真正的业务代码,mock 放的是测试桩数据,test 和 ohosTest 分别是本地单元测试和设备测试。很多新手拿到工程直接改 main 里的东西,跑不起来就慌,其实先看 mock 里的数据结构能帮你快速理解这个食谱 App 的数据模型长什么样。
2.2 json5 与 json 的分工逻辑
这个工程里 json5 文件有 11 个,json 文件有 8 个,数量上 json5 占了大头。为什么不用纯 json?因为 json5 支持注释、尾逗号、单引号,写配置的时候不用那么憋屈。HarmonyOS NEXT 的构建链从 API 12 开始原生支持 json5 解析,所以 app.json5、module.json5、build-profile.json5 这些核心配置全用 json5 写。
但注意,不是所有配置都能用 json5。oh-package-lock.json5 虽然带 json5 后缀,但它的内容格式是锁文件,手改容易出问题。oh-package.json5 才是你声明依赖的地方,类似 npm 的 package.json。这两个文件的关系是:你改 oh-package.json5,执行构建时工具会自动更新 oh-package-lock.json5。如果你手动改了锁文件,下次构建可能被覆盖,或者直接报依赖解析失败。
// oh-package.json5 典型结构 { "name": "recipe_app", "version": "1.0.0", "description": "食谱应用", "main": "", "author": "", "license": "Apache-2.0", "dependencies": { // 这里放三方库依赖 }, "devDependencies": { // 这里放构建期依赖 } }上面这段是 oh-package.json5 的骨架。dependencies 里放运行时需要的包,devDependencies 放只在构建和测试时用的包。这个工程本身没有引入额外的三方库,所以这两个字段大概率是空的或者只有基础依赖。你如果要加网络请求库或者状态管理库,就往 dependencies 里塞,然后执行ohpm install让工具去拉包并更新锁文件。
2.3 ets 与 ts 的边界
7 个 ets 文件和 2 个 ts 文件,这个比例说明主体代码用 ArkTS 写,也就是 .ets 后缀。ArkTS 是 HarmonyOS NEXT 的应用开发语言,基于 TypeScript 扩展了声明式 UI 和状态管理。那 2 个 ts 文件干什么用?通常是放纯逻辑工具函数或者类型定义,不涉及 UI 构建的部分可以写成 ts,让编译器按标准 TypeScript 处理。
// 典型的 ts 工具文件:recipe_utils.ts export interface RecipeItem { id: number; name: string; ingredients: string[]; steps: string[]; } // 按关键词过滤食谱 export function filterRecipes(list: RecipeItem[], keyword: string): RecipeItem[] { if (!keyword) return list; const lower = keyword.toLowerCase(); return list.filter(item => item.name.toLowerCase().includes(lower) || item.ingredients.some(i => i.toLowerCase().includes(lower)) ); }这个工具文件定义了一个 RecipeItem 接口和一个过滤函数。接口描述食谱的数据结构,过滤函数接收列表和关键词,返回匹配的项。逻辑很直白,但关键点是:这个 ts 文件被 ets 文件 import 时,类型信息会保留,ArkTS 编译器能识别。如果你把这段逻辑直接写在 ets 里也能跑,但拆出来更干净,测试也好写。
ets 文件里则是 @Entry、@Component、@State 这些 ArkTS 装饰器的天下。入口页面通常有一个 @Entry 标记的组件,里面用 build 方法描述 UI 树。食谱列表页会用 List 组件配合 ForEach 渲染,详情页用 Navigation 或者 router 跳转。这些和 Flutter 的 Widget 树思路相通,但语法完全是两套。
3. 环境搭建与首次构建:从零到跑通的血泪步骤
3.1 DevEco Studio 版本与 SDK 对齐
跑这个工程的第一步不是打开代码,而是确认你的 DevEco Studio 版本和 SDK 版本。HarmonyOS NEXT 的 API 版本迭代很快,API 12 和 API 11 的构建配置有差异。这个工程的 hvigor-config.json5 里会写明 hvigorVersion 和依赖的 plugin 版本,你打开这个文件看一眼,然后去 DevEco Studio 的 SDK Manager 里确认对应版本的 SDK 已经下载。
常见做法是:DevEco Studio 用 5.0 以上版本,SDK 选 API 12 或更高。如果你本地只有 API 11 的 SDK,构建时会报hvigor plugin not found或者compatibleSdkVersion mismatch。这时候要么升级 SDK,要么改 build-profile.json5 里的 compatibleSdkVersion 字段往下调,但往下调可能遇到 API 不兼容的问题,不推荐。
# 检查本地已安装的 SDK 版本(命令行方式) # 在 DevEco Studio 的 terminal 里执行 ohpm -v # 输出示例:ohpm 5.0.0 # 再检查 hvigor 版本 hvigorw -v # 输出示例:hvigor 5.0.0这两个命令分别查 ohpm 包管理器和 hvigor 构建工具的版本。版本号要和 hvigor-config.json5 里声明的一致,不一致就改配置文件或者升级工具。我一般会先把 hvigor-config.json5 里的版本号抄下来,然后逐个核对本地工具版本,省得构建到一半报错再回头查。
3.2 签名配置与 build-profile.json5 修改
HarmonyOS NEXT 的应用安装必须签名,没签名的 HAP 装不进设备。build-profile.json5 里有一个 signingConfigs 字段,默认可能是空的或者指向一个不存在的证书。你需要用 DevEco Studio 的自动签名功能生成调试证书,或者手动配置。
自动签名的操作路径是:File → Project Structure → Signing Configs → 勾选 Automatically generate signature。IDE 会帮你生成证书和 profile 文件,并自动填进 build-profile.json5。手动配置的话,需要先在 AppGallery Connect 里创建应用、下载证书和 profile,然后在 build-profile.json5 里填路径。
// build-profile.json5 签名相关片段 { "app": { "signingConfigs": [ { "name": "default", "type": "HarmonyOS", "material": { "certpath": "C:/Users/xxx/.ohos/config/default_xxx.cer", "storePassword": "xxxxx", "keyAlias": "debugKey", "keyPassword": "xxxxx", "profile": "C:/Users/xxx/.ohos/config/default_xxx.p7b", "signAlg": "SHA256withECDSA", "storeFile": "C:/Users/xxx/.ohos/config/default_xxx.p12" } } ] } }certpath 是证书文件路径,profile 是描述文件路径,storeFile 是密钥库文件。这三个文件缺一不可。storePassword 和 keyPassword 是密钥库和密钥的密码,自动签名生成的密码 IDE 会帮你填。如果你手动改过密码,这里要同步改。signAlg 是签名算法,一般用 SHA256withECDSA,别乱改。
3.3 依赖安装与首次构建命令
签名配好之后,在项目根目录执行依赖安装。HarmonyOS NEXT 用 ohpm 而不是 npm,命令是ohpm install。这个命令会读 oh-package.json5,把依赖拉到本地 oh_modules 目录,同时生成或更新 oh-package-lock.json5。
# 在项目根目录执行 ohpm install # 安装完成后执行构建 hvigorw assembleHap --mode module -p product=default # 如果想清理构建缓存后重新构建 hvigorw clean hvigorw assembleHapohpm install的输出会告诉你装了多少个包,有没有报错。如果卡在某个包下载不动,检查网络或者换 ohpm 的 registry。hvigorw assembleHap是构建 HAP 包的命令,--mode module表示按模块构建,-p product=default指定构建产物类型。构建成功后,HAP 文件会生成在 entry/build/default/outputs/default/ 目录下。
构建过程中最常见的报错是Failed to resolve ohpm dependencies,这通常是 oh-package.json5 里写了不存在的包或者版本号格式不对。另一个高频报错是hvigorfile.ts execution failed,这多半是 hvigorfile.ts 里的构建脚本逻辑有问题,比如引用了不存在的插件。遇到这两个报错,先看完整日志的最后 20 行,定位到具体文件和行号再改。
4. 避坑与排查:迁移食谱 App 时最容易翻车的五个点
4.1 坑一:module.json5 里 entry ability 的 srcEntry 路径写错
现象:构建成功,但安装到设备后打开闪退,日志里报Ability not found或者srcEntry path invalid。
原因:module.json5 里 entry 模块的 abilities 数组中,srcEntry 字段指向的 ets 文件路径不对。这个路径是相对于 entry/src/main/ets 目录的,不是相对于项目根目录。很多人从 Flutter 转过来,习惯写绝对路径或者从 src 开始写,结果路径解析失败。
解决:打开 entry/src/main/module.json5,找到 abilities 数组,确认 srcEntry 的值形如"srcEntry": "./ets/entryability/EntryAbility.ets"。注意开头的./和中间的ets/层级。如果你把 EntryAbility.ets 移到了别的目录,这里要同步改。改完重新构建安装。
4.2 坑二:AppScope/app.json5 的 bundleName 与签名不匹配
现象:构建通过,签名也配了,但安装时报signature verification failed或者bundleName mismatch。
原因:app.json5 里的 bundleName 必须和签名 profile 文件里绑定的 bundleName 完全一致。自动签名时 IDE 会根据 app.json5 里的 bundleName 去生成 profile,但如果你后来手动改了 bundleName,profile 没重新生成,就会不匹配。
解决:先确认 app.json5 里的 bundleName 是什么,然后去 AppGallery Connect 或者本地 profile 文件里核对。如果不一致,要么改回原来的 bundleName,要么重新生成签名。自动签名的场景下,删掉 .ohos/config 目录下的旧证书,重新走一遍自动签名流程。
4.3 坑三:json5 文件里用了 json 不支持的语法但工具版本太低
现象:构建时报Unexpected token或者Invalid json5 format,指向某个 json5 文件的某一行。
原因:json5 支持注释和尾逗号,但低版本的 hvigor 或者 ohpm 可能只按严格 json 解析。如果你的工具版本低于 API 12 对应的版本,json5 里的注释会被当成非法字符。
解决:先确认 hvigor-config.json5 里声明的 hvigorVersion 是否支持 json5。如果不支持,要么升级工具版本,要么把 json5 文件里的注释和尾逗号去掉,退化成严格 json。我一般会优先升级工具,因为 json5 的可读性优势在配置文件多的时候很明显。
4.4 坑四:oh-package-lock.json5 被手动修改导致依赖树断裂
现象:ohpm install报lock file integrity check failed,或者安装的包版本和 oh-package.json5 里声明的不一致。
原因:oh-package-lock.json5 是自动生成的锁文件,记录了每个依赖的精确版本和哈希。手动改这个文件,或者从别的项目拷贝过来,会导致哈希对不上。
解决:删掉 oh-package-lock.json5,重新执行ohpm install,让工具根据 oh-package.json5 重新生成锁文件。如果 oh-package.json5 里的版本号写的是范围(比如^1.0.0),生成的锁文件会锁定到具体版本。想固定版本就直接写死,别用范围符号。
4.5 坑五:资源文件放错目录导致图片加载不出来
现象:应用能跑,但界面上的图标或者图片显示为空白,日志里报resource not found。
原因:HarmonyOS NEXT 的资源引用有严格的目录约定。AppScope/resources 放全局资源,entry/src/main/resources 放模块资源。图片要放在 resources/base/media 目录下,引用时用$r('app.media.xxx')。如果图片放在 rawfile 目录,引用方式不同,要用$rawfile('xxx.png')。
解决:确认 png 文件的位置。这个工程有 4 个 png,大概率在 entry/src/main/resources/base/media 下。引用时检查代码里用的是$r('app.media.文件名')还是$rawfile('文件名'),两者不能混用。改完资源目录后,执行一次 clean 再构建,避免缓存导致资源没打包进去。
5. 从跑通到改出自己东西:三个进阶操作和一个验证习惯
5.1 把 mock 数据换成真实接口数据
这个工程自带 mock 目录,里面的数据结构就是食谱的字段定义。你跑通之后第一件事应该是把 mock 数据替换成真实数据源。常见做法是在 entry/src/main/ets 下新建一个 services 目录,写一个数据请求模块,用@ohos.net.http发请求,然后把返回的 json 解析成 RecipeItem 数组。
// services/recipe_service.ts import http from '@ohos.net.http'; import { RecipeItem } from '../utils/recipe_utils'; export async function fetchRecipes(): Promise<RecipeItem[]> { const httpRequest = http.createHttp(); try { const response = await httpRequest.request( 'https://your-api.com/recipes', { method: http.RequestMethod.GET, header: { 'Content-Type': 'application/json' }, connectTimeout: 10000, readTimeout: 10000 } ); if (response.responseCode === 200) { return JSON.parse(response.result as string) as RecipeItem[]; } return []; } finally { httpRequest.destroy(); } }这段代码创建了一个 HTTP 请求,GET 方法拉取食谱列表,超时设了 10 秒。responseCode 为 200 时把结果解析成 RecipeItem 数组返回。finally 里销毁请求对象,避免内存泄漏。注意 HarmonyOS NEXT 的网络请求需要申请 ohos.permission.INTERNET 权限,在 module.json5 的 requestPermissions 里加上。
5.2 用 ArkTS 的 @State 和 @Prop 做列表与详情联动
食谱 App 的核心交互是列表点进去看详情。ArkTS 的状态管理用 @State 和 @Prop 装饰器。列表页维护一个 @State 修饰的 recipes 数组,点击某一项时把选中的 RecipeItem 通过 router 参数传给详情页,详情页用 @Prop 接收。
// 列表页片段 @Entry @Component struct RecipeListPage { @State recipes: RecipeItem[] = []; build() { List() { ForEach(this.recipes, (item: RecipeItem) => { ListItem() { Text(item.name) .fontSize(18) .onClick(() => { router.pushUrl({ url: 'pages/RecipeDetailPage', params: { recipe: item } }); }) } }, (item: RecipeItem) => item.id.toString()) } } }ForEach 的第三个参数是 key 生成函数,用 id 的字符串形式保证唯一性。onClick 里用 router.pushUrl 跳转,params 把整个 item 传过去。详情页在 aboutToAppear 生命周期里通过 router.getParams() 拿到参数,赋值给 @Prop 修饰的变量。这套流程和 Flutter 的 Navigator.push 加构造函数传参思路一致,但 ArkTS 的 router 是全局的,不需要 BuildContext。
5.3 构建产物验证:HAP 包里到底装了什么
改完代码构建出 HAP 之后,别急着装设备。先把 HAP 解压看一眼,确认资源文件和代码都打进去了。HAP 本质是个 zip 包,改后缀为 .zip 解压即可。
# 把 HAP 复制一份改名为 zip cp entry/build/default/outputs/default/entry-default-signed.hap ./check.zip unzip -l check.zip # 输出会列出包内所有文件unzip -l列出包内文件清单。重点看三个东西:ets 目录下的 .abc 文件(ArkTS 编译后的字节码)、resources 目录下的资源索引、module.json5 是否在根目录。如果 resources 目录是空的,说明资源没打包进去,回头检查 resources 目录结构和 build-profile.json5 里的资源配置。如果 .abc 文件缺失,说明代码编译失败但构建没报错,这种情况少见但遇到过,一般是 hvigorfile.ts 里的编译任务被跳过了。
从那以后我每次改完构建配置,都强制走一遍「clean → install → assembleHap → 解压检查」的流程,不省这一步。希望帮到你。
本文还有配套的精品资源,点击获取