Flutter三方库适配OpenHarmony【apple_product_name】oh-package.json5配置详解

前言

欢迎加入开源鸿蒙跨平台社区: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 字段

模块解析流程:

  1. 编译器遇到 import ... from 'apple_product_name'
  2. oh_modules 目录中查找名为 apple_product_name 的模块
  3. 读取该模块的 oh-package.json5,找到 main 字段
  4. 加载 main 指向的入口文件(index.ets

2.3 命名规范

规则 说明 示例
全小写 不使用大写字母 apple_product_name
下划线分隔 多个单词用下划线连接 apple_product_name
不以数字开头 必须以字母开头 3d_plugin
不含特殊字符 仅允许字母、数字、下划线 apple-name
作用域前缀 官方包使用 @ohos/ 前缀 @ohos/flutter_ohos

注意:name 字段的值同时也是 module.json5module.name 的值,两者必须保持一致。如果不一致,编译时会报模块名冲突错误。

三、version 字段详解

3.1 字段定义

json5 复制代码
"version": "1.0.0"

version 遵循语义化版本规范Semantic Versioning),格式为 主版本号.次版本号.修订号

3.2 版本号含义

位置 名称 变更时机 示例
第一位 主版本号(Major) 不兼容的 API 变更 1.0.02.0.0
第二位 次版本号(Minor) 向后兼容的功能新增 1.0.01.1.0
第三位 修订号(Patch) 向后兼容的问题修复 1.0.01.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 应该包含以下要素:

  1. 核心功能:做什么(translating machine identifiers into product names)
  2. 适用平台:for OpenHarmony
  3. 简洁明了:一句话说清楚,不超过 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_nameauthor 字段为空字符串,这在开源项目中是常见的做法------作者信息通常在 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.json5module.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 的模块类型包括:

  1. entry --- 应用入口模块,包含 Ability
  2. feature --- 功能模块,按需加载
  3. har --- 库模块,被其他模块引用
  4. 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

它们的协作关系:

  1. oh-package.json5 告诉包管理器这个模块叫什么、依赖什么
  2. module.json5 告诉编译器这个模块是什么类型、支持什么设备
  3. 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 的包管理器按以下流程解析:

  1. 读取 entry/oh-package.json5dependencies
  2. oh_modules 目录中查找对应的模块
  3. 读取模块的 oh-package.json5,递归解析其依赖
  4. 构建完整的依赖树

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运行时必需 的,因为插件类直接使用了 FlutterPluginMethodChannel
  • 测试框架 @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

  1. ohpm install 读取 oh-package.json5dependencies,下载并安装到 oh_modules
  2. ohpm install <pkg> 将新依赖写入 oh-package.json5dependencies
  3. ohpm 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 排查步骤

当遇到配置相关问题时,按以下顺序排查:

  1. 检查 ohos/oh-package.json5name 字段是否正确
  2. 检查 ohos/index.ets 是否存在且正确导出插件类
  3. 检查 ohos/src/main/module.json5name 是否与 oh-package 一致
  4. 检查 GeneratedPluginRegistrant.ets 的 import 语句
  5. 清理构建缓存:删除 oh_modulesbuild 目录后重新编译

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

  • 相同的字段名:nameversiondescriptionmainauthorlicensedependencies
  • 相同的依赖解析逻辑:从 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 的标准配置范本。

下一篇文章将介绍如何构建设备信息展示页面。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
2501_921930832 小时前
第三方库引入实战指南 Flutter for OpenHarmony:path_provider 文件路径详解
flutter
2501_921930833 小时前
Flutter for OpenHarmony:第三方库实战 chewie 视频播放器UI组件详解
flutter·ui
Sun_gentle3 小时前
android studio创建flutter项目
android·flutter·android studio
Haha_bj3 小时前
Flutter——List.map()
flutter·app
LawrenceLan5 小时前
30.Flutter 零基础入门(三十):GridView 网格布局 —— 九宫格与商品列表必学
开发语言·前端·flutter·dart
fifiAmx5 小时前
Flutter 接入RevenueCat后台配置相关
flutter
不爱吃糖的程序媛5 小时前
Flutter Orientation 插件在鸿蒙平台的使用指南
flutter·华为·harmonyos
2501_921930835 小时前
Flutter for OpenHarmony:三方库引入 geocoding 地理编码详解
flutter