开发工具: 华为云码道

DevEco Studio: 26.0.0(26.0.0.851)

原始仓库: josercc/flutter_channel_json(GitHub)

本文配套仓库: oh-flutter/flutter_channel_json(AtomGit)

flutter_channel_json 用于规范 Flutter MethodChannel 两端的数据传输格式:它定义了一套 code / message / data / success 四字段契约,并给出 Dart、Android(Kotlin)、iOS(Swift)与 OpenHarmony(ArkTS)四端的一致实现,通常与 flutter_channel_event_bus 搭配使用。本文以 flutter_channel_json 1.0.1 为例,介绍源码准备、OHOS 平台实现、Example 宿主工程配置和真机运行验证。

在这里插入图片描述

这是一个插件库:Dart 层保持零改动,适配工作集中在 ArkTS 侧的数据模型与唯一的方法通道上。文中的代码以提交 029804c4f9205ecb21cbcc7bfc6a09fdc3437835 为参考,对应 TAG 1.0.1-ohos-1.0.0-beta.1;文中所有截图均为真机实拍。

在这里插入图片描述

一、插件简介与适配目标

MethodChannel 的消息体在 Flutter 侧是 Map、在原生侧是各自的字典类型,两端对「这条消息长什么样」没有天然约束。flutter_channel_json 把消息格式钉死为四字段:

code    : int      状态码,成功固定 200,失败默认 500
message : String   消息
data    : T?       数据
success : bool     是否成功

并为每一端提供同构的实现:Dart 的 FlutterChannelJson<T>、Kotlin 与 Swift 的同名模型、以及本次适配补齐的 ArkTS 模型。原生侧把结果按契约封好回传,Dart 侧统一用 fromJsonString 解析,两端不再各写一套解析逻辑。

输入与解析 输入与解析 输入与解析 输入与解析 输入与解析 输入与解析

需要特别说明的是上游的一个设计事实:Dart 层没有实现任何通道调用。它只提供数据契约与编解码,原生侧(Android / iOS)只保留了插件模板方法 getPlatformVersion。因此本次适配的核心不是「实现一堆方法」,而是:

  1. 把 ArkTS 侧的数据模型与 Dart 契约逐字段对齐;
  2. 让唯一的通道方法在鸿蒙上返回与 Android / iOS 语义一致的字符串;
  3. 生成可编译、可运行、可验证的 Example/ohos 宿主工程,在真机上跑通完整链路。

二、环境准备

环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。

完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:

flutter --version
flutter doctor -v
hdc list targets

在这里插入图片描述
在这里插入图片描述

编辑用户

工程使用的工具链和 SDK 配置如下:

项目版本或配置用途
Flutter OHOS SDK3.47.7-ohos-1.0.0Flutter 编译与 OHOS 平台工具链
Flutter 分支oh-3.47-dev对应开发分支
Dart SDK3.13.3Dart 语言与包管理环境
DevEco Studio26.0.0(26.0.0.851)OHOS 应用工程开发套件
compatibleSdkVersion6.1.1(24)当前工程声明的最低兼容版本
targetSdkVersion6.1.1(24)应用面向的行为版本
测试设备 ROMOpenHarmony-6.1.1.120(API 24)真机系统版本(TLR-AL00,nova 14)
库版本1.0.1pubspec.yaml 中的包版本
原生语言ArkTSohos/ 模块实现语言
插件产物HAR(ohos/)经 GeneratedPluginRegistrant.ets 自动注册

2.1 开发套件版本与工程中的 SDK 版本配置

6.1.1(24) 和工程配置涉及 OpenHarmony 版本号体系。在本文工程中,相关版本的含义与配置方式如下:

  • 6.1.1(24) 采用「版本(API)」写法,表示 OpenHarmony 6.1.1 版本、对应 API 24;
  • compatibleSdkVersion 与 targetSdkVersion 均为 6.1.1(24):声明最低兼容 API 24,并以 API 24 为行为目标;
  • 本机安装的 DevEco Studio 自带 SDK 为 26.0.0(26),高于工程声明值,编译按本机 SDK 执行、安装兼容性按工程声明判断,两者并不冲突。

在这里插入图片描述

这里有一个本库实际踩过的坑:Example 工程最初沿用模板的 26.0.0,在 API 24 的真机上安装时报 code:9568297 older sdk version in the device——设备的 API 版本低于工程声明值,HAP 直接被拒装。本次适配把两个版本值统一下调到 6.1.1(24) 后安装成功,详见 9.4。


三、从源码仓库开始准备适配工程

3.1 将上游源码同步到 AtomGit

适配已有第三方库时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。

在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。

