Flutter动态JSON组件鸿蒙适配实战:json_component纯Dart能力兼容改造与全场景功能落地
开发工具: 华为云码道
本文配套仓库: 上游 herisetiawan00/json-component;
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/json-component
json_component 把"JSON 描述 → Flutter Widget"的映射封装成纯 Dart 能力:BaseJsonComponent 定义组件基类,JsonComponent 提供单例注册表(initialize / register / build / setState / clear),ComponentBuilderWidget 负责生命周期与状态通知,StringExtension.withContext() 解析 $state.xxx 模板变量。平台通道只有一个模板方法 getPlatformVersion,其余渲染逻辑完全不经过原生。本文以 json_component 0.0.3 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 构建。
插件目前支持 ohos 平台,上游源码位于 GitHub 仓库。文中的代码以上游 main 分支为适配基线,OHOS 改动记录在本地提交 22f1fe7d7c03f8081ab3ecb8b1ecdbd980544cd8,并按 0.0.3-ohos-0.0.1-beta.1 打 TAG 发布。

OHOS 适配位于本地仓库提交 22f1fe7 的 ohos/、example/ohos/、README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md、docs/OHOS_Adaptation_Blog.md 与 docs/screenshots/。

一、插件简介与适配目标
在很多业务场景中,页面结构需要后端下发、运营配置或 AB 实验动态调整。json_component 提供了一种轻量方案:用 JSON 描述组件(_id 标识类型、_key 标识状态、_state 给出初始状态),在 Dart 端注册对应构造器后,JsonComponent.build() 即可把 JSON 转换成真正的 Flutter Widget;状态更新通过 JsonComponent.setState() 下发,组件内部用 $state.xxx 模板变量读取最新值。
这套核心能力全部由纯 Dart 实现(lib/json_component.dart、component_builder.dart、components/base_json_component.dart、common/extensions/string_extension.dart 等),不触碰任何平台 API,天然跨平台一致。插件真正的原生依赖只有一个模板方法:getPlatformVersion 通过 MethodChannel('json_component') 查询系统版本。
正因如此,这个插件的 OHOS 适配工作量极小:在 pubspec.yaml 声明 ohos 平台并生成 HAR 模块;在 ArkTS 中实现 JsonComponentPlugin,用 deviceInfo.osFullName 返回与 Android/iOS 语义一致的版本字符串;核心组件框架零改动。
| 操作 | 预期表现 |
|---|---|
| 点击「+1」按钮 | 计数器数值 + 1,界面实时刷新展示最新计数 |
| 点击「重置」按钮 | 计数器清零,界面刷新显示计数为 0 |
| 点击「切换用户 / 角色」按钮 | 切换全局 $state 状态,用户卡片更新头像首字母、用户名与角色,计数器数值保持不变 |
| 修改全局 state.name/state.role | UI 自动响应状态变更,用户信息卡片重新渲染 |
| json_component 框架渲染 | 根据 JSON 描述注册并构建组件,支持多组件注册、状态驱动更新、模板变量解析 |
| 页面初始化加载 | 渲染全部卡片组件,用户默认 David,计数初始值为 0,展示平台、框架版本与核心能力清单 |
以下是操作的视屏,可以参考一下:
二、环境准备
环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。
完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:
flutter --version
flutter doctor -v
hdc list targets


工程使用的工具链和 SDK 配置如下:
| 项目 | 版本或配置 | 用途 |
|---|---|---|
| Flutter OHOS SDK | 3.44.9+ohos-0.0.1-canary1 | Flutter 编译与 OHOS 平台工具链 |
| Flutter 分支 | 0.0.3-ohos-0.0.1-beta.1 | CPF-Flutter 对应开发分支 |
| Dart SDK | 3.12.2 | Dart 语言与包管理环境 |
| HarmonyOS 开发套件 | 26.0.0(API 26) | 开发套件版本及对应的 API 级别 |
compatibleSdkVersion | 5.1.0(18) | 当前工程声明的最低兼容版本 |
runtimeOS | HarmonyOS | 真机构建目标运行时 |
| 插件版本 | 0.0.3 | pubspec.yaml 中的包版本 |
| OHOS 发布 TAG | 0.0.3-ohos-0.0.1-beta.1 | 本次适配的发布标记 |
| 原生语言 | ArkTS | HarmonyOS 插件实现 |
| 插件产物 | HAR | 被应用 entry 模块依赖 |
2.1 开发套件版本与工程中的 SDK 版本配置
26.0.0(API 26) 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:
26.0.0(API 26)表示本机 DevEco Studio 安装的开发套件为26.0.0,对应 API 26,Flutter 工具链构建时使用该 SDK;- 本工程的
example/ohos/build-profile.json5没有显式声明compileSdkVersion和targetSdkVersion,构建时按开发套件默认的 API 26 编译; 5.1.0(18)是本文工程中compatibleSdkVersion的属性值,声明最低兼容 API 18;runtimeOS为HarmonyOS,说明产物面向 HarmonyOS 真机运行。
对应的 product 配置为:
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.1.0(18)",
"runtimeOS": "HarmonyOS"
}

