前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
oh-package.json5 是 OpenHarmony 模块的核心配置文件 ,类似于 Node.js 的 package.json 或 Dart 的 pubspec.yaml,它定义了模块的元信息、依赖关系和入口文件。在 Flutter 插件适配 OpenHarmony 的过程中,正确配置这个文件是确保插件能够被正确识别、加载和使用的前提条件 。本文将以 apple_product_name 库的真实配置为基础,逐字段剖析每个配置项的作用、取值规范和最佳实践。
先给出结论式摘要:
- 4 个核心字段 :
name(模块名称)、version(版本号)、main(入口文件)、dependencies(依赖)是模块正常工作的最低要求 - name 字段是导入标识 :
import AppleProductNamePlugin from 'apple_product_name'中的模块名就来自 name 字段 - main 字段指向 index.ets:入口文件负责导出插件类,是模块加载的起点
提示:本文所有配置来源于 apple_product_name 库的
ohos/oh-package.json5文件,建议对照源码阅读。
一、oh-package.json5 完整配置总览
1.1 真实配置文件
以下是 apple_product_name 插件的 ohos/oh-package.json5 完整内容:
json5
// ohos/oh-package.json5
{
"name": "apple_product_name",
"version": "1.0.0",
"description": "Library for translating Apple machine identifiers into Apple product names for OpenHarmony.",
"main": "index.ets",
"author": "",
"license": "MIT",
"dependencies": {
"@ohos/flutter_ohos": "file:./har/flutter.har"
}
}
1.2 字段速查表
| 字段 | 值 | 必填 | 作用 |
|---|---|---|---|
name |
"apple_product_name" |
✓ | 模块名称,用于 import 导入 |
version |
"1.0.0" |
✓ | 语义化版本号 |
description |
"Library for translating..." |
推荐 | 模块功能描述 |
main |
"index.ets" |
✓ | 入口文件路径 |
author |
"" |
可选 | 作者信息 |
license |
"MIT" |
推荐 | 开源许可证 |
dependencies |
{ "@ohos/flutter_ohos": "..." } |
✓ | 运行时依赖 |
1.3 JSON5 格式说明
oh-package.json5 使用 JSON5 格式而非标准 JSON,JSON5 相比 JSON 有以下优势:
- 支持单行注释 (
// 注释)和多行注释 (/* 注释 */) - 支持尾随逗号(最后一个属性后可以加逗号)
- 字符串支持单引号
- 对象键名可以不加引号(如果是合法标识符)
json5
// JSON5 示例 --- 支持注释和尾随逗号
{
name: 'apple_product_name', // 键名无引号,值用单引号
version: '1.0.0',
dependencies: {
"@ohos/flutter_ohos": "file:./har/flutter.har", // 尾随逗号合法
},
}
提示:虽然 JSON5 语法更灵活,但
apple_product_name的配置文件仍然使用了标准 JSON 风格(双引号、无注释),这是为了保持最大兼容性。建议在团队协作中统一风格。
二、name 字段详解
2.1 字段定义
json5
"name": "apple_product_name"
name 是模块的唯一标识符 ,它决定了其他模块如何通过 import 语句引用这个模块。
2.2 name 与 import 的关系
在 GeneratedPluginRegistrant.ets 中,插件的导入语句直接使用了 name 字段的值:
typescript
// example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets
import AppleProductNamePlugin from 'apple_product_name';
// ^^^^^^^^^^^^^^^^^^^^
// 来自 oh-package.json5 的 name 字段
模块解析流程:
- 编译器遇到
import ... from 'apple_product_name' - 在
oh_modules目录中查找名为apple_product_name的模块 - 读取该模块的
oh-package.json5,找到main字段 - 加载
main指向的入口文件(index.ets)
2.3 命名规范
| 规则 | 说明 | 示例 |
|---|---|---|
| 全小写 | 不使用大写字母 | apple_product_name ✓ |
| 下划线分隔 | 多个单词用下划线连接 | apple_product_name ✓ |
| 不以数字开头 | 必须以字母开头 | 3d_plugin ✗ |
| 不含特殊字符 | 仅允许字母、数字、下划线 | apple-name ✗ |
| 作用域前缀 | 官方包使用 @ohos/ 前缀 |
@ohos/flutter_ohos ✓ |
注意:
name字段的值同时也是module.json5中module.name的值,两者必须保持一致。如果不一致,编译时会报模块名冲突错误。
三、version 字段详解
3.1 字段定义
json5
"version": "1.0.0"
version 遵循语义化版本规范 (Semantic Versioning),格式为 主版本号.次版本号.修订号。
3.2 版本号含义
| 位置 | 名称 | 变更时机 | 示例 |
|---|---|---|---|
| 第一位 | 主版本号(Major) | 不兼容的 API 变更 | 1.0.0 → 2.0.0 |
| 第二位 | 次版本号(Minor) | 向后兼容的功能新增 | 1.0.0 → 1.1.0 |
| 第三位 | 修订号(Patch) | 向后兼容的问题修复 | 1.0.0 → 1.0.1 |
3.3 与 pubspec.yaml 版本的关系
apple_product_name 的 Dart 端版本(pubspec.yaml)为 3.7.0,而 OpenHarmony 原生端版本为 1.0.0:
yaml
# pubspec.yaml
name: apple_product_name
version: 3.7.0 # Dart 端版本
json5
// ohos/oh-package.json5
"version": "1.0.0" // 原生端版本
两个版本号独立管理,原因如下:
- Dart 端版本跟随 pub.dev 发布节奏
- 原生端版本跟随 OpenHarmony 适配进度
- 两端的 API 变更频率不同
提示:建议在 README 或 CHANGELOG 中明确标注两端版本的对应关系,避免使用者混淆。
四、description 字段详解
4.1 字段定义
json5
"description": "Library for translating Apple machine identifiers into Apple product names for OpenHarmony."
description 是模块的功能描述,用于在包管理器(ohpm)中展示模块信息。
4.2 编写建议
一个好的 description 应该包含以下要素:
- 核心功能:做什么(translating machine identifiers into product names)
- 适用平台:for OpenHarmony
- 简洁明了:一句话说清楚,不超过 100 个字符
不推荐的写法:
"description": ""--- 空描述,无法被搜索到"description": "A plugin"--- 过于笼统,缺乏信息量"description": "This is a very long description that..."--- 过长,应放在 README 中
五、main 字段详解
5.1 字段定义
json5
"main": "index.ets"
main 指定了模块的入口文件 路径,当其他模块通过 import 导入时,实际加载的就是这个文件。
5.2 入口文件源码
typescript
// ohos/index.ets
import AppleProductNamePlugin from './src/main/ets/components/plugin/AppleProductNamePlugin';
export default AppleProductNamePlugin;
export { AppleProductNamePlugin };
5.3 入口文件的职责
index.ets 作为模块入口,承担了桥梁的角色:
外部 import 'apple_product_name'
→ oh-package.json5 的 main 字段
→ index.ets
→ import from './src/main/ets/.../AppleProductNamePlugin'
→ export default AppleProductNamePlugin
→ 外部获得 AppleProductNamePlugin 类
5.4 为什么不直接指向插件文件
你可能会问:为什么不把 main 直接设为 "./src/main/ets/components/plugin/AppleProductNamePlugin.ets"?
使用 index.ets 作为中间层有以下好处:
- 解耦内部结构 :外部只依赖
index.ets,内部目录结构可以自由调整 - 控制导出范围:只导出需要公开的类和函数,隐藏内部实现
- 支持多导出 :如果模块有多个公开类,可以在
index.ets中统一导出
typescript
// 如果未来需要导出多个类
export default AppleProductNamePlugin;
export { AppleProductNamePlugin };
export { HUAWEI_DEVICE_MAP }; // 可以选择性导出映射表
注意:
main字段的路径是相对于 oh-package.json5 所在目录 的。"main": "index.ets"表示ohos/index.ets,因为oh-package.json5位于ohos/目录下。
六、author 与 license 字段
6.1 author 字段
json5
"author": ""
apple_product_name 的 author 字段为空字符串,这在开源项目中是常见的做法------作者信息通常在 LICENSE 文件和 pubspec.yaml 中维护。
推荐的填写格式:
json5
// 简单格式
"author": "张三"
// 带邮箱
"author": "[name] <[email]>"
// 带主页
"author": "[name] ([url])"
6.2 license 字段
json5
"license": "MIT"
license 声明了模块的开源许可证类型 。apple_product_name 使用 MIT 许可证,这是最宽松的开源许可之一。
常见许可证对比:
| 许可证 | 商用 | 修改 | 分发 | 专利授权 | 必须开源 |
|---|---|---|---|---|---|
| MIT | ✓ | ✓ | ✓ | ✗ | ✗ |
| Apache-2.0 | ✓ | ✓ | ✓ | ✓ | ✗ |
| GPL-3.0 | ✓ | ✓ | ✓ | ✓ | ✓ |
| BSD-3-Clause | ✓ | ✓ | ✓ | ✗ | ✗ |
提示:
license字段的值应与项目根目录的LICENSE文件内容一致。apple_product_name的 LICENSE 文件确认了 MIT 许可证。详见 开源许可证选择指南。
七、dependencies 字段详解
7.1 字段定义
json5
"dependencies": {
"@ohos/flutter_ohos": "file:./har/flutter.har"
}
dependencies 声明了模块的运行时依赖 。apple_product_name 只有一个依赖:@ohos/flutter_ohos。
7.2 依赖声明方式
OpenHarmony 支持多种依赖声明方式:
json5
"dependencies": {
// 1. 本地 HAR 文件引用
"@ohos/flutter_ohos": "file:./har/flutter.har",
// 2. ohpm 仓库版本号
"@ohos/some_lib": "1.0.0",
// 3. ohpm 仓库版本范围
"@ohos/some_lib": "^1.0.0",
// 4. 本地目录引用
"local_module": "file:../local_module"
}
7.3 @ohos/flutter_ohos 提供了什么
@ohos/flutter_ohos 是 OpenHarmony 平台的 Flutter 引擎运行时库,它提供了插件开发所需的全部基础设施:
| 导出类/接口 | 作用 | 在插件中的使用 |
|---|---|---|
FlutterPlugin |
插件生命周期接口 | implements FlutterPlugin |
FlutterPluginBinding |
引擎绑定上下文 | onAttachedToEngine(binding) |
MethodChannel |
方法通道 | new MethodChannel(messenger, name) |
MethodCall |
方法调用封装 | call.method, call.argument() |
MethodCallHandler |
方法处理器接口 | implements MethodCallHandler |
MethodResult |
调用结果回调 | result.success(), result.error() |
7.4 file: 协议说明
json5
"@ohos/flutter_ohos": "file:./har/flutter.har"
file: 前缀表示依赖来自本地文件系统 而非远程仓库。路径 ./har/flutter.har 是相对于 oh-package.json5 所在目录的。
这种本地引用方式的优势:
- 离线可用:不需要网络即可编译
- 版本锁定:使用固定的 HAR 文件,避免远程版本变更导致的兼容问题
- 构建速度:无需下载依赖,编译更快
注意:
file:./har/flutter.har中的flutter.har文件是 Flutter 构建工具在编译时自动生成的,开发者无需手动创建。详见 Flutter OpenHarmony 构建流程。
八、与 pubspec.yaml 的联动关系
8.1 pluginClass 配置
pubspec.yaml 中的 flutter.plugin.platforms.ohos 配置将 Dart 端与原生端连接起来:
yaml
# pubspec.yaml
flutter:
plugin:
platforms:
ohos:
pluginClass: AppleProductNamePlugin
8.2 联动链路
pluginClass 的值 AppleProductNamePlugin 串联了整个配置链路:
pubspec.yaml
→ pluginClass: AppleProductNamePlugin
→ Flutter 构建工具生成 GeneratedPluginRegistrant.ets
→ import AppleProductNamePlugin from 'apple_product_name'
→ oh-package.json5 的 name: "apple_product_name"
→ main: "index.ets"
→ export default AppleProductNamePlugin
8.3 两端配置对照表
| 配置项 | pubspec.yaml | oh-package.json5 | 关系 |
|---|---|---|---|
| 包名 | name: apple_product_name |
"name": "apple_product_name" |
通常一致 |
| 版本 | version: 3.7.0 |
"version": "1.0.0" |
独立管理 |
| 描述 | description: Library for... |
"description": "Library for..." |
内容相似 |
| 许可证 | 无专用字段 | "license": "MIT" |
oh-package 独有 |
| 入口 | 无(Dart 自动解析) | "main": "index.ets" |
oh-package 独有 |
| 插件类 | pluginClass: AppleProductNamePlugin |
无(由 index.ets 导出) | pubspec 独有 |
| 依赖 | dependencies: |
"dependencies": |
各管各端 |
提示:
pubspec.yaml管理 Dart 端依赖(如device_info_plus),oh-package.json5管理原生端依赖(如@ohos/flutter_ohos)。两者互不干扰,各司其职。
九、module.json5 与 oh-package.json5 的关系
9.1 module.json5 源码
json5
// ohos/src/main/module.json5
{
"module": {
"name": "apple_product_name",
"type": "har",
"deviceTypes": [
"default",
"tablet"
]
}
}
9.2 两个配置文件的职责划分
| 维度 | oh-package.json5 | module.json5 |
|---|---|---|
| 位置 | ohos/ 根目录 |
ohos/src/main/ |
| 职责 | 包管理(依赖、版本、入口) | 模块声明(类型、设备兼容) |
| 类比 | Node.js 的 package.json |
Android 的 AndroidManifest.xml |
| name 字段 | 包名(import 标识) | 模块名(编译标识) |
| 必须一致 | ✓ 两个 name 必须相同 | ✓ 两个 name 必须相同 |
9.3 name 一致性要求
oh-package.json5 和 module.json5 中的 name 字段必须完全一致:
json5
// oh-package.json5
"name": "apple_product_name" // ← 必须一致
// module.json5
"module": {
"name": "apple_product_name" // ← 必须一致
}
如果两者不一致,编译时会报错:
ERROR: Module name mismatch between oh-package.json5 and module.json5
9.4 HAR 模块类型
module.json5 中 "type": "har" 表示这是一个 HAR(Harmony Archive) 模块,即可被其他模块引用的库模块。OpenHarmony 的模块类型包括:
- entry --- 应用入口模块,包含 Ability
- feature --- 功能模块,按需加载
- har --- 库模块,被其他模块引用
- shared --- 共享库模块
Flutter 插件的原生端通常是 har 类型。
十、build-profile.json5 配置
10.1 源码
json5
// ohos/build-profile.json5
{
"apiType": "stageMode",
"buildOption": {
},
"targets": [
{
"name": "default"
}
]
}
10.2 字段说明
| 字段 | 值 | 说明 |
|---|---|---|
apiType |
"stageMode" |
使用 Stage 模型(推荐),另一种是 FA 模型 |
buildOption |
{} |
构建选项,可配置混淆、优化等 |
targets |
[{"name": "default"}] |
构建目标,default 为默认配置 |
10.3 三个配置文件的协作
一个完整的 OpenHarmony 模块由三个配置文件共同定义:
ohos/
├── oh-package.json5 ← 包管理:name、version、dependencies、main
├── build-profile.json5 ← 构建配置:apiType、buildOption、targets
└── src/main/
└── module.json5 ← 模块声明:name、type、deviceTypes
它们的协作关系:
oh-package.json5告诉包管理器这个模块叫什么、依赖什么module.json5告诉编译器这个模块是什么类型、支持什么设备build-profile.json5告诉构建工具用什么模式编译、有哪些构建目标
提示:对于 Flutter 插件的原生端,
build-profile.json5通常保持默认配置即可,无需额外修改。
十一、example 项目的 oh-package 配置
11.1 项目级配置
json5
// example/ohos/oh-package.json5
{
"modelVersion": "5.1.0",
"name": "apple_product_name_example",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {},
"devDependencies": {
"@ohos/hypium": "1.0.6"
}
}
11.2 entry 模块配置
json5
// example/ohos/entry/oh-package.json5
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {}
}
11.3 插件模块 vs 应用模块配置对比
| 配置项 | 插件模块 (ohos/) | 应用模块 (example/ohos/) |
|---|---|---|
| name | apple_product_name |
apple_product_name_example |
| main | index.ets(必填) |
""(应用无需入口文件) |
| dependencies | @ohos/flutter_ohos |
{}(通过 entry 管理) |
| devDependencies | 无 | @ohos/hypium(测试框架) |
| modelVersion | 无 | 5.1.0(项目模型版本) |
关键区别:
- 插件模块的
main字段必须指向入口文件,因为其他模块需要通过它导入插件类 - 应用模块的
main字段为空,因为应用通过EntryAbility启动,不需要被其他模块导入 - 应用模块多了
modelVersion字段,标识项目使用的 DevEco Studio 工程模型版本
十二、依赖解析机制
12.1 依赖解析流程
当 entry 模块依赖 apple_product_name 插件时,OpenHarmony 的包管理器按以下流程解析:
- 读取
entry/oh-package.json5的dependencies - 在
oh_modules目录中查找对应的模块 - 读取模块的
oh-package.json5,递归解析其依赖 - 构建完整的依赖树
12.2 oh_modules 目录结构
example/ohos/
├── oh_modules/
│ ├── .ohpm/ ← 包管理器缓存
│ │ ├── @ohos+flutter_ohos@xxx/ ← flutter_ohos 包
│ │ ├── @ohos+hypium@1.0.6/ ← 测试框架包
│ │ └── lock.json5 ← 依赖锁定文件
│ └── @ohos/
│ └── hypium/ ← 符号链接
├── entry/
│ └── oh_modules/
│ ├── @ohos/flutter_ohos/ ← 符号链接
│ ├── apple_product_name/ ← 符号链接
│ └── flutter_native_arm64_v8a/ ← 符号链接
12.3 lock 文件的作用
oh-package-lock.json5 锁定了所有依赖的精确版本,确保团队成员和 CI 环境使用完全相同的依赖版本:
- 首次安装依赖时自动生成
- 应该提交到版本控制系统(Git)
- 使用
ohpm install时优先读取 lock 文件
注意:
oh-package-lock.json5类似于 npm 的package-lock.json或 Dart 的pubspec.lock,是保证构建可重复性的关键文件。
十三、配置字段与源码的映射关系
13.1 完整映射图
每个 oh-package.json5 字段都能在源码中找到对应的使用位置:
| oh-package 字段 | 值 | 源码使用位置 | 使用方式 |
|---|---|---|---|
name |
apple_product_name |
GeneratedPluginRegistrant.ets |
import ... from 'apple_product_name' |
name |
apple_product_name |
module.json5 |
"name": "apple_product_name" |
main |
index.ets |
模块加载器 | 解析 import 时加载此文件 |
dependencies |
@ohos/flutter_ohos |
AppleProductNamePlugin.ets |
import { FlutterPlugin, ... } from '@ohos/flutter_ohos' |
version |
1.0.0 |
包管理器 | 版本冲突检测 |
license |
MIT |
ohpm 仓库 | 许可证展示 |
13.2 name 字段的三处使用
name 字段的值 "apple_product_name" 在项目中出现了三次,且必须完全一致:
typescript
// 1. oh-package.json5 --- 定义
"name": "apple_product_name"
// 2. module.json5 --- 声明
"module": { "name": "apple_product_name" }
// 3. GeneratedPluginRegistrant.ets --- 使用
import AppleProductNamePlugin from 'apple_product_name';
13.3 通道名称的巧合
值得注意的是,MethodChannel 的通道名称也是 "apple_product_name":
typescript
// AppleProductNamePlugin.ets
this.channel = new MethodChannel(binding.getBinaryMessenger(), "apple_product_name");
这不是强制要求,但 apple_product_name 选择让包名、模块名、通道名三者一致,这是一种简洁的命名策略,减少了记忆负担。
十四、devDependencies 与 dependencies 的区别
14.1 两种依赖类型
json5
{
"dependencies": {
"@ohos/flutter_ohos": "file:./har/flutter.har" // 运行时依赖
},
"devDependencies": {
"@ohos/hypium": "1.0.6" // 开发时依赖
}
}
14.2 区别对比
| 维度 | dependencies | devDependencies |
|---|---|---|
| 打包 | 包含在最终产物中 | 不包含在最终产物中 |
| 用途 | 运行时必需 | 测试、构建工具 |
| 示例 | @ohos/flutter_ohos |
@ohos/hypium |
| 传递性 | 被依赖方也会安装 | 不会传递给依赖方 |
14.3 apple_product_name 的依赖策略
apple_product_name 插件模块只声明了 dependencies,没有 devDependencies:
@ohos/flutter_ohos是运行时必需 的,因为插件类直接使用了FlutterPlugin、MethodChannel等- 测试框架
@ohos/hypium只在example项目中声明,因为测试代码在 example 中
提示:保持插件模块的依赖最小化是一种好的实践。
apple_product_name只依赖@ohos/flutter_ohos,没有引入任何额外的第三方库。
十五、ohpm 包管理器
15.1 ohpm 简介
ohpm(OpenHarmony Package Manager)是 OpenHarmony 的官方包管理器,类似于 npm(Node.js)或 pub(Dart)。
15.2 常用命令
| 命令 | 作用 | 类比 |
|---|---|---|
ohpm install |
安装所有依赖 | npm install / flutter pub get |
ohpm install <pkg> |
安装指定包 | npm install <pkg> |
ohpm uninstall <pkg> |
卸载指定包 | npm uninstall <pkg> |
ohpm list |
列出已安装的包 | npm list |
ohpm info <pkg> |
查看包信息 | npm info <pkg> |
15.3 ohpm 与 oh-package.json5 的关系
ohpm 命令操作的核心就是 oh-package.json5:
ohpm install读取oh-package.json5的dependencies,下载并安装到oh_modulesohpm install <pkg>将新依赖写入oh-package.json5的dependenciesohpm uninstall <pkg>从oh-package.json5中移除依赖声明
提示:在 Flutter 插件开发中,依赖通常由 Flutter 构建工具自动管理,开发者很少需要直接使用
ohpm命令。但了解其工作原理有助于排查依赖问题。详见 OpenHarmony 包管理文档。
十六、配置验证演示页面
16.1 验证思路
为了验证 oh-package.json5 的配置是否正确,我们编写了一个 Flutter 演示页面,通过实际调用插件 API 来反向验证配置链路的完整性:
- 如果
name字段错误 →import失败 → 插件未注册 →MissingPluginException - 如果
main字段错误 → 入口文件加载失败 → 插件类未导出 → 编译错误 - 如果
dependencies缺失 →FlutterPlugin接口不可用 → 编译错误
16.2 完整演示代码
dart
import 'package:apple_product_name/apple_product_name_ohos.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'oh-package 配置验证',
theme: ThemeData(primarySwatch: Colors.brown),
home: const Article22DemoPage(),
);
}
}
/// 第22篇文章演示页面:oh-package.json5 配置详解
/// 点击按钮后验证 oh-package 配置链路是否正确:
/// 模块名称解析、入口文件加载、依赖关系、插件导出等
class Article22DemoPage extends StatefulWidget {
const Article22DemoPage({Key? key}) : super(key: key);
@override
State<Article22DemoPage> createState() => _Article22DemoPageState();
}
class _Article22DemoPageState extends State<Article22DemoPage> {
final List<_ConfigCheck> _checks = [];
bool _hasRun = false;
bool _isRunning = false;
Future<void> _runConfigCheck() async {
if (_isRunning) return;
setState(() {
_checks.clear();
_isRunning = true;
_hasRun = true;
});
final ohos = OhosProductName();
const channel = MethodChannel('apple_product_name');
// ① name 字段验证 --- 模块名称解析
try {
await ohos.getMachineId();
_add(
field: 'name',
config: '"name": "apple_product_name"',
desc: 'import AppleProductNamePlugin from "apple_product_name"\n'
'模块名称正确解析,插件成功导入',
status: '✓ 模块名称解析成功',
success: true,
);
} on MissingPluginException {
_add(
field: 'name',
config: '"name": "apple_product_name"',
desc: '模块名称解析失败,插件未被找到',
status: '✗ MissingPluginException',
success: false,
);
setState(() => _isRunning = false);
return;
}
// ② main 字段验证 --- 入口文件加载
try {
final id = await ohos.getMachineId();
_add(
field: 'main',
config: '"main": "index.ets"',
desc: 'index.ets → export default AppleProductNamePlugin\n'
'入口文件正确加载,插件类成功导出',
status: '✓ 入口文件加载成功 → getMachineId="$id"',
success: true,
);
} catch (e) {
_add(
field: 'main',
config: '"main": "index.ets"',
desc: '入口文件加载失败',
status: '✗ $e',
success: false,
);
}
// ③ dependencies 字段验证
try {
final name = await ohos.getProductName();
_add(
field: 'dependencies',
config: '"@ohos/flutter_ohos": "file:./har/flutter.har"',
desc: 'FlutterPlugin、MethodChannel、MethodCallHandler\n'
'均来自 @ohos/flutter_ohos 依赖包',
status: '✓ flutter_ohos 依赖正常 → getProductName="$name"',
success: true,
);
} catch (e) {
_add(
field: 'dependencies',
config: '"@ohos/flutter_ohos": "file:./har/flutter.har"',
desc: '@ohos/flutter_ohos 依赖异常',
status: '✗ $e',
success: false,
);
}
// ④ pluginClass 配置验证
try {
final result = await ohos.lookup('CFR-AN00');
_add(
field: 'pluginClass',
config: 'pubspec.yaml → pluginClass: AppleProductNamePlugin',
desc: 'Flutter 构建工具通过 pluginClass 生成注册代码\n'
'GeneratedPluginRegistrant 自动 import 并实例化',
status: '✓ pluginClass 配置正确 → lookup="$result"',
success: true,
);
} catch (e) {
_add(
field: 'pluginClass',
config: 'pubspec.yaml → pluginClass: AppleProductNamePlugin',
desc: 'pluginClass 配置异常',
status: '✗ $e',
success: false,
);
}
// ⑤ 通道名称验证
try {
final id = await channel.invokeMethod<String>('getMachineId');
_add(
field: 'MethodChannel',
config: 'Dart: MethodChannel("apple_product_name")\n'
'原生: MethodChannel(messenger, "apple_product_name")',
desc: '通道名称通常与 oh-package name 字段一致\n确保双端通信正确建立',
status: '✓ 通道名称匹配 → "$id"',
success: id != null && id.isNotEmpty,
);
} catch (e) {
_add(
field: 'MethodChannel',
config: '通道名称: "apple_product_name"',
desc: '通道名称不匹配或 Handler 未注册',
status: '✗ $e',
success: false,
);
}
// ⑥ version 字段说明
_add(
field: 'version',
config: '"version": "1.0.0"',
desc: '语义化版本:主版本.次版本.修订号\n建议与 pubspec.yaml 版本保持同步',
status: '✓ 版本号格式正确',
success: true,
);
// ⑦ license 字段说明
_add(
field: 'license',
config: '"license": "MIT"',
desc: 'MIT 许可证:允许自由使用、修改和分发\n只需保留版权声明',
status: '✓ 许可证已声明',
success: true,
);
// ⑧ description 字段说明
_add(
field: 'description',
config: '"description": "Library for translating..."',
desc: '模块功能描述,用于 ohpm 搜索和展示\n建议简洁明了,包含核心功能关键词',
status: '✓ 描述已填写',
success: true,
);
// ⑨ 完整配置链路端到端验证
try {
final id = await ohos.getMachineId();
final name = await ohos.getProductName();
final lookup = await ohos.lookup(id);
_add(
field: '端到端链路',
config: 'oh-package.json5 → index.ets → Plugin → MethodChannel → Dart',
desc: 'getMachineId="$id"\ngetProductName="$name"\nlookup($id)="$lookup"',
status: '✓ 配置链路完整,所有 API 正常工作',
success: true,
);
} catch (e) {
_add(
field: '端到端链路',
config: 'oh-package.json5 → index.ets → Plugin → MethodChannel → Dart',
desc: '端到端验证失败',
status: '✗ $e',
success: false,
);
}
// ⑩ 注册完整性验证
try {
await channel.invokeMethod('__config_test__');
_add(
field: '注册完整性',
config: 'onMethodCall → default → result.notImplemented()',
desc: '未知方法应触发 notImplemented',
status: '✗ 意外成功',
success: false,
);
} on MissingPluginException {
_add(
field: '注册完整性',
config: 'onMethodCall → default → result.notImplemented()',
desc: '未知方法正确触发 MissingPluginException\n'
'证明 setMethodCallHandler(this) 已生效',
status: '✓ 插件注册完整,Handler 正常工作',
success: true,
);
} catch (e) {
_add(
field: '注册完整性',
config: 'default 分支',
desc: '异常',
status: '✗ $e',
success: false,
);
}
setState(() => _isRunning = false);
}
void _add({
required String field,
required String config,
required String desc,
required String status,
required bool success,
}) {
setState(() {
_checks.add(_ConfigCheck(
field: field, config: config,
desc: desc, status: status, success: success,
));
});
}
@override
Widget build(BuildContext context) {
final passCount = _checks.where((c) => c.success).length;
final total = _checks.length;
return Scaffold(
appBar: AppBar(title: const Text('oh-package 配置验证'), centerTitle: true),
body: Column(children: [
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
color: !_hasRun ? Colors.grey.shade100
: _isRunning ? Colors.orange.shade50
: (passCount == total ? Colors.green.shade50 : Colors.red.shade50),
child: Column(children: [
Text(
!_hasRun ? '点击下方按钮开始验证'
: _isRunning ? '⏳ 配置验证中...'
: (passCount == total ? '✅ 全部配置验证通过' : '⚠️ 部分配置异常'),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold,
color: !_hasRun ? Colors.grey.shade600
: _isRunning ? Colors.orange.shade800
: (passCount == total ? Colors.green.shade800 : Colors.red.shade800)),
),
if (_hasRun) ...[
const SizedBox(height: 4),
Text('通过 $passCount / $total 项',
style: TextStyle(fontSize: 14, color: Colors.grey.shade700)),
],
]),
),
Padding(
padding: const EdgeInsets.all(12),
child: SizedBox(width: double.infinity, height: 48,
child: ElevatedButton.icon(
onPressed: _isRunning ? null : _runConfigCheck,
icon: Icon(_isRunning ? Icons.hourglass_top : Icons.settings),
label: Text(_isRunning ? '验证中...'
: (_hasRun ? '重新验证配置' : '开始验证 oh-package 配置'),
style: const TextStyle(fontSize: 16)),
),
),
),
Expanded(
child: _checks.isEmpty
? Center(child: Column(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.description_outlined, size: 64, color: Colors.grey.shade300),
const SizedBox(height: 12),
Text('点击按钮验证 oh-package.json5 配置链路',
style: TextStyle(fontSize: 15, color: Colors.grey.shade500)),
]))
: ListView.separated(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
itemCount: _checks.length,
separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (context, index) {
final item = _checks[index];
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: item.success ? Colors.green.shade50 : Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: item.success
? Colors.green.shade200 : Colors.red.shade200),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Icon(item.success ? Icons.check_circle : Icons.error,
color: item.success ? Colors.green : Colors.red, size: 20),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.brown.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(4)),
child: Text(item.field, style: const TextStyle(
fontSize: 11, fontWeight: FontWeight.w600, color: Colors.brown)),
),
]),
const SizedBox(height: 8),
Container(
width: double.infinity, padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey.shade100, borderRadius: BorderRadius.circular(4)),
child: Text(item.config, style: TextStyle(fontSize: 11,
color: Colors.grey.shade700, height: 1.4, fontFamily: 'monospace')),
),
const SizedBox(height: 6),
Text(item.desc, style: TextStyle(
fontSize: 11, color: Colors.grey.shade600, height: 1.3)),
const SizedBox(height: 4),
Text(item.status, style: TextStyle(fontSize: 12,
color: item.success ? Colors.green.shade800 : Colors.red.shade800,
fontWeight: FontWeight.w500)),
]),
);
},
),
),
]),
);
}
}
class _ConfigCheck {
final String field, config, desc, status;
final bool success;
const _ConfigCheck({
required this.field, required this.config,
required this.desc, required this.status, required this.success,
});
}
16.3 验证项说明
| 序号 | 验证字段 | 验证方式 | 预期结果 |
|---|---|---|---|
| ① | name | 调用 getMachineId | 成功返回 → 模块名称解析正确 |
| ② | main | 调用 getMachineId | 成功返回 → index.ets 加载正确 |
| ③ | dependencies | 调用 getProductName | 成功返回 → flutter_ohos 依赖正常 |
| ④ | pluginClass | 调用 lookup | 成功返回 → 注册代码生成正确 |
| ⑤ | MethodChannel | 直接调用通道 | 成功返回 → 通道名称匹配 |
| ⑥ | version | 静态检查 | 格式正确 |
| ⑦ | license | 静态检查 | 已声明 |
| ⑧ | description | 静态检查 | 已填写 |
| ⑨ | 端到端链路 | 三个 API 连续调用 | 全部成功 → 配置链路完整 |
| ⑩ | 注册完整性 | 调用未知方法 | MissingPluginException → Handler 正常 |
十七、配置错误排查指南
17.1 常见错误一览
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| 编译报 "Cannot find module" | name 字段与 import 不匹配 |
对比 import 语句和 oh-package name |
| 编译报 "Cannot find entry" | main 字段路径错误 |
检查 index.ets 是否存在 |
| 运行时 MissingPluginException | 插件未注册或通道名不匹配 | 检查 GeneratedPluginRegistrant |
| 编译报 "Module not found" | dependencies 未声明 |
检查依赖是否在 oh-package 中声明 |
| 编译报 "Name mismatch" | oh-package 和 module.json5 name 不一致 | 对比两个文件的 name 字段 |
17.2 排查步骤
当遇到配置相关问题时,按以下顺序排查:
- 检查
ohos/oh-package.json5的name字段是否正确 - 检查
ohos/index.ets是否存在且正确导出插件类 - 检查
ohos/src/main/module.json5的name是否与 oh-package 一致 - 检查
GeneratedPluginRegistrant.ets的 import 语句 - 清理构建缓存:删除
oh_modules和build目录后重新编译
17.3 清理缓存命令
bash
# 清理 oh_modules 缓存
rm -rf ohos/oh_modules
rm -rf example/ohos/oh_modules
rm -rf example/ohos/entry/oh_modules
# 清理构建产物
rm -rf ohos/build
rm -rf example/ohos/entry/build
# 重新安装依赖
cd example/ohos && ohpm install
提示:大部分配置问题都可以通过清理缓存后重新编译解决。如果问题仍然存在,建议逐字段对比本文的配置说明。
十八、配置最佳实践
18.1 必填字段清单
创建一个新的 OpenHarmony Flutter 插件时,oh-package.json5 至少需要以下字段:
json5
{
"name": "your_plugin_name", // 必填:模块名称
"version": "1.0.0", // 必填:版本号
"main": "index.ets", // 必填:入口文件
"dependencies": {
"@ohos/flutter_ohos": "file:./har/flutter.har" // 必填:Flutter 运行时
}
}
18.2 推荐的完整配置模板
json5
{
"name": "your_plugin_name",
"version": "1.0.0",
"description": "A brief description of what your plugin does.",
"main": "index.ets",
"author": "Your Name",
"license": "MIT",
"dependencies": {
"@ohos/flutter_ohos": "file:./har/flutter.har"
}
}
18.3 注意事项
编写 oh-package.json5 时需要注意以下几点:
name字段值必须与module.json5中的module.name完全一致main字段指向的文件必须实际存在且正确导出插件类dependencies中的@ohos/flutter_ohos是 Flutter 插件的必需依赖version建议遵循语义化版本规范,便于版本管理license建议填写,尤其是开源项目
十九、与其他平台配置文件的对比
19.1 跨平台配置对比
| 维度 | OpenHarmony | Android | iOS | Node.js |
|---|---|---|---|---|
| 配置文件 | oh-package.json5 |
build.gradle |
Podspec |
package.json |
| 格式 | JSON5 | Groovy/KTS | Ruby DSL | JSON |
| 包名字段 | name |
namespace |
name |
name |
| 版本字段 | version |
versionName |
version |
version |
| 入口字段 | main |
无 | 无 | main |
| 依赖字段 | dependencies |
dependencies |
dependency |
dependencies |
| 包管理器 | ohpm | Gradle | CocoaPods | npm |
19.2 与 package.json 的相似性
oh-package.json5 的设计明显借鉴了 Node.js 的 package.json:
- 相同的字段名:
name、version、description、main、author、license、dependencies - 相同的依赖解析逻辑:从
node_modules/oh_modules目录查找 - 相同的 lock 文件机制:
package-lock.json/oh-package-lock.json5
这种设计降低了前端开发者的学习成本,如果你熟悉 package.json,那么 oh-package.json5 几乎可以零学习成本上手。
二十、配置字段完整参考
20.1 所有支持的字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | ✓ | 模块名称,用于 import |
version |
string | ✓ | 语义化版本号 |
description |
string | 推荐 | 模块功能描述 |
main |
string | 库模块必填 | 入口文件路径 |
author |
string | 可选 | 作者信息 |
license |
string | 推荐 | 开源许可证 |
dependencies |
object | 按需 | 运行时依赖 |
devDependencies |
object | 按需 | 开发时依赖 |
modelVersion |
string | 项目级 | DevEco 工程模型版本 |
keywords |
array | 可选 | 搜索关键词 |
repository |
string | 可选 | 源码仓库地址 |
homepage |
string | 可选 | 项目主页 |
20.2 apple_product_name 使用了哪些字段
json5
{
"name": "apple_product_name", // ✓ 使用
"version": "1.0.0", // ✓ 使用
"description": "Library for...", // ✓ 使用
"main": "index.ets", // ✓ 使用
"author": "", // △ 声明但为空
"license": "MIT", // ✓ 使用
"dependencies": { ... } // ✓ 使用
// keywords --- 未使用
// repository --- 未使用
// homepage --- 未使用
}
7 个字段中使用了 6 个(author 为空),这是一个精简但完整的配置。
总结
oh-package.json5 是 OpenHarmony 模块的身份证,它通过 name 定义模块标识、通过 main 指定入口文件、通过 dependencies 声明依赖关系,三者共同构成了模块被正确加载和使用的基础。apple_product_name 的配置简洁而完整,7 个字段覆盖了插件模块的全部需求,是 Flutter 插件适配 OpenHarmony 的标准配置范本。
下一篇文章将介绍如何构建设备信息展示页面。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- OpenHarmony适配仓库:apple_product_name
- 开源鸿蒙跨平台社区:openharmonycrossplatform.csdn.net
- 语义化版本规范:semver.org
- 开源许可证选择:choosealicense.com
- Flutter 插件开发指南:flutter.dev
- Flutter 平台通道文档:flutter.dev
- OpenHarmony Flutter 引擎:gitee.com
- OpenHarmony 开发文档:developer.huawei.com