本例的上游为 josercc/flutter_channel_json,包名 flutter_channel_json,pubspec.yaml 中的版本为 1.0.1(上游另有 v1.0.2 标签,但其指向的提交里 pubspec.yaml 版本仍是 1.0.1,适配 TAG 按包版本命名为 1.0.1-ohos-1.0.0-beta.1)。有一点必须如实说明:上游 LICENSE 文件的内容是占位文本(「暂时还没想好」),尚未声明具体开源协议,本次适配未擅自更改协议,分发时保留上游 LICENSE 原文即可。

flutter_channel_json 现可从 AtomGit 获取。下面使用 AtomGit 地址拉取代码;需要提交修改时,使用自己有写权限的仓库或 Fork。

3.2 将代码拉取到宿主机

在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:

git clone https://atomgit.com/oh-flutter/flutter_channel_json.git
cd flutter_channel_json
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 flutter_channel_json/ 目录;cd 后的位置就是下文所说的仓库根目录,这里应能看到 pubspec.yaml、lib/、ohos/ 和 Example/。Git 仓库名与 Dart 包名相同,都是 flutter_channel_json。

需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:

git switch --detach 029804c4f9205ecb21cbcc7bfc6a09fdc3437835

适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

请添加图片描述

图 1:在宿主机终端输入 AtomGit 仓库拉取命令。

3.3 在仓库根目录确认分支与版本标记

本库没有为 OHOS 适配单独开特性分支,适配提交直接落在 main 上,版本通过 TAG 标记。TAG 命名规则为 原库版本-ohos-版本号-beta.x,本库使用 1.0.1-ohos-1.0.0-beta.1。在 flutter_channel_json/ 根目录执行:

git branch --show-current
git tag --list
git log --oneline -3
git status --short --branch

git branch --show-current 用于确认当前所在分支,git tag --list 用于确认版本标记是否已推送。向接收仓库提交时按其对分支与 TAG 的要求执行。

请添加图片描述

图 2:在 flutter_channel_json 仓库根目录确认分支与 TAG。

3.4 补全 ohos 平台与 Example 宿主工程

与纯 Dart 包不同,本库上游已经按插件形式声明了 android 与 ios 平台,只是缺少 ohos。补全方式是在仓库根目录执行插件模板生成,让它同时产出 ohos/ 模块与 Example/ohos/ 宿主工程:

flutter create . --template=plugin --platforms=ohos
git status --short
git diff -- pubspec.yaml
  • --template=plugin 匹配本库的插件身份,生成 HAR 模块骨架;
  • --platforms=ohos 指定只补全 OHOS 平台,不影响已有的 android/、ios/ 实现;
  • 最后的 . 表示在当前插件根目录内补全,不是另建一层目录。