这组配置最低兼容 API 18。本插件原生侧只用到了 @ohos.deviceInfo 的 osFullName 字段,该能力自 API 7 起即可用,也不需要任何运行时权限,因此 API 18 及以上设备均可运行。本例真机验证环境为 OpenHarmony-6.1.1.120(API 24)。
三、从源码仓库开始准备适配工程
3.1 确认上游源码仓库与同步方式
适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。
在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。
json_component 的上游位于 GitHub(仓库名与 Dart 包名均为 json_component),采用 BSD-3-Clause 许可证(Copyright © 2025 Heri Setiawan)。本仓库 README.OpenHarmony_CN.md 的安装地址直接使用了 GitHub 仓库,没有 AtomGit 占位;若后续需要在 AtomGit 发布,应创建配套仓库(社区适配仓库常按 fluttertpc_ 前缀命名)并同步更新 README 中的安装说明。
3.2 将代码拉取到宿主机
在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:
git clone https://github.com/herisetiawan00/json-component.git
cd json-component
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 json-component/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml、lib/ 和 example/。本仓库的仓库名与 Dart 包名一致,都是 json_component,因此后续 --project-name 也传 json_component。
需要使用与本文相同的代码版本时,切换到以下提交:
git switch --detach a3b7e05f1d1de78b08f2ce42ec2a018dbee5643c
该提交即本次 OHOS 适配提交,其父提交为上游 main 分支的 a3b7e05f1d1de78b08f2ce42ec2a018dbee5643c。适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

图 1:在宿主机终端输入仓库拉取命令。
3.3 在仓库根目录确认分支与发布 TAG
json_component 的鸿蒙改动直接基于上游默认分支 main 维护,并在发布时通过 TAG 标记 OHOS 版本。在仓库根目录执行:
git branch --show-current
git tag 0.0.3-ohos-0.0.1-beta.1
git tag -l
本例按仓库现有工作方式直接以 main + TAG 发布:适配提交 22f1fe7 位于本地 main(领先上游一个提交),TAG 0.0.3-ohos-0.0.1-beta.1 指向该提交。如果习惯使用适配分支,也可以先创建 feat/ohos_json_component_0.0.3,完成后合并回 main 再打 TAG。

图 2:在 json_component 仓库根目录确认分支与 TAG。
3.4 自动补全 OHOS 适配结构
分支确认后,仍在同一个插件根目录执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件:
flutter create --template=plugin --platforms=ohos --project-name json_component .
git status --short
git diff -- pubspec.yaml .metadata
--template=plugin指定插件模板。--platforms=ohos指定需要补全的平台。--project-name json_component使用 Dart 包名,与pubspec.yaml中的name保持一致。- 最后的
.表示在当前插件目录补全工程,不是另建一层目录。
在上游基线 a3b7e05 上执行后,git status --short 的输出为:
M .metadata
M pubspec.yaml
M ohos/
M example/ohos/
pubspec.yaml 的变化主要是新增 ohos: pluginClass: JsonComponentPlugin。.metadata 记录了 ohos 平台的创建信息;ohos/ 与 example/ohos/ 是新生成的 HAR 脚手架和宿主工程。
还要注意清理模板多余产物:flutter create 会按当前插件模板重建各平台,可能生成 lib/json_component_platform_interface.dart、lib/json_component_method_channel.dart、lib/json_component_web.dart 等 federated 骨架或 web 文件。本仓库的 Dart 层在适配提交中保持零改动,清理后不应把这些模板文件提交。实际当前仓库的 test/json_component_method_channel_test.dart 和 example/integration_test/plugin_integration_test.dart 仍引用已被删除的模板文件,将在 8.1 中说明处理方式。
如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:
cd example
flutter create --platforms=ohos .
cd ..
本机执行该命令时曾因 Xcode 13.2.1 过旧、工具探测 example/ios 时调用 xcodebuild 失败而崩溃,实际采用的规避方式是:在干净目录生成 ohos 模板后再整体拷贝 ohos/ 与 example/ohos/ 到仓库(详见 9.11)。

图 3:在插件根目录输入 OHOS 结构补全命令。
3.5 适配后的项目目录
适配后的关键目录如下:
json-component/
├── lib/
│ ├── json_component.dart # 对外 API(JsonComponent 单例)
│ ├── component_builder.dart # ComponentBuilderWidget 生命周期与状态通知
│ ├── components/
│ │ └── base_json_component.dart # BaseJsonComponent 基类
│ └── common/
│ ├── extensions/ # null / string 模板变量扩展
│ └── types/ # 类型定义与异常
├── ohos/
│ ├── index.ets
│ ├── oh-package.json5
│ ├── build-profile.json5
│ ├── hvigorfile.ts
│ └── src/main/
│ ├── ets/components/plugin/JsonComponentPlugin.ets
│ └── module.json5
├── example/
│ ├── lib/main.dart # 多组件动态渲染演示
│ └── ohos/entry/
├── android/
├── ios/
├── macos/
├── windows/
├── linux/
├── web/
├── docs/
│ ├── OHOS_Adaptation_Blog.md
│ └── screenshots/ # 真机截图
└── pubspec.yaml
项目根目录如下,其中包含 ohos/、example/ohos/、已补齐的 README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md,以及归档真机截图的 docs/screenshots/:

图 4:适配后的 json_component 项目根目录。
| 文件 | 主要职责 |
|---|---|
lib/json_component.dart | 对外入口:initialize / register / build / setState / clear |
lib/component_builder.dart | ComponentBuilderWidget:状态通知、生命周期、dispose 清理 |
lib/components/base_json_component.dart | BaseJsonComponent 基类与 ComponentJson / ComponentState 类型 |
JsonComponentPlugin.ets | 响应 getPlatformVersion,返回系统版本字符串 |
插件 module.json5 | 声明 HAR 模块 |
示例 entry module.json5 | 声明宿主 Ability、设备类型和 INTERNET 权限 |
example/lib/main.dart | 完整 Demo:文本、用户卡片、计数器、信息卡片、列表 |
docs/screenshots/ | 真机运行截图 |
四、Dart 接口与通道分析
OHOS 适配前先理清通道契约:这个插件绝大部分逻辑是纯 Dart,唯一经过平台通道的只有模板方法 getPlatformVersion。先阅读 lib/ 的全部文件和 Android/iOS 的原生实现,再在 ohos/src/main/ets/components/plugin/ 中实现对应的原生类。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。
本例的对应关系如下:
| Dart 入口或模型 | 实际依赖 | OHOS 实现 | 应保持的行为 |
|---|---|---|---|
JsonComponent 系列静态方法 | 纯 Dart | 无需适配 | 跨平台行为一致 |
getPlatformVersion(模板方法) | 插件通道 json_component | JsonComponentPlugin.ets 返回系统版本 | 返回 "OpenHarmony x.y" 字符串 |
通道名和方法名属于跨语言协议。任何一端拼写不一致,都会让 getPlatformVersion 直接抛出 MissingPluginException。
4.1 跨端架构与调用时序
Flutter 侧和 HarmonyOS 侧之间是一条极窄的单向拉取链路,没有事件订阅和平台视图:
- 原生链路:Flutter 页面读取平台版本,经
MethodChannel('json_component')以invokeMethod('getPlatformVersion')发起一次请求,JsonComponentPlugin.ets返回系统版本字符串; - 纯 Dart 链路:JSON 解析、组件注册表、状态合并、模板变量替换全部在 Dart isolate 内完成,完全不经过原生,也就不存在跨端差异。
插件通道一次调用、一次应答,没有需要取消的订阅。组件状态则通过 ValueNotifier + ValueListenableBuilder 在 Widget 内部局部刷新。
4.1.1 一次完整平台版本查询的时序
4.2 对外 API 入口:lib/json_component.dart
JsonComponent 是一个单例注册表,所有 API 都是静态方法:
class JsonComponent {
static void initialize();
static JsonComponent get instance;
static void register(String id, ComponentBuilder builder);
static bool containsId(String id);
static void setState(String key, ComponentState state);
static void clear();
static Widget build(BuildContext context, ComponentJson json, [ComponentContext cContext]);
}
| 成员 | 签名 | 行为 |
|---|---|---|
initialize | static void initialize() | 创建内部单例,必须在 register 前调用 |
register | static void register(String id, ComponentBuilder builder) | 把组件构造器注册到 _id 映射表;重复注册抛 ComponentRegisteredException |
build | static Widget build(BuildContext context, ComponentJson json, [ComponentContext]) | 根据 JSON 的 _id 找到构造器,生成 Widget;缺少 _id 或未注册时抛对应异常 |
setState | static void setState(String key, ComponentState state) | 按 _key 定位组件,把新状态合并到旧状态,触发重建 |
clear | static void clear() | 清空注册表与状态回调 |
这是库的核心入口;平台版本查询通常只在示例页展示,业务侧主要使用 register + build + setState。JsonComponent 没有任何实例状态,重复调用互不干扰。
4.3 公开 API 与平台接口
公开 API 分成两部分:纯 Dart 组件框架加一个小型通道入口,没有 platform_interface 抽象层,也没有 Stream:
lib/
├── json_component.dart # 对外 API(JsonComponent 单例注册表)
├── component_builder.dart # ComponentBuilderWidget 生命周期封装
├── components/
│ └── base_json_component.dart # BaseJsonComponent 基类
└── common/
├── extensions/
│ ├── null_extension.dart # `.let` 扩展
│ └── string_extension.dart # `$state.xxx` 模板变量解析
└── types/
├── component_type.dart # ComponentJson / ComponentState / ComponentContext / ComponentBuilder
└── component_exception.dart # ComponentRegisteredException 等
| 公开 API | 签名 | 说明 |
|---|---|---|
JsonComponent.initialize | void | 初始化单例 |
JsonComponent.register | void | 注册组件构造器 |
JsonComponent.containsId | bool | 判断组件是否已注册 |
JsonComponent.setState | void | 按 _key 更新组件状态 |
JsonComponent.clear | void | 清空所有注册与状态 |
JsonComponent.build | Widget | 根据 JSON 构建 Widget |
StringExtension.withContext | String | 把 $state.xxx 替换为真实值 |
- 组件构造链路:
ComponentJson→ComponentBuilder→BaseJsonComponent→Widget; - 状态链路:
setState(key, state)→ 对应_registeredState[key]回调 →ValueNotifier.value更新 →ValueListenableBuilder重建; - 模板链路:字符串中的
$state.path被withContext(cContext)解析为cContext['state']?['path']; - 异常处理:缺少
_id抛ComponentInvalidException、未注册_id抛ComponentNotFoundException、构造失败抛ComponentParsingException。
pubspec.yaml 中的多端 pluginClass: JsonComponentPlugin 用于原生插件注册。本库没有原生业务逻辑,OHOS 上无需任何原生对应物。
4.4 Dart 通道协议分析
4.4.1 通道名称必须两端完全一致
通道名称三端完全一致,都是 json_component:
// Android
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "json_component")
// iOS
let channel = FlutterMethodChannel(name: "json_component", binaryMessenger: registrar.messenger())
// OHOS
this.channel = new MethodChannel(binding.getBinaryMessenger(), "json_component");
这是插件唯一的一条通道,OHOS 侧注册时必须与两端拼写一致。
4.4.2 命令处理与返回值模型
命令处理是"一问一答"模型:Dart 侧只调用 getPlatformVersion,原生侧只实现 getPlatformVersion:
// Dart(模板测试中的调用)
final String? version = await _channel.invokeMethod('getPlatformVersion');
// OHOS
onMethodCall(call: MethodCall, result: MethodResult): void {
try {
if (call.method == "getPlatformVersion") {
result.success("OpenHarmony " + deviceInfo.osFullName)
} else {
result.notImplemented()
}
} catch (e) {
result.error("JsonComponentError", "Failed to handle method call: " + JSON.stringify(e), null)
}
}
返回值约定:原生侧返回 "平台名 + 系统版本号" 字符串,Dart 侧映射为 String?。三端的返回语义完全一致:
| 平台 | 返回值示例 |
|---|---|
| Android | "Android " + Build.VERSION.RELEASE(如 Android 13.0) |
| iOS | "iOS " + UIDevice.current.systemVersion(如 iOS 17.0) |
| OHOS | "OpenHarmony " + deviceInfo.osFullName(如 OpenHarmony OpenHarmony-6.1.1.120) |
未知命令一律 result.notImplemented(),与上游 Android/iOS 行为一致。
4.4.3 解绑与清理
插件没有需要取消的订阅。Engine 解绑时清理通道处理器:
onDetachedFromEngine(binding: FlutterPluginBinding): void {
if (this.channel != null) {
this.channel.setMethodCallHandler(null)
}
}
通道上没有需要持久保存的原生状态,getPlatformVersion 是无副作用的同步查询,调用结束即完成全部工作。组件侧的状态 ValueNotifier 由 ComponentBuilderWidget 在 dispose 时自行释放。
五、补全 OHOS 原生实现与工程配置
5.1 在 JsonComponentPlugin.ets 中实现原生能力
业务使用 JsonComponent.build() 渲染组件后,原生侧只需返回系统版本字符串;JSON 解析、组件构造、状态管理全部在 Dart 侧完成,原生不参与。
JsonComponentPlugin 只实现 FlutterPlugin 和 MethodCallHandler:前者接入 Engine 生命周期,后者组成命令处理链路。不需要 AbilityAware(没有权限弹窗),没有平台视图,也没有事件订阅。
原生插件位于(本库只有一个原生文件):
ohos/src/main/ets/components/plugin/JsonComponentPlugin.ets
5.1.1 引入 Flutter 和 HarmonyOS 能力
import deviceInfo from '@ohos.deviceInfo';
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
其中:
FlutterPlugin负责接入 Flutter Engine 生命周期;MethodChannel、MethodCall、MethodCallHandler、MethodResult组成命令处理的完整链路;deviceInfo是本插件唯一用到的系统能力,提供osFullName系统版本字段。
注意导入方式必须是默认导入 import deviceInfo from '@ohos.deviceInfo',不能写成命名导入 { deviceInfo },因为 @ohos.deviceInfo.d.ts 使用默认导出。写错会在编译期报 Module has no exported member 'deviceInfo'。
5.1.2 连接 Flutter Engine
getUniqueClassName(): string {
return "JsonComponentPlugin"
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), "json_component");
this.channel.setMethodCallHandler(this)
}
Engine 启动时创建通道并注册处理器,通道名与 Android/iOS 一致。getUniqueClassName 返回类名,供引擎侧的插件管理使用,必须与 pubspec.yaml 中的 pluginClass 一致。
5.1.3 读取系统版本:先查 .d.ts 再写 API
getPlatformVersion 的三端语义都是"系统发布版本号":Android 用 Build.VERSION.RELEASE,iOS 用 UIDevice.current.systemVersion。OHOS 的等价物是 @ohos.deviceInfo 的 osFullName 字段。
这里有一个真实的教训:初稿若凭记忆写成 deviceInfo.version 或 deviceInfo.displayVersion,编译期会报属性不存在。OpenHarmony 的版本信息字段名需要先从本机 SDK 的类型声明核对:
// ohos/sdk/default/openharmony/ets/api/@ohos.deviceInfo.d.ts(节选)
/**
* Describes the full name of the operating system (OS) version.
*
* @syscap SystemCapability.Startup.SystemInfo
* @since 7
*/
const osFullName: string;
.d.ts 同时确认了两件事:osFullName 是 string 类型、自 API 7 起可用(本工程最低兼容 API 18,满足要求)。改用 osFullName 后一次编译通过。这也是适配的一般原则:先查 OpenHarmony SDK 的类型声明,再写系统 API,不要凭印象拼字段名。
5.1.4 命令处理与异常回传
命令处理把整个方法体包进 try/catch,异常时通过 result.error 回传,避免 Dart 侧的 Future 悬挂:
onMethodCall(call: MethodCall, result: MethodResult): void {
try {
if (call.method == "getPlatformVersion") {
result.success("OpenHarmony " + deviceInfo.osFullName)
} else {
result.notImplemented()
}
} catch (e) {
result.error("JsonComponentError", "Failed to handle method call: " + JSON.stringify(e), null)
}
}
- 已知命令:拼接
"OpenHarmony " + osFullName返回; - 未知命令:
result.notImplemented(),Dart 侧invokeMethod抛出MissingPluginException,与两端一致; - 原生异常:以
JsonComponentError为 code 回传错误详情,Dart 侧invokeMethod抛PlatformException,调用方可以捕获处理。
上游模板的默认实现没有 try/catch 保护,这里的异常回传是适配时补充的增强项。
5.1.5 三端实现对照
三端实现逐行对照,契约完全一致:
| 环节 | Android(Kotlin) | iOS(Swift) | OHOS(ArkTS) |
|---|---|---|---|
| 通道名 | "json_component" | "json_component" | "json_component" |
| 命令 | getPlatformVersion | getPlatformVersion | getPlatformVersion |
| 版本来源 | Build.VERSION.RELEASE | UIDevice.current.systemVersion | deviceInfo.osFullName |
| 返回值 | "Android 13.0" | "iOS 17.0" | "OpenHarmony OpenHarmony-6.1.1.120" |
| 未知命令 | result.notImplemented() | result(FlutterMethodNotImplemented) | result.notImplemented() |
| 生命周期 | onAttachedToEngine / onDetachedFromEngine | attachToRegistrar / detachFromEngine | onAttachedToEngine / onDetachedFromEngine |
业务侧不需要感知平台差异:示例页直接展示 getPlatformVersion 的返回值,同一个页面在 Android/iOS/OHOS 上会分别显示对应前缀的版本号。组件渲染则完全在 Dart 侧完成,三端行为一致。
5.1.6 Engine 解绑时释放资源
onDetachedFromEngine(binding: FlutterPluginBinding): void {
if (this.channel != null) {
this.channel.setMethodCallHandler(null)
}
}
Flutter Engine 销毁时清理通道 Handler。插件不持有通道之外的原生资源,解绑即完成全部清理。组件侧的 ValueNotifier 由 ComponentBuilderWidget.dispose() 释放。
5.2 声明插件和宿主权限
本插件的原生侧只读取系统版本信息,不申请任何敏感权限,是权限配置最简单的一类插件。
5.2.1 插件 HAR 的权限
插件的 ohos/src/main/module.json5 只声明 HAR 模块信息,不带 requestPermissions:
{
"module": {
"name": "json_component",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
权限统一由宿主应用声明,HAR 保持无权限依赖。
5.2.2 应用 entry 的权限
最终安装的是宿主应用。本例的 example/ohos/entry/src/main/module.json5 只保留了模板默认的 INTERNET:
"requestPermissions": [
{"name" : "ohos.permission.INTERNET"}
]
INTERNET 是 Flutter Debug 模式接入开发工具的常规配置;@ohos.deviceInfo 读取 osFullName 属于公开系统信息,不需要 user_grant 权限,因此无需 reason / usedScene 等声明。README.OpenHarmony_CN.md 中也明确写了"不申请任何敏感权限,无需额外权限配置"。
5.3 注册并导出插件
pubspec.yaml 通过以下配置声明 OHOS 插件类:
flutter:
plugin:
platforms:
ohos:
pluginClass: JsonComponentPlugin
插件的 ohos/index.ets 需要导出实现:
import JsonComponentPlugin from './src/main/ets/components/plugin/JsonComponentPlugin';
export default JsonComponentPlugin;
执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码,example/ohos/entry/.../GeneratedPluginRegistrant.ets 中会出现:
import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import IntegrationTestPlugin from 'integration_test';
import JsonComponentPlugin from 'json_component';
export class GeneratedPluginRegistrant {
static registerWith(flutterEngine: FlutterEngine) {
try {
flutterEngine.getPlugins()?.add(new IntegrationTestPlugin());
flutterEngine.getPlugins()?.add(new JsonComponentPlugin());
} catch (e) {
Log.e(TAG, "Tried to register plugins with FlutterEngine failed.");
}
}
}
通常不应手工编辑该文件,因为下次构建可能覆盖它。本例注册了 IntegrationTestPlugin 与 JsonComponentPlugin;缺少 JsonComponentPlugin 会导致 getPlatformVersion 抛出 MissingPluginException。
注册异常的排查步骤见第九节 MissingPluginException。
5.4 检查 example 的 OHOS 应用结构
本例的 example/ohos/build-profile.json5 应在 products 中设置版本。下面是需核对的配置片段,请合并到现有工程;其中 signingConfig: "default" 需要与本机配置的签名名称一致,签名材料保留在本地:
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.1.0(18)",
"runtimeOS": "HarmonyOS"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
}
]
}
配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。
宿主 app.json5 的 bundleName 为 com.example.ohos_example_scaffold(模板默认),调试签名 profile 的 bundleName 必须与此一致,否则签名包无法安装。EntryAbility 保持模板原样:本例的演示闭环全部由 Dart 完成,宿主没有注册任何额外通道。
六、补全交付文件并提交适配分支
6.1 除代码外还要补全哪些文件
代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:
| 文件 | 应写清楚的内容 |
|---|---|
README.OpenSource | 上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖 |
README.md | 原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息 |
README.OpenHarmony_CN.md | 简介、安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题 |
README.OpenHarmony.md | 与中文说明对应的英文文档 |
CHANGELOG.OpenHarmony.md | OHOS 新增能力、适配版本、兼容限制与测试范围 |
LICENSE / NOTICE | 保留上游许可证;NOTICE 按许可证和原项目要求保留或补充 |
example/README.md | 依赖方式、运行目录、签名、操作步骤与效果图 |
pubspec.yaml、ohos/oh-package.json5 | 核对包名、版本、插件注册、仓库地址、许可证和依赖 |
.gitignore | 忽略构建缓存及本机签名材料,不漏提交必要源码和配置 |
本仓库的适配提交 22f1fe7 中已经包含 README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md(记录 0.0.3-ohos-0.0.1-beta.1)、docs/OHOS_Adaptation_Blog.md 适配记录,以及 docs/screenshots/ 下的真机截图,但发布前仍需修正几处:
ohos/oh-package.json5的license字段仍是脚手架默认值Apache-2.0,与上游LICENSE(BSD-3-Clause,Copyright © 2025 Heri Setiawan)不一致,应改为一致的声明;- 已提交的
example/ohos/build-profile.json5中包含本机调试签名的绝对路径与密钥口令(storeFile指向~/.ohos/config/...),正式对外发布前应还原为占位配置,避免泄露本机材料; pubspec.yaml的 web 平台声明包含fileName: json_component_web.dart,但lib/json_component_web.dart当前不存在,web 构建会失败,需补写 web 实现或移除该条目;- 上游遗留的
test/json_component_method_channel_test.dart与example/integration_test/plugin_integration_test.dart引用了已删除的json_component_method_channel.dart/MethodChannelJsonComponent/JsonComponent()/getPlatformVersion,导致flutter analyze报错、flutter test无法全部通过,发布前需修复或移除这些测试; - 模板清理后应检查
ohos/与example/ohos/中是否残留 probe 命名的包名或 bundleName,确保与项目真实名称一致。
6.2 提交前检查
提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:
git branch --show-current
git diff --check
git status --short
git diff --stat
git diff
检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。
6.3 提交并推送适配分支
文档和代码整理完成后,在根目录暂存并提交。文件名按项目实际情况调整:
git add ohos example/ohos pubspec.yaml .metadata
git add README.OpenHarmony.md README.OpenHarmony_CN.md CHANGELOG.OpenHarmony.md
git add docs
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: add OpenHarmony platform support"
git tag 0.0.3-ohos-0.0.1-beta.1
git remote -v
git branch --show-current
git push -u origin main
git push origin 0.0.3-ohos-0.0.1-beta.1
本例的适配提交为 22f1fe7,提交信息为 feat: add OpenHarmony platform support,内容涵盖 ohos 平台实现、宿主工程、README/CHANGELOG 与真机测试证据;Dart 层零改动。DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置(storeFile 等指向本机绝对路径),提交前需要从暂存内容中移除或还原为占位(见 6.1 第 2 条)。推送时,origin 应指向自己有写权限的仓库;上游仓库在 GitHub,无权限直接推送时先推送到自己的镜像,再推送 TAG。
推送后在托管平台发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行说明或运行图。目标分支和评审流程以接收仓库要求为准。
七、使用根目录 example 演示接入
仓库自带 example/,可以直接用来调试插件和体验 JSON 动态组件渲染。
7.1 本地适配时使用路径依赖
当前 example/pubspec.yaml 的依赖是:
dependencies:
flutter:
sdk: flutter
json_component:
path: ../
../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。environment.sdk 为 ">=3.4.4 <4.0.0",满足 example 中 super.key 等语法。
7.2 通过 Git 引入插件
业务应用通过 Git 引入时,将 json_component 的 path 配置替换为下面的 Git 依赖。这里固定到本文使用的 TAG:
dependencies:
flutter:
sdk: flutter
json_component:
git:
url: https://github.com/herisetiawan00/json-component.git
ref: 0.0.3-ohos-0.0.1-beta.1
url 使用实际发布仓库——本仓库 README 直接使用了上游 GitHub 地址。若后续迁移到 AtomGit 发布,请将 url 改为对应的 AtomGit 仓库地址,ref 保持为已推送的 TAG。注意上游 GitHub 仓库的 main 分支虽然包含本次适配提交,但直接引用未打 TAG 的版本不利于版本追溯,建议固定到 TAG。
从插件根目录执行:
cd example
flutter pub get
flutter pub deps
检查 example/pubspec.lock 中 json_component 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。
7.3 调用接口实现 JSON 动态组件渲染
仓库中的 example/lib/main.dart 已经是一个完整的演示页,包含文本、用户卡片、计数器、信息卡片和列表五种组件,并演示了状态驱动更新与模板变量。最小接入代码如下:
import 'package:flutter/material.dart';
import 'package:json_component/json_component.dart';
class TextComponent extends BaseJsonComponent {
final String text;
TextComponent.fromJson(super.json)
: text = json['text'] ?? '', super.fromJson();
Widget build(BuildContext context, ComponentContext cContext) {
return Text(text.withContext(cContext));
}
}
void main() {
JsonComponent.initialize();
JsonComponent.register('text', TextComponent.fromJson);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: JsonComponent.build(context, const {
'_id': 'text',
'text': 'Hello from json_component',
}),
),
),
);
}
}
完整 Demo 在此基础上增加了 user_card(按 _key 更新 name/role)、counter(+1 / 重置)、info_card(模板变量展示 $state.name / $state.role)和 list(核心能力清单),覆盖多组件注册、状态驱动更新与模板变量三大能力。
7.4 页面退出时的资源处理
JsonComponent 本身不持有需要释放的原生资源;页面退出时的资源处理主要在 ComponentBuilderWidget 与业务 Widget 中完成:
ComponentBuilderWidget.dispose()会释放_stateNotifier,并调用widget.onDispose移除_registeredState中对应的回调,再调用widget.component.dispose();- 业务页面如果持有
TextEditingController、ScrollController等,应在dispose中统一释放; - 异步回调中的
setState前检查mounted,避免页面已销毁后更新状态; - 原生侧没有订阅和缓存资源,
onDetachedFromEngine只清理通道 Handler。
因此业务侧只需按常规 Flutter 页面管理资源即可,无需为 OHOS 做额外清理。
八、验证、构建与鸿蒙设备运行效果
8.1 分别验证插件与 example
从插件仓库根目录执行:
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
本仓库当前的 flutter analyze 报告 12 条问题,但全部来自上游既有代码或模板残留,与 OHOS 原生适配无关:
example/integration_test/plugin_integration_test.dart:19:34:JsonComponent没有无参构造函数;example/integration_test/plugin_integration_test.dart:20:42:JsonComponent没有getPlatformVersion方法;test/json_component_method_channel_test.dart:package:json_component/json_component_method_channel.dartURI 不存在,MethodChannelJsonComponent未定义(上游在e5ca954删除了lib/json_component_method_channel.dart,但9a4ff15又保留了/新增了引用它的测试文件);- 其余 7 条 info 为
json_component_test.dart与example/lib/main.dart中json_component公开 API 已 re-export 导致的重复导入。
由于适配原则是 Dart 层零改动,这些告警原样保留;接入方如需清理,建议先与上游沟通,或在发布前修复测试与模板残留(见 6.1 第 4 条)。
flutter test 在本仓库的结果是:7 条用例通过,1 条用例失败(test/json_component_method_channel_test.dart 加载失败,原因同上)。example/test/widget_test.dart 已改写为示例页渲染断言(标题、各组件卡片均存在)。
Dart 测试只能覆盖通道封装,getPlatformVersion 的真实返回和真机渲染还需要在鸿蒙设备上验证。
8.2 确认设备连接
hdc list targets
flutter devices
设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。
8.3 配置签名
真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置自动签名:
- 用 DevEco Studio 打开
example/ohos,不是仓库根目录; - 等待工程 Sync 成功,确认 Project 视图中存在
entry模块; - 打开 File > Project Structure > Signing Configs;
- 为
defaultproduct 选择或生成签名; - 确认设备、应用包名、证书和 Profile 匹配;
- 再回到终端执行 Flutter 构建或运行。
签名材料保存在本机,公开仓库中只保留构建所需的通用配置。
8.4 运行示例
以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:
flutter run -d <device-id>
也可以先构建 HAP:
flutter build hap --debug
典型产物位于:
example/build/ohos/hap/
本例真机构建记录:Hvigor assembleHap 任务完成,产物为 entry-default-signed.hap。真机安装应选择与当前设备匹配的已签名产物。构建失败时按第九节的检查项排查签名与 SDK 配置后重试。
8.5 在设备上测试动态组件流程
- 安装并启动应用,确认首页显示
json_component · OpenHarmony 示例标题; - 查看"当前用户"卡片,初始显示
David / Flutter Developer; - 点击 切换用户 / 角色,确认卡片实时切换为
Alice / UI Designer、Bob / QA Engineer并循环——这验证了JsonComponent.setState()按_key驱动状态更新; - 点击计数器卡片的 +1 与 重置,确认计数实时变化;
- 查看"适配信息"卡片,确认"登录用户"与"用户角色"两行的
$user.name/$user.role被替换为当前状态值——这验证了模板变量withContext(); - 查看"核心能力"列表,确认多组件注册、状态驱动更新、模板变量三条均正常渲染;
- 与 Android/iOS 设备上相同 JSON、相同组件的输出对比,确认渲染结果一致(纯 Dart 实现保证跨平台一致)。
本例验证设备为 OpenHarmony-6.1.1.120(API 24),安装与启动日志:
[Info]App install path:...entry-default-signed.hap msg:install bundle successfully.
start ability successfully.
8.6 鸿蒙设备运行效果
完成适配后,Flutter 应用可以在 OHOS 页面中完成 JSON 动态组件渲染 与 状态驱动更新:前者完全在 Dart 侧完成,后者通过 JsonComponent.setState() 触发。
真机运行截图(OpenHarmony-6.1.1.120):
| 操作 | 预期表现 |
|---|---|
| 点击「+1」按钮 | 计数器数值 + 1,界面实时刷新展示最新计数 |
| 点击「重置」按钮 | 计数器清零,界面刷新显示计数为 0 |
| 点击「切换用户 / 角色」按钮 | 切换全局 $state 状态,用户卡片更新头像首字母、用户名与角色,计数器数值保持不变 |
| 修改全局 state.name/state.role | UI 自动响应状态变更,用户信息卡片重新渲染 |
| json_component 框架渲染 | 根据 JSON 描述注册并构建组件,支持多组件注册、状态驱动更新、模板变量解析 |
| 页面初始化加载 | 渲染全部卡片组件,用户默认 David,计数初始值为 0,展示平台、框架版本与核心能力清单 |
以下是操作的视屏,可以参考一下:
| 操作 | 预期表现 |
|---|---|
| 启动应用 | 标题与多个组件卡片自上而下正常渲染 |
| 切换用户 | 用户卡片实时更新 name / role |
| 点击 +1 / 重置 | 计数器卡片实时变化 |
| 查看适配信息卡片 | $user.name / $user.role 已替换为当前状态值 |
| 核心能力列表 | 多组件注册、状态驱动更新、模板变量三条正常展示 |
运行日志(节选,归档于 docs/screenshots/hilog_runtime_demo.log)显示 Flutter 引擎正常启动、oh_flutter_1Surface 正常渲染,应用进程稳定运行无崩溃。该插件不依赖特殊系统能力,API 18 及以上设备均可运行。
九、FAQ:适配过程与使用问题
9.1 Missing SDK components
典型错误如下:
Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.
这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。
处理顺序:
- 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
- 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
- 避免误用
/Applications/DevEco-Studio.app/Contents/sdk之类的不完整目录; - 确认 SDK 根目录下存在
toolchains、ets、js、native、previewer; - 执行
flutter config --ohos-sdk <正确路径>; - 重新执行
flutter doctor -v和 DevEco Studio Sync。
因为 Sync 和 Compile 首先读取 Mac 本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。
当前工程的 compatibleSdkVersion 是 API 18,因此 API 24 在安装版本门槛上是满足的;但设备还必须满足签名要求;本插件不依赖特殊系统能力或权限。
9.2 DevEco Studio 中看不到 entry 模块
插件的 ohos/ 目录是 HAR 模块,可安装应用的 entry 模块位于 example/ohos/entry。
请直接使用 DevEco Studio 打开:
json-component/example/ohos
如果仍看不到 entry,先解决 SDK Sync 错误,再检查 example/ohos/build-profile.json5 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。
9.3 无法手动签名
签名配置依附于可构建的应用模块和 product。只有 HAR 插件模块、工程 Sync 失败,或者打开了错误目录时,DevEco Studio 都可能无法提供 entry 签名入口。
建议先确认:
- 打开的是
example/ohos; - SDK 组件完整并且 Sync 成功;
entry的模块类型为entry;defaultproduct 和 target 已正确关联;- 当前账号、证书和调试设备状态有效。
9.4 能安装但组件不渲染或空白
组件渲染空白或抛异常时按以下顺序检查:
- 是否在
main()中先调用JsonComponent.initialize(),再调用JsonComponent.register(...); - JSON 中是否包含
_id字段,且_id的值已通过register()注册; - 组件构造器是否为
BaseJsonComponent.fromJson子类,并且正确调用了super.fromJson(); - 如果需要状态更新,JSON 中是否包含
_key,且setState(key, ...)的 key 与之完全一致; - 查看日志中的
ComponentInvalidException/ComponentNotFoundException/ComponentParsingException异常栈,定位具体失败位置。
9.5 MissingPluginException
这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从仓库根目录执行:
cd example
flutter clean
flutter pub get
flutter run -d <device-id>
如果仍然出现,检查自动生成的插件注册文件中是否包含 JsonComponentPlugin,同时核对 pubspec.yaml、ohos/index.ets 和 oh-package.json5。
9.6 模板变量 $state.xxx 不替换
现象:组件中写了 $state.name 或 $user.role,但界面上原样显示字符串,没有被替换。
原因:模板变量必须满足三个条件才会替换:
- 组件 JSON 中声明了
_key,用于在JsonComponent.setState()时定位; - 调用
JsonComponent.setState('_key', {'name': 'David'})下发了状态; - 组件的
build方法中把字段传给withContext(cContext),例如text.withContext(cContext)。
如果缺少 _key、没有调用 setState、或者组件内没有使用 withContext,模板变量都会保持原样。完整示例中的 InfoCardComponent 演示了正确用法。
9.7 flutter analyze / flutter test 报错
flutter analyze 或 flutter test 报错时,先确认错误来源:
- 如果错误出现在
test/json_component_method_channel_test.dart或example/integration_test/plugin_integration_test.dart,引用的是MethodChannelJsonComponent/JsonComponent()/getPlatformVersion,这是上游模板残留问题(库的真实 API 是initialize/register/build/setState),不是 OHOS 适配引入的; - 如果错误是
unnecessary_import,来自json_component重新 export 了 common 组件,业务侧重复导入导致,不影响运行; - 如果是 ArkTS 编译错误,优先检查
@ohos/deviceInfo的导入方式是否为默认导入、字段名是否为osFullName。
修复建议:发布前删除或重写模板测试,使其与库的真实 API 一致(见 6.1 第 4 条)。
9.8 flutter create 不认识 ohos,或包名不合法
先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name json_component(本仓库包名与仓库名一致)。生成后注意清理模板多出来的 federated 骨架与 web 文件(见 3.4 与 6.1 第 3 条),再核对 ArkTS 实现与两端契约一致。
如果报错与 json_component_web.dart 相关,说明 pubspec.yaml web 平台声明了 fileName 但 lib/ 下没有对应文件,需要补写 web 实现或移除该条目。
9.9 Git 依赖提示找不到 TAG 或仓库
先检查 url 是否指向已包含 OHOS 适配的仓库,并确认 ref 写的是已推送的 TAG(如 0.0.3-ohos-0.0.1-beta.1)。TAG 未推送时 Git 依赖会解析失败;分支未包含 ohos/ 目录时构建也会失败。私有仓库还需在本机配置 Git 认证。
本仓库 README 直接使用 GitHub 地址,因此 url 应写 https://github.com/herisetiawan00/json-component.git。若后续改用 AtomGit 发布,请同步替换为对应仓库地址。
9.10 改了本地 ArkTS,Demo 为什么没变化
现象:修改了 JsonComponentPlugin.ets 或 example 的 ArkTS 代码,重新构建后 Demo 行为没有变化。
常见原因与排查:
- 未执行
flutter pub get重新生成插件注册代码,导致GeneratedPluginRegistrant.ets还是旧版本; example/ohos/build-profile.json5中的签名 bundleName 与AppScope/app.json5的bundleName(本例为com.example.ohos_example_scaffold)不一致,导致安装的是旧签名包;- 设备上已安装同名 bundle 的旧版本,需先卸载再安装;
- 模板清理不彻底,
ohos/或example/ohos/中残留 probe 命名的包名或 bundleName,与实际项目不一致。
建议每次修改原生代码后执行 flutter clean + flutter pub get + flutter build hap --debug,并核对日志中安装的 bundleName。
9.11 flutter create 在本机崩溃(xcodebuild 退出码 64)
现象:在插件根目录执行 flutter create --template=plugin --platforms=ohos . 直接崩溃,日志中出现 xcodebuild -list -skipPackageUpdates ... 且退出码 64。
原因:本机 Xcode 13.2.1 过旧,不认识 -skipPackageUpdates 参数;而工程中存在 example/ios 时,Flutter 工具生成前会探测既有 iOS 工程的组织名,触发该调用。该问题记录于本仓库 docs/OHOS_Adaptation_Blog.md 的踩坑复盘。
处理方式任选其一:
- 在干净目录生成 ohos 模板后再整体拷贝
ohos/与example/ohos/到仓库(本仓库实际采用的规避方式); - 临时把
ios/与example/ios/移出仓库,生成 ohos 平台后再恢复; - 升级 Xcode 到支持该参数的版本。
相关链接
更多推荐





所有评论(0)