生成后务必用 diff 检查:模板会连带注入与 OHOS 无关的内容,例如 federated 骨架(lib/*_method_channel.dart、lib/*_platform_interface.dart)、.kts 构建脚本、*.iml 等。本次适配保留了已有 API、其他平台注册项及依赖配置,只保留 ohos/ 与 Example/ohos/ 相关产物。不同 Flutter OH 版本生成的模板可能略有差异。

pubspec.yaml 的净变化只有 2 行——在 flutter: plugin: platforms 下追加 ohos 声明:

flutter:
  plugin:
    platforms:
      android:
        package: com.example.flutter_channel_json
        pluginClass: FlutterChannelJsonPlugin
      ios:
        pluginClass: FlutterChannelJsonPlugin
      ohos:
        pluginClass: FlutterChannelJsonPlugin

pluginClass 必须与 ArkTS 侧 getUniqueClassName() 的返回值一致(都是 FlutterChannelJsonPlugin),否则插件注册表找不到类。

请添加图片描述

图 3:在仓库根目录输入 OHOS 平台补全命令。

3.5 适配后的项目目录

适配后的关键目录如下:

flutter_channel_json/
├── lib/
│   └── flutter_channel_json.dart
├── ohos/
│   ├── index.ets
│   ├── oh-package.json5
│   ├── build-profile.json5
│   ├── README.md
│   └── src/main/
│       ├── module.json5
│       └── ets/components/
│           ├── model/FlutterChannelJson.ets
│           └── plugin/FlutterChannelJsonPlugin.ets
├── Example/
│   ├── lib/
│   │   ├── main.dart
│   │   ├── home_page.dart
│   │   └── demo_log.dart
│   └── ohos/
│       ├── build-profile.json5
│       ├── oh-package.json5
│       └── entry/src/main/
│           ├── ets/entryability/EntryAbility.ets
│           ├── ets/plugins/GeneratedPluginRegistrant.ets
│           ├── module.json5
│           └── resources/
├── docs/ohos/
│   ├── REAL-DEVICE-TEST.md
│   ├── OHOS-ADAPTATION-BLOG.md
│   ├── images/
│   └── logs/
├── test/
├── README.OpenHarmony_CN.md
├── CHANGELOG.OpenHarmony.md
└── pubspec.yaml

项目根目录如下,其中包含 ohos/ 与 Example/ohos/,以及 OpenHarmony 中英文说明、变更记录、真机验证报告和适配实录:

请添加图片描述

图 4:适配后的 flutter_channel_json 项目根目录。

文件主要职责
lib/flutter_channel_json.dart四字段契约的 Dart 实现,业务侧统一入口
ohos/src/main/ets/components/model/FlutterChannelJson.etsArkTS 数据模型,与 Dart 契约逐字段对齐
ohos/src/main/ets/components/plugin/FlutterChannelJsonPlugin.ets插件类:注册、方法分发、错误处理与 hilog 打点
ohos/index.ets导出插件类与数据模型,供宿主工程复用
Example/lib/home_page.dart示例页:预设、构造、往返校验、批量调用、契约表与日志
Example/ohos/build-profile.json5声明 product、SDK 版本与签名配置
docs/ohos/REAL-DEVICE-TEST.md真机验证报告:环境、日志、截图、踩坑
README.OpenHarmony_CN.md / README.OpenHarmony.mdOpenHarmony 中英文说明
CHANGELOG.OpenHarmony.md鸿蒙化变更记录

四、Dart 实现分析

本库的全部 Dart 实现就是 lib/flutter_channel_json.dart 一个文件,上游同时存在 Kotlin 与 Swift 侧的同构实现。适配插件时,应保留其公开接口和其他平台实现,Dart 层本次零改动。

本例的实现全文如下:

import 'dart:convert';

/// 规范 Flutter 通道消息格式
class FlutterChannelJson<T> {
  /// 状态码
  final int code;

  /// 消息
  final String message;

  /// 数据
  final T? data;

  /// 是否成功
  final bool success;

  const FlutterChannelJson({
    required this.code,
    required this.message,
    this.data,
    required this.success,
  });

  factory FlutterChannelJson.fromJson(Map<String, dynamic> json) {
    return FlutterChannelJson<T>(
      code: json['code'] as int,
      message: json['message'] as String,
      data: json['data'] as T?,
      success: json['success'] as bool,
    );
  }

  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'code': code,
      'message': message,
      'data': data,
      'success': success,
    };
  }

  String toJsonString() {
    return json.encode(toJson());
  }

  factory FlutterChannelJson.fromJsonString(String jsonString) {
    return FlutterChannelJson.fromJson(json.decode(jsonString));
  }

  factory FlutterChannelJson.success(T data) {
    return FlutterChannelJson<T>(
      code: 200, message: '', data: data, success: true,
    );
  }

  factory FlutterChannelJson.failure(String message) {
    return FlutterChannelJson<T>(
      code: 500, message: message, data: null, success: false,
    );
  }
}

4.1 跨端架构与调用时序

虽然类名叫 flutter_channel_json,但它的 Dart 层不创建任何 MethodChannel:库本身只负责「把消息编码成约定的 JSON、把收到的 JSON 解码回对象」。通道由业务或配套的 flutter_channel_event_bus 持有,原生侧按同一契约构造消息回传。四端的关系如下:

ArkTS / Kotlin / Swift 原生代码

FlutterChannelJson.success(data)

toJsonString() 四字段 JSON

MethodChannel(业务持有)

Dart fromJsonString

统一处理 code / message / data / success

无论原生侧是哪一端,只要按契约封包,Dart 侧的解析代码就完全不用改——这正是这个库存在的意义。

4.1.1 一次完整回传的时序
Dart 业务 MethodChannel FlutterChannelJson ArkTS 原生 Dart 业务 MethodChannel FlutterChannelJson ArkTS 原生 FlutterChannelJson.success(data) toJson() 四字段字典 toJsonString() 经通道回传 通道消息(JSON 字符串) fromJsonString 解码 校验 code / success 后取用 data

4.2 数据契约模型

FlutterChannelJson<T> 的全部语义可以归纳为一张表:

场景codemessagedatasuccess
success(data)200空串传入值true
failure(message)500传入消息nullfalse
默认构造必填必填可空必填

几个对业务有直接影响的细节:

  • fromJson 用 json['data'] as T? 做类型收窄,data 缺失时得到 null,不会抛错;但 code、message、success 缺失或类型不符会抛 TypeError;
  • fromJsonString 内部先走 json.decode,非法 JSON 字符串抛 FormatException,example 的「非法 JSON」按钮演示的正是这条路径;
  • 泛型 T 只在 Dart 侧收窄,JSON 本身不带类型信息——原生侧封了什么类型,Dart 侧就要用对应的 T 去解。

4.3 公开 API

名称描述类型
FlutterChannelJson(...)默认构造,四字段显式指定构造方法
FlutterChannelJson.fromJson根据字典初始化工厂方法
FlutterChannelJson.fromJsonString根据 JSON 字符串初始化工厂方法
FlutterChannelJson.success成功格式,code 固定 200工厂方法
FlutterChannelJson.failure失败格式,code 固定 500工厂方法
code / message / data / success四字段契约属性
toJson / toJsonString序列化为字典 / JSON 字符串方法

4.4 为什么原生侧只有一个 getPlatformVersion

这是上游的设计,不是适配遗漏。Dart 层没有实现任何通道调用,原生侧(Android / iOS)只保留了插件模板自带的 getPlatformVersion——它存在的意义是提供一条可验证的最小通路:业务接入后调用一次,能拿到版本串就说明通道两端已经接通。本次适配与 Android / iOS 保持一致:不新增、不删除任何方法,Dart 层保持零改动,原生侧同样只实现 getPlatformVersion。


五、ArkTS 平台实现

5.1 插件注册

pubspec.yaml 声明 ohos 平台后,Flutter 工具会在构建时为宿主工程生成注册代码。本库适配后的 Example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets 内容如下:

import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import FlutterChannelJsonPlugin from 'flutter_channel_json';

export class GeneratedPluginRegistrant {
  static registerWith(flutterEngine: FlutterEngine) {
    try {
      flutterEngine.getPlugins()?.add(new FlutterChannelJsonPlugin());
    } catch (e) {
      Log.e(TAG, "Tried to register plugins with FlutterEngine failed.");
      Log.e(TAG, "Received exception while registering", e);
    }
  }
}

GeneratedPluginRegistrant.ets 是生成文件,通常不应手工编辑,下次构建可能覆盖它;注册是否生效可以直接看 import 语句里有没有本库条目。

5.2 数据模型:与 Dart 契约逐字段对齐

ohos/src/main/ets/components/model/FlutterChannelJson.ets 是本次适配的核心产出之一:

export type FlutterChannelJsonMap = Record<string, ESObject>;

export default class FlutterChannelJson<T> {
  code: number;
  message: string;
  data: T | null;
  success: boolean;

  static success<T>(data: T): FlutterChannelJson<T> {
    return new FlutterChannelJson<T>(200, '', data, true);
  }

  static failure<T>(message: string, code: number = 500): FlutterChannelJson<T> {
    return new FlutterChannelJson<T>(code, message, null, false);
  }

  static fromJson<T>(json: FlutterChannelJsonMap): FlutterChannelJson<T> { /* ... */ }
  static fromJsonString<T>(jsonString: string): FlutterChannelJson<T> { /* ... */ }
  toJson(): FlutterChannelJsonMap { /* ... */ }
  toJsonString(): string { return JSON.stringify(this.toJson()); }
}

几个刻意的对齐点:

细节DartArkTS 实现
成功码200,message 为空串相同
失败码500,data 为 null相同(failure 额外支持自定义 code,与 Kotlin 的 failure(message, code) 对齐)
键名code/message/data/success完全相同,JSON.stringify 后可直接被 Dart 解析
data 为空nullnull(fromJson 里把 undefined 归一成 null,避免 Dart 侧收到 undefined)

模型经 ohos/index.ets 对外导出,宿主工程里的其它插件可以直接 FlutterChannelJson.success(data).toJsonString() 回传数据,而不必再手写一遍四字段字典。

5.3 插件类:注册、分发与全量错误处理

FlutterChannelJsonPlugin.ets 的职责是把通道接起来,并保证任何路径都有回音:

const CHANNEL_NAME: string = 'flutter_channel_json';

export default class FlutterChannelJsonPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  getUniqueClassName(): string {
    return "FlutterChannelJsonPlugin";
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    try {
      this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
      this.channel.setMethodCallHandler(this);
      hilog.info(DOMAIN, TAG, 'onAttachedToEngine success, channel: %{public}s', CHANNEL_NAME);
    } catch (e) {
      const error = e as BusinessError;
      hilog.error(DOMAIN, TAG, 'onAttachedToEngine failed, code: %{public}d, message: %{public}s',
        error.code, error.message);
    }
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    try {
      if (call.method == "getPlatformVersion") {
        const version: string = this.getPlatformVersion();
        hilog.info(DOMAIN, TAG, 'onMethodCall %{public}s -> %{public}s', call.method, version);
        result.success(version);
      } else {
        result.notImplemented();
      }
    } catch (e) {
      const error = e as BusinessError;
      hilog.error(DOMAIN, TAG, 'onMethodCall %{public}s failed, code: %{public}d, message: %{public}s',
        call.method, error.code, error.message);
      result.error('FLUTTER_CHANNEL_JSON_ERROR', 'Failed to handle method call: ' + call.method,
        error.message);
    }
  }
}

要点:

  • 通道名 flutter_channel_json 与 Android / iOS 完全一致;
  • 未实现方法走 result.notImplemented(),Dart 侧得到 MissingPluginException,与 Android 的同名行为一致;
  • 注册、分发、异常三个环节都写 hilog(TAG FlutterChannelJsonPlugin),只靠 hdc shell hilog 就能验证通道接线,不必依赖界面;
  • 任何异常都以 FLUTTER_CHANNEL_JSON_ERROR 回传,避免「Dart 侧一直 await 到超时」这种最难查的问题。

5.4 getPlatformVersion:三级取值对齐两端语义

Android 返回 Build.VERSION.RELEASE,iOS 返回 UIDevice.current.systemVersion,两者都是「用户可见的系统版本」。鸿蒙上最先想到的 deviceInfo.osFullName(形如 OpenHarmony-6.1.1.120)在 HarmonyOS 设备上并不是用户看到的版本。最终实现是三级兜底:

private getPlatformVersion(): string {
  const osName: string = deviceInfo.distributionOSName ? deviceInfo.distributionOSName : '';
  const osVersion: string = deviceInfo.distributionOSVersion ? deviceInfo.distributionOSVersion : '';
  if (osName.length > 0 && osVersion.length > 0) {
    return osName + ' ' + osVersion;   // HarmonyOS 6.1.0
  }
  if (deviceInfo.osFullName && deviceInfo.osFullName.length > 0) {
    return deviceInfo.osFullName;      // OpenHarmony-x.x.x.x
  }
  return 'OpenHarmony API ' + deviceInfo.sdkApiVersion;
}

取值优先级的理由:

  1. distributionOSName + distributionOSVersion 才是「发行版名 + 版本」,与 Android / iOS 的「平台名 + 版本」语义最贴近,真机实测输出 HarmonyOS 6.1.0;
  2. OpenHarmony 设备(无发行版信息)退到 osFullName,其本身已含平台名前缀,不再重复拼接;
  3. 最后用 sdkApiVersion 兜底,保证任何设备都有可读返回值,不会出现空串。

以上字段均不需要申请权限(@ohos.deviceInfo 中仅 UDID / ODID / 磁盘序列号等需要)。

5.5 权限

5.5.1 插件侧:HAR 无权限

ohos/src/main/module.json5 是 HAR 模块声明,没有 requestPermissions 配置段。插件读取的系统信息字段属于免权限接口。

5.5.2 应用 entry 的权限

Example/ohos/entry/src/main/module.json5 中只有模板自带的 ohos.permission.INTERNET:

"requestPermissions": [
  {"name" :  "ohos.permission.INTERNET"},
]

这一条是宿主工程模板自带的,与本库的功能无关,保留或删除都不影响运行。除此之外整个工程没有任何权限声明,也不涉及运行时授权。


六、补全交付文件并提交适配分支

6.1 除代码外还要补全哪些文件

代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:

文件应写清楚的内容
README.OpenSource上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖
README.md原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息
README.OpenHarmony_CN.md简介、安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题
README.OpenHarmony.md与中文说明对应的英文文档
CHANGELOG.OpenHarmony.mdOHOS 新增能力、适配版本、兼容限制与测试范围
LICENSE / NOTICE保留上游 LICENSE 原文;上游未声明协议时不要擅自添加
example/README.md依赖方式、运行目录、签名、操作步骤与效果图
pubspec.yaml核对包名、版本、SDK 约束和依赖

本仓库已提供 README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md、ohos/README.md 与 docs/ohos/ 文档集。发布前有三处需要核对统一:

  1. 两份 README 的下载地址目前写的是 atomgit.com/CPF-Flutter/fluttertpc_flutter_channel_json,与实际托管位置 atomgit.com/oh-flutter/flutter_channel_json 不一致,接入时以本文 7.2 的配置为准,或先统一文档地址;
  2. README 的 TAG 表中分支列写的是 br_v1.0.1_ohos,实际分支为 main,发布前统一;
  3. pubspec.yaml 的 SDK 约束保持上游的 >=2.16.1 <3.0.0 未改动,本次在 flutter_ohos 工具链(Dart 3.13.3)下 flutter pub get 与 flutter build hap --debug 均正常通过;如在其他工具链遇到 SDK 上界报错,可参考同类适配的做法放宽上界。

6.2 提交前检查

提交前先完成第八节的 example 和真机测试,再从根目录检查改动:

git branch --show-current
git diff --check
git status --short
git diff --stat
git diff

检查 diff 中的依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。

签名凭据不入库: 本仓库的 Example/ohos/build-profile.json5 中 signingConfigs 保持为空数组,调试签名材料只保留在开发者本机(~/.ohos/config 下的自动签名材料与个人账号绑定,不适合进入公开仓库)。真机安装前需按 8.3 在 DevEco Studio 中生成自己的调试签名。

6.3 提交并推送到 AtomGit

文档和代码整理完成后,在根目录暂存并提交。文件名按项目实际情况调整:

git add lib pubspec.yaml ohos Example docs test
git add README.OpenHarmony_CN.md README.OpenHarmony.md
git add CHANGELOG.OpenHarmony.md
git diff --cached --check
git diff --cached --stat
git commit -m "feat: add OpenHarmony platform support"
git remote -v
git branch --show-current
git push -u origin main
git tag 1.0.1-ohos-1.0.0-beta.1
git push origin 1.0.1-ohos-1.0.0-beta.1

本例的适配已合并为一次提交 029804c,推送在 AtomGit 的 main 分支上,TAG 1.0.1-ohos-1.0.0-beta.1 已随提交推送(见图 2)。

推送后在 AtomGit 发起合并请求,说明上游来源和版本、适配范围(ArkTS 数据模型 + 唯一通道方法,Dart 层零改动)、依赖变化(仅 pubspec 新增 ohos 平台声明)、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。


七、使用根目录 Example 演示接入

仓库自带 Example/,可以直接用来调试和体验四字段契约在鸿蒙上的完整链路。

7.1 本地适配时使用路径依赖

当前 Example/pubspec.yaml 的依赖是:

dependencies:
  flutter:
    sdk: flutter
  flutter_channel_json:
    path: ../

../ 相对于 Example/pubspec.yaml 指向仓库根目录,修改根目录的库代码后可直接联调。

7.2 通过 AtomGit 引入库

业务应用通过 AtomGit 引入时,将 flutter_channel_json 的 path 配置替换为下面的 Git 依赖。这里固定到本文使用的 TAG:

dependencies:
  flutter:
    sdk: flutter
  flutter_channel_json:
    git:
      url: https://atomgit.com/oh-flutter/flutter_channel_json.git
      ref: 1.0.1-ohos-1.0.0-beta.1

使用自己的适配版本时,先推送分支,再将 url 改为对应仓库,ref 改为分支名。正式发布后可固定到 tag 或 commit。

从仓库根目录执行:

cd Example
flutter pub get
flutter pub deps

检查 Example/pubspec.lock 中 flutter_channel_json 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。

7.3 调用接口完成消息编解码

下面的片段展示 Dart 侧的核心用法,可直接用于业务页面:

import 'package:flutter_channel_json/flutter_channel_json.dart';

// 原生侧回传的 JSON 字符串
const raw = '{"code":200,"message":"","data":2,"success":true}';

final result = FlutterChannelJson<int>.fromJsonString(raw);
if (result.success) {
  // 正常路径:code == 200,data 已按 T 收窄
  print('data = ${result.data}');
} else {
  // 业务失败:按 code / message 分支处理,而不是裸抛异常
  print('failed(${result.code}): ${result.message}');
}

// 构造并序列化(Dart -> 原生的方向同理)
print(FlutterChannelJson<String>.success('ok').toJsonString());
// {"code":200,"message":"","data":"ok","success":true}

7.4 原生侧按契约回传

OpenHarmony 侧的插件代码用 ArkTS 模型构造同构消息,经业务持有的通道回传:

import FlutterChannelJson from 'flutter_channel_json';

// 成功格式
const successText: string = FlutterChannelJson.success<number>(2).toJsonString();
// 失败格式(code 可自定义,默认 500)
const failureText: string = FlutterChannelJson.failure<number>('message').toJsonString();

建议业务失败用 failure(message, code) 表达、由 Dart 侧按 code 分支处理,而不是在原生侧抛 result.error——四字段契约的价值就在于把「成功 / 失败」变成可解析的数据,而不是异常流。


八、验证、构建与鸿蒙设备运行效果

8.1 分别验证库与 example

本仓库包含单元测试 test/flutter_channel_json_test.dart,7 个用例分别覆盖默认构造、fromJson、fromJsonString、toJson、toJsonString、success 与 failure。从仓库根目录执行:

flutter pub get
flutter test
flutter analyze
cd Example
flutter pub get
flutter analyze

单元测试验证的是 Dart 层编解码行为,所有平台一致;通道链路与界面渲染还需要在鸿蒙设备上确认。

8.2 确认设备连接

hdc list targets
flutter devices

设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。

8.3 配置签名

真机安装的 HAP 通常需要有效签名。仓库中的 signingConfigs 为空,推荐使用 DevEco Studio 为 entry 模块配置自动签名:

  1. 用 DevEco Studio 打开 Example/ohos,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs;
  4. 勾选 Automatically generate signature;
  5. 确认设备、应用包名、证书和 Profile 匹配(调试 Profile 与 bundleName: com.example.flutter_channel_json_example 强绑定);
  6. 再回到终端执行构建或安装。

签名材料保存在本机,不要提交到仓库。SDK 自带的 OpenHarmony 默认调试证书链不被真机信任,无法通过自签名安装。

8.4 构建与安装

在 Example/ 目录执行 flutter run -d <device-id> 可以直接构建运行。仅构建 HAP 时:

flutter build hap --debug

Windows 环境下该命令可能因 DevEco Studio 26 的 ohpm.bat 批处理递归缺陷中断(见 9.11),可改用工程内等价的构建命令:

cd Example/ohos
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

典型产物位于 Example/build/ohos/hap/entry-default-signed.hap。真机安装选择已签名产物:

hdc install -r entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_channel_json_example

本文验证使用的产物为 flutter build hap --debug 构建的已签名 HAP,工程声明的 compatibleSdkVersion / targetSdkVersion 为 6.1.1(24),与测试设备(API 24)一致。

通道接线还可以从 hilog 独立验证(TAG FlutterChannelJsonPlugin):

FlutterChannelJsonPlugin: onAttachedToEngine success, channel: flutter_channel_json
FlutterChannelJsonPlugin: onMethodCall getPlatformVersion -> HarmonyOS 6.1.0

8.5 在设备上测试契约与通道

example 页面按库的语义分支分区,逐项核对:

  1. 启动即自动探测一次 getPlatformVersion:运行环境卡显示 Dart 平台 OpenHarmony / HarmonyOS、通道名 flutter_channel_json、平台版本 HarmonyOS 6.1.0,徽标变为「通道已连通」;
  2. 点按快捷预设「成功 200」:表单与 JSON 输入框同步填充 code=200 的四字段形态;依次点按「失败 500」「Map 数据」「List 数据」「bool 数据」,对照四种数据形态的序列化结果;
  3. 在构造对象分区分别点按「默认构造」「success(data)」「failure(message)」,确认三种构造路径的输出;
  4. 在反序列化分区执行 fromJsonString、fromJson(Map),点按「往返校验」确认 fromJsonString(toJsonString()) 与原始串一致;点按「非法 JSON」确认抛错路径有清晰报错而不是闪退;
  5. 在序列化分区执行 toJson() / toJsonString();
  6. 在平台方法通道分区点按「获取平台版本」,确认返回 HarmonyOS 6.1.0;点按「连续调用 x5」,确认批量摘要为成功 5 次 / 失败 0 次并给出平均耗时;点按「调用未实现方法」,确认抛出 MissingPluginException(notImplemented 语义);
  7. 对照四端字段契约表,确认 Dart / Kotlin / Swift / ArkTS 四列的字段类型与默认码(200 / 500)一致;
  8. 查看调用日志区:每次调用带时间戳,可用「全部 / 成功 / 异常」筛选。

本文已在真机完成上述全部 8 项并截图(见 8.6)。

8.6 鸿蒙设备运行效果

完成适配后,四字段契约与通道链路在 OpenHarmony 真机上与 Android / iOS 行为一致。

首屏的通道状态与运行环境:

通道已连通与运行环境

通道状态与运行环境
徽标「通道已连通」,平台版本 HarmonyOS 6.1.0 由 getPlatformVersion 返回

快捷预设与构造对象、反序列化 / 序列化分区:

快捷预设与构造对象 反序列化与序列化

快捷预设与构造对象反序列化与序列化
预设填充表单,success 开关与三种构造按钮齐全JSON 输入框与 fromJsonString / toJsonString() 按钮成对

平台方法通道与四端字段契约、未实现方法的异常语义:

连续调用 x5 与四端契约未实现方法
成功 5 次 / 失败 0 次,总耗时 16 ms,平均 3.2 ms/次;契约表四列类型与默认码一致结果预览区显示 MissingPluginException: No implementation found for method notImplementedMethod,与 Android 的 notImplemented 行为一致

结果预览与调用日志:

输入与解析 输入与解析 输入与解析 输入与解析 输入与解析 输入与解析

结果预览与调用日志
预览 getPlatformVersion -> HarmonyOS 6.1.0,日志按时间戳记录并支持筛选

getPlatformVersion 的返回值取决于设备系统(发行版信息),四字段编解码与通道行为则与设备硬件无关,在所有支持 Flutter 的 OpenHarmony 设备上一致。


九、FAQ:适配过程与使用问题

9.1 Missing SDK components

典型错误如下:

Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.

这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。

处理顺序:

  1. 在 DevEco Studio SDK Manager 中确认对应 API 的组件已经下载完整;
  2. 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
  3. 避免误用不完整的 SDK 目录;
  4. 确认 SDK 根目录下存在 toolchains、ets、js、native、previewer;
  5. 执行 flutter config --ohos-sdk <正确路径>;
  6. 重新执行 flutter doctor -v 和 DevEco Studio Sync。
为什么连接 API 24 手机仍然会报这个错误?

因为 Sync 和 Compile 首先读取宿主机本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。

当前工程的 compatibleSdkVersion 是 6.1.1(24)(API 24),因此 API 24 设备在安装版本门槛上是满足的。

9.2 DevEco Studio 中看不到 entry 模块

可安装应用的 entry 模块位于 Example/ohos/entry。

请直接使用 DevEco Studio 打开:

flutter_channel_json/Example/ohos

如果仍看不到 entry,先解决 SDK Sync 错误,再检查 Example/ohos/build-profile.json5 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。

9.3 无法手动签名 / 签名材料从哪来

仓库中的 signingConfigs 刻意保持为空。调试签名材料按 8.3 的步骤在 DevEco Studio 中自动生成,与 bundleName(com.example.flutter_channel_json_example)强绑定。若业务改了自己的包名,需要重新生成对应的调试 Profile,否则安装时报签名与包名不匹配。

9.4 安装报 code:9568297 older sdk version in the device

本库实际踩过的坑:Example 工程最初沿用模板的 compatibleSdkVersion: 26.0.0,而真机是 API 24,安装直接被拒。处理方式是把 Example/ohos/build-profile.json5 中 product 的两个版本值统一下调:

"compatibleSdkVersion": "6.1.1(24)",
"targetSdkVersion": "6.1.1(24)",

判断依据是设备 param get const.ohos.apiversion 的返回值——工程声明的 API 版本不能高于目标设备的 API 版本。安装到更低 API 的设备时需同步下调。

9.5 安装报 no signature file 或 install version downgrade

真机安装的另外两个常见错误码:

报错原因处理
code:9568320 no signature file装了未签名 HAP使用 entry-default-signed.hap 安装
code:9568263 install version downgrade目标包名已被更高 versionCode 的应用占用卸载旧应用、换包名或提升 versionCode

9.6 调用未实现方法抛 MissingPluginException 是不是 bug

不是。ArkTS 侧对未注册的方法统一走 result.notImplemented(),Dart 侧对应的异常就是 MissingPluginException,与 Android / iOS 的行为一致。example 的「调用未实现方法」按钮演示这条路径(见 8.6 截图),业务侧应捕获它并提示「当前平台不支持该能力」,而不是当作崩溃处理。

9.7 修改了四字段契约,原生侧怎么同步

契约的四个键名散落在四端实现里。修改字段(增删改名)时,Dart、Kotlin、Swift、ArkTS 四份模型必须同步改,并同步更新单元测试与 example 的四端契约表。只改 Dart 侧的 fromJson 而不改 ArkTS 的 toJson,是最容易出现的静默失配——Dart 侧会因 TypeError 或 null 直接失败。建议以 example 的「往返校验」作为回归用例。

9.8 flutter create 不认识 ohos

先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。注意 --template=plugin 只应对库根目录执行一次;对 Example/ 或其他目录重复执行会引入多余的模板文件,生成后务必 diff 检查并清理。

9.9 AtomGit 依赖提示找不到分支或无权限

先检查 URL 是否指向已同步的目标仓库(https://atomgit.com/oh-flutter/flutter_channel_json.git),再确认 TAG 1.0.1-ohos-1.0.0-beta.1 已推送。仓库 README 中目前的下载地址与 TAG 表分支名和实际不一致(见 6.1),接入时以本文 7.2 的配置为准,或先统一文档地址。私有仓库还需在本机配置 Git 认证。

9.10 改了本地库代码,Demo 为什么没变化

先检查 Example/pubspec.yaml:Git 依赖读取远程提交,不会自动读取本地库改动。本地联调切回 path: ../。Dart 文件变更用热重载/热重启即可生效;但 ohos/ 内 ArkTS 代码变更需要重新构建 HAP 并重新安装。

9.11 flutter build hap 卡在 ohpm install:BATCH RECURSION exceeds STACK limits

Windows 环境下最坑的一个问题,报错如下:

> hvigor start to execute ohpm install
****** B A T C H   R E C U R S I O N  exceeds STACK limits ******
Recursion Count=242, Stack Usage=90 percent
> hvigor ERROR: 00306053 Specification Limit Violation
Error Message: ohpm install failed.

排查路径:

  1. 手动 ohpm install 完全正常 → 排除网络与依赖问题;
  2. bash 里直接 hvigorw assembleHap 正常 → 说明与 flutter 的调用链有关;
  3. 读 hvigorw.js 发现它无条件把 process.env.ohpmBin 覆盖成 DevEco 自带的 ohpm.bat;ohpm.bat 的首参只要包含 ohpm 就会进逐字符递归;
  4. 对比 PATH:Git Bash 的 usr/bin 里有一个无扩展名的 ohpm shell 脚本,命令解析走了脚本分支,参数被整体当成 %1,递归层数正好等于整条命令行长度。

解决:构建时使用不含 Git usr/bin 的 PATH(保留 Git/cmd 供 flutter 找 git),或改用 8.4 的 hvigorw assembleHap 命令。

9.12 spawn java ENOENT

越过 ohpm 之后,打包阶段(PackageHap)报:

> hvigor ERROR: Failed :entry:default@PackageHap...
> hvigor ERROR: Error Code: 00308018 Unknown Error
spawn java ENOENT

PackageHap 用 java 跑打包工具,环境里没有 java 就报这个「Unknown Error」。补 DevEco 自带 JBR(<DevEco Studio>/jbr)或设置 JAVA_HOME 即可,与工程代码无关。


相关链接

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