开发工具: 华为云码道

本文配套仓库: 上游 WaterHashira/encrypt_password;OHOS 适配位于本地仓库提交 60c35a8 的 ohos/、example/ohos/、README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md 与 docs/,真机截图与日志证据归档在 docs/test-evidence/。
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/https://atomgit.com/oh-flutter/encrypt_password

hash_password 把"用一个易记口令派生高强度密码"封装成纯 Dart 能力:Hashing_Functionalities 提供 SHA-256 / SHA-384 / SHA-512 哈希、Hex / Base64 / Base58 编码转换和输出长度截断,Password_Hasher 组件则可以直接包裹输入框实时生成结果。插件的平台通道只有一个模板方法 HashPassword.platformVersion,其余逻辑完全不经过原生。本文以 hash_password 0.0.3 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 构建。

插件目前支持 ohos 平台,上游源码位于 GitHub 仓库。文中的代码以上游 main 分支提交 c0a7ab476efa42e99fc935ca12cc3364156c1d0f 为适配基线。

在这里插入图片描述

OHOS 改动记录https://atomgit.com/oh-flutter/https://atomgit.com/oh-flutter/encrypt_password在本地提交 60c35a86d6eddf951f87006dda67f4e136b6d8c0,并按 0.0.3-ohos-1.0.0-beta.1 打 TAG 发布。

在这里插入图片描述


一、插件简介与适配目标

在保证口令安全的同时轻松记忆,是普通用户最难做到的事:每个网站一个独立强密码几乎不可能记住。hash_password 的思路是让用户只记一个口令,由插件实时派生出对应的高强度密码:Hashing_Functionalities 用 SHA-256 / SHA-384 / SHA-512 对输入做哈希,再按 Hex / Base64 / Base58 编码,并可截断到指定位数;Password_Hasher 是一个可直接包裹 TextFormField 的组件,每次输入时实时生成结果。例如输入 my-password、选择 SHA-256 + Base64 + 截断 16 位,立刻得到一段 16 位的高强度口令。

这套核心能力全部由纯 Dart 包(crypto、convert、fast_base58)实现,不触碰任何平台 API,天然跨平台一致。插件真正的原生依赖只有一个模板方法:HashPassword.platformVersion 通过 MethodChannel('hash_password') 查询系统版本。

正因如此,这个插件的 OHOS 适配工作量极小:在 pubspec.yaml 声明 ohos 平台并生成 HAR 模块;在 ArkTS 中实现 getPlatformVersion,用 deviceInfo.displayVersion 返回与 Android/iOS 语义一致的版本字符串;核心哈希逻辑零改动。


获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容

操作预期表现
在浏览器或图库中复制一张图片,点击「从剪贴板读取」
直接点击「选择本地图片」
剪贴板为空或内容不是图片时读取
OpenHarmony 首次读取弹出剪贴板权限授权框,允许后成功读取(本次使用允许 / 始终允许 / 不允许)

以下是操作的视屏,可以参考一下:

Example 启动授权 Example 启动授权 Example 启动授权


二、环境准备

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

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

flutter --version
flutter doctor -v
hdc list targets

在这里插入图片描述

在这里插入图片描述

编辑用户

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

项目版本或配置用途
Flutter OHOS SDK3.44.9+ohos-0.0.1-canary1Flutter 编译与 OHOS 平台工具链
Flutter 分支0.0.3-ohos-1.0.0-beta.1CPF-Flutter 对应开发分支
Dart SDK3.12.2Dart 语言与包管理环境
HarmonyOS 开发套件26.0.0(API 26)开发套件版本及对应的 API 级别
compatibleSdkVersion5.1.0(18)当前工程声明的最低兼容版本
插件版本0.0.3pubspec.yaml 中的包版本
OHOS 发布 TAG0.0.3-ohos-1.0.0-beta.1本次适配的发布标记
原生语言ArkTSHarmonyOS 插件实现
插件产物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。

对应的 product 配置为:

{
  "name": "default",
  "signingConfig": "default",
  "compatibleSdkVersion": "5.1.0(18)",
  "runtimeOS": "HarmonyOS"
}

在这里插入图片描述

这组配置最低兼容 API 18。本插件原生侧只用到了 @ohos.deviceInfo 的 displayVersion 字段,该能力自很低的 API 版本起即可用,也不需要任何运行时权限,因此 API 18 及以上设备均可运行。本例真机验证环境为 OpenHarmony-6.1.1.120(API 24)。

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

3.1 将上游源码同步到 AtomGit

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

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

hash_password 的上游位于 GitHub(仓库名为 encrypt_password),采用 MIT 许可证。注意配套交付仓库的地址:本仓库 README.OpenHarmony_CN.md 中的安装地址当前是占位符(https://atomgit.com/your-namespace/hash_password.git),需要在正式发布前替换为自己有写权限的实际仓库(社区适配仓库常按 fluttertpc_ 前缀命名),并同步更新 README 中的安装说明。

3.2 将代码拉取到宿主机

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

git clone https://github.com/WaterHashira/encrypt_password.git
cd encrypt_password
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

在这里插入图片描述

git clone 会创建 encrypt_password/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml、lib/ 和 example/。注意一个容易混淆的细节:仓库名是 encrypt_password,而 Dart 包名是 hash_password——pubspec.yaml 的 name、lib/ 源文件名、通道名和插件类名都用 hash_password,后续 flutter create 的 --project-name 也必须传包名而不是仓库名。

需要使用与本文相同的代码版本时,切换到以下提交:

git switch --detach c0a7ab476efa42e99fc935ca12cc3364156c1d0f

该提交即本次 OHOS 适配提交,其父提交为上游 master 分支的 c0a7ab4。适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

在这里插入图片描述

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

3.3 在仓库根目录确认分支与发布 TAG

hash_password 的鸿蒙改动直接基于上游默认分支 master 维护,并在发布时通过 TAG 标记 OHOS 版本。在仓库根目录执行:

git branch --show-current
git tag 0.0.3-ohos-1.0.0-beta.1
git tag -l

注意这个仓库的默认分支是 master 而不是 main——README.OpenHarmony_CN.md 的版本对应表中分支名写作 main,与仓库实际情况不一致,属于发布前应修正的文档细节(见 6.1)。如果习惯使用适配分支,也可以先创建 feat/ohos_hash_password_0.0.3,完成后合并回 master 再打 TAG。本例按仓库现有工作方式直接以 master + TAG 发布:适配提交 60c35a8 位于本地 master(领先上游一个提交),TAG 0.0.3-ohos-1.0.0-beta.1 指向该提交。

请添加图片描述

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

3.4 自动补全 OHOS 适配结构

分支确认后,仍在同一个插件根目录执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件:

flutter create --template=plugin --platforms=ohos --project-name hash_password --org com.lakshay .
git status --short
git diff -- pubspec.yaml .metadata
  • --template=plugin 指定插件模板。
  • --platforms=ohos 指定需要补全的平台。
  • --project-name hash_password 使用 Dart 包名,与 pubspec.yaml 中的 name 保持一致,而不是仓库名 encrypt_password。
  • --org com.lakshay 显式指定组织名。本机执行该命令时曾因 Xcode 13.2.1 过旧、工具探测 example/ios 时调用 xcodebuild 失败而崩溃,显式传 --org 可以跳过 iOS 工程探测直接生成 ohos 平台(详见 9.11)。
  • 最后的 . 表示在当前插件目录补全工程,不是另建一层目录。

在上游基线 c0a7ab4 上执行后,git status --short 的输出为:

 M .metadata
 M pubspec.yaml
 M ohos/
 M example/ohos/

pubspec.yaml 有两处变化:新增 ohos: pluginClass: HashPasswordPlugin 两行;environment.sdk 从 ">=2.12.0 <3.0.0" 提升为 ">=2.17.0 <4.0.0"——因为重写的 example 使用了 super.key 等需要 Dart 2.17 的语法(见 9.6)。.metadata 记录了 ohos 平台的创建信息;ohos/ 与 example/ohos/ 是新生成的 HAR 脚手架和宿主工程。

还要注意清理模板多余产物:flutter create 会按当前插件模板重建各平台,可能生成 federated 骨架(如 lib/hash_password_platform_interface.dart)、Gradle KTS、Swift 模板等本仓库原本没有的文件。应按"只保留 ohos 相关产物"的原则清理,恢复 Android/iOS 原状后再提交。本仓库适配提交中 lib/ 与 test/ 相比上游零改动,就是清理后的结果。

如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:

cd example
flutter create --platforms=ohos .
cd ..

请添加图片描述

图 3:在插件根目录输入 OHOS 结构补全命令。

3.5 适配后的项目目录

适配后的关键目录如下:

encrypt_password/
├── lib/
│   ├── hash_password.dart
│   ├── hashing_functionalities.dart
│   └── password_hasher.dart
├── ohos/
│   ├── index.ets
│   ├── oh-package.json5
│   ├── build-profile.json5
│   ├── hvigorfile.ts
│   └── src/main/
│       ├── ets/components/plugin/HashPasswordPlugin.ets
│       └── module.json5
├── example/
│   ├── lib/main.dart
│   ├── test/widget_test.dart
│   └── ohos/entry/
├── android/
├── ios/
├── docs/
│   ├── hash_password_ohos_adaptation_blog.md
│   └── test-evidence/
└── pubspec.yaml

项目根目录如下,其中包含 ohos/、example/ohos/、已补齐的 README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md,以及归档真机截图与日志的 docs/test-evidence/:

请添加图片描述

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

文件主要职责
lib/hash_password.dart对外入口,静态 platformVersion 通道调用
lib/hashing_functionalities.dart纯 Dart:哈希、编码转换、长度截断
lib/password_hasher.dart纯 Dart:Password_Hasher 实时哈希组件
HashPasswordPlugin.ets响应 getPlatformVersion,返回系统版本字符串
插件 module.json5声明 HAR 模块
示例 entry module.json5声明宿主 Ability、设备类型和 INTERNET 权限
example/lib/main.dart完整 Demo:平台版本卡片、哈希参数选择、结果展示、组件式用法
docs/test-evidence/真机运行截图与日志证据

四、Dart 接口与通道分析

OHOS 适配前先理清通道契约:这个插件绝大部分逻辑是纯 Dart,唯一经过平台通道的只有模板方法 getPlatformVersion。先阅读 lib/ 的三个文件和 Android/iOS 的原生实现,再在 ohos/src/main/ets/components/plugin/ 中实现对应的原生类。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。

本例的对应关系如下:

Dart 入口或模型实际依赖OHOS 实现应保持的行为
HashPassword.platformVersion插件通道 hash_passwordHashPasswordPlugin.ets 返回系统版本返回 "OpenHarmony x.y" 字符串
Hashing_Functionalities 三个方法纯 Dart(crypto/convert/fast_base58)无需适配三平台结果完全一致
Password_Hasher 组件纯 Dart无需适配实时哈希行为一致

通道名和方法名属于跨语言协议。任何一端拼写不一致,都会让 platformVersion 直接抛出 MissingPluginException。

4.1 跨端架构与调用时序

Flutter 侧和 HarmonyOS 侧之间是一条极窄的单向拉取链路,没有事件订阅和平台视图:

  1. 原生链路:Flutter 页面读取 HashPassword.platformVersion,经 MethodChannel('hash_password') 以 invokeMethod('getPlatformVersion') 发起一次请求,HashPasswordPlugin.ets 返回系统版本字符串;
  2. 纯 Dart 链路:哈希、编码转换与截断全部在 Dart isolate 内完成(crypto 的 SHA 实现、convert 的 Base64、fast_base58 的 Base58),完全不经过原生,也就不存在跨端差异。
渲染错误: Mermaid 渲染失败: Lexical error on line 6. Unrecognized text. ... NP -.OpenHarmony x.y.-> MC MC -.St -----------------------^

插件通道一次调用、一次应答,没有需要取消的订阅。

4.1.1 一次完整平台版本查询的时序
deviceInfo HashPasswordPlugin.ets HashPassword Flutter App deviceInfo HashPasswordPlugin.ets HashPassword Flutter App HashPassword.platformVersion invokeMethod('getPlatformVersion') try { 命令分发 } deviceInfo.displayVersion "6.1.1.120" result.success("OpenHarmony 6.1.1.120") Future<String?>

4.2 对外 API 入口:lib/hash_password.dart

HashPassword 是无状态的静态入口,全部逻辑只有一次通道调用:

class HashPassword {
  static const MethodChannel _channel = MethodChannel('hash_password');

  static Future<String?> get platformVersion async {
    final String? version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}
成员签名行为
platformVersionstatic Future<String?> get platformVersion一次 invokeMethod,返回 "平台名 + 版本号" 或 null

这是 Flutter 插件模板生成的最小封装,上游未做扩展;HashPassword 没有任何实例状态,重复调用互不干扰。它通常只用于验证插件通道是否接通,真正的业务能力在另外两个纯 Dart 文件里。

4.3 公开 API 与平台接口

公开 API 分成两部分:一个通道入口加一组纯 Dart 能力,没有 platform_interface 抽象层,也没有 Stream:

lib/
├── hash_password.dart              # 通道入口(模板结构)
├── hashing_functionalities.dart    # 纯 Dart 哈希/编码/截断
└── password_hasher.dart            # 纯 Dart 组件
公开 API签名说明
HashPassword.platformVersionFuture<String?>通道调用,验证原生接入
input_hash(userText, userSha)String按 '256'/'384'/'512' 选择 SHA 算法
number_system_convert(userNumSys, convHashText)StringHex 原样返回;Base64 / Base58 重编码
final_encrypted_password(convertedText, outputDigits)String按位数截断,0 表示不截断
Password_HasherStatefulWidget包裹 TextFormField,每次输入实时哈希并写回控制器
  • 哈希链路是同步纯函数:utf8.encode → sha256/384/512.convert → 十六进制字符串;
  • 编码链路:hex.decode 后按选择转 Base64(convert 包)或 Base58(fast_base58 包);
  • Password_Hasher 通过 trigger 参数区分"仅展示输入框"与"实时哈希写回"两种模式。

pubspec.yaml 中的多端 pluginClass: HashPasswordPlugin 用于原生插件注册。三个依赖 crypto、convert、fast_base58 都是纯 Dart 包,OHOS 上无需任何原生对应物。

4.4 Dart 通道协议分析

4.4.1 通道名称必须两端完全一致

通道名称三端完全一致,都是 hash_password:

// Android
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "hash_password")
// iOS
let channel = FlutterMethodChannel(name: "hash_password", binaryMessenger: registrar.messenger())
// OHOS
this.channel = new MethodChannel(binding.getBinaryMessenger(), "hash_password");

这是插件唯一的一条通道,OHOS 侧注册时必须与两端拼写一致。

4.4.2 命令处理与返回值模型

命令处理是"一问一答"模型:Dart 侧只调用 getPlatformVersion,原生侧只实现 getPlatformVersion:

// Dart
final String? version = await _channel.invokeMethod('getPlatformVersion');
// OHOS
onMethodCall(call: MethodCall, result: MethodResult): void {
  if (call.method == "getPlatformVersion") {
    result.success("OpenHarmony " + deviceInfo.displayVersion)
  } else {
    result.notImplemented()
  }
}

返回值约定:原生侧返回 "平台名 + 系统版本号" 字符串,Dart 侧映射为 String。三端的返回语义完全一致:

平台返回值示例
Android"Android " + Build.VERSION.RELEASE(如 Android 13.0)
iOS"iOS " + UIDevice.current.systemVersion(如 iOS 17.0)
OHOS"OpenHarmony " + deviceInfo.displayVersion(如 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 是无副作用的同步查询,调用结束即完成全部工作。

五、补全 OHOS 原生实现与工程配置

5.1 在 HashPasswordPlugin.ets 中实现原生能力

业务读取 HashPassword.platformVersion 后,原生侧只需返回系统版本字符串;哈希、编码、截断全部在 Dart 侧完成,原生不参与。

HashPasswordPlugin 只实现 FlutterPlugin 和 MethodCallHandler:前者接入 Engine 生命周期,后者组成命令处理链路。不需要 AbilityAware(没有权限弹窗),没有平台视图,也没有事件订阅。

原生插件位于(本库只有一个原生文件):

ohos/src/main/ets/components/plugin/HashPasswordPlugin.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 是本插件唯一用到的系统能力,提供 displayVersion 系统版本字段。
5.1.2 连接 Flutter Engine
getUniqueClassName(): string {
  return "HashPasswordPlugin"
}

onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.channel = new MethodChannel(binding.getBinaryMessenger(), "hash_password");
  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 的 displayVersion 字段。

这里有一个真实的教训:初稿凭记忆写成 deviceInfo.version,编译期直接报属性不存在。OpenHarmony 的版本信息并不是 deviceInfo 上叫 version 的字段,正确做法是先查本机 SDK 的类型声明再写代码:

// ohos/sdk/default/openharmony/ets/api/@ohos.deviceInfo.d.ts(节选)
/**
 * The software version visible to consumers, for example, OpenHarmony 6.1.1.120.
 *
 * @syscap SystemCapability.Startup.SystemInfo
 * @since 7
 */
const displayVersion: string;

.d.ts 同时确认了两件事:displayVersion 是 string 类型、自 API 7 起可用(本工程最低兼容 API 18,满足要求)。改用 displayVersion 后一次编译通过。这也是适配的一般原则:先查 OpenHarmony SDK 的类型声明,再写系统 API,不要凭印象拼字段名。

5.1.4 命令处理与异常回传

命令处理把整个方法体包进 try/catch,异常时通过 result.error 回传,避免 Dart 侧的 Future 悬挂:

onMethodCall(call: MethodCall, result: MethodResult): void {
  try {
    if (call.method == "getPlatformVersion") {
      // 与 Android(Build.VERSION.RELEASE) / iOS(UIDevice.systemVersion) 语义对齐:
      // 返回系统发布版本号,前缀标明平台。
      result.success("OpenHarmony " + deviceInfo.displayVersion)
    } else {
      result.notImplemented()
    }
  } catch (e) {
    result.error("HashPasswordError", "Failed to handle method '" + call.method + "': " + e, null)
  }
}
  • 已知命令:拼接 "OpenHarmony " + displayVersion 返回;
  • 未知命令:result.notImplemented(),Dart 侧 invokeMethod 抛出 MissingPluginException,与两端一致;
  • 原生异常:以 HashPasswordError 为 code 回传错误详情,Dart 侧 invokeMethod 抛 PlatformException,调用方可以捕获处理。

上游模板的默认实现没有 try/catch 保护,这里的异常回传是适配时补充的增强项。

5.1.5 三端实现对照

三端实现逐行对照,契约完全一致:

环节Android(Kotlin)iOS(Swift)OHOS(ArkTS)
通道名"hash_password""hash_password""hash_password"
命令getPlatformVersiongetPlatformVersiongetPlatformVersion
版本来源Build.VERSION.RELEASEUIDevice.current.systemVersiondeviceInfo.displayVersion
返回值"Android 13.0""iOS 17.0""OpenHarmony 6.1.1.120"
未知命令result.notImplemented()result(FlutterMethodNotImplemented)result.notImplemented()
生命周期onAttachedToEngine / onDetachedFromEngineattachToRegistrar / detachFromEngineonAttachedToEngine / onDetachedFromEngine

业务侧不需要感知平台差异:示例页直接展示 platformVersion 的返回值,同一个页面在 Android/iOS/OHOS 上会分别显示对应前缀的版本号。

5.1.6 Engine 解绑时释放资源
onDetachedFromEngine(binding: FlutterPluginBinding): void {
  if (this.channel != null) {
    this.channel.setMethodCallHandler(null)
  }
}

Flutter Engine 销毁时清理通道 Handler。插件不持有通道之外的原生资源,解绑即完成全部清理。

5.2 声明插件和宿主权限

本插件的原生侧只读取系统版本信息,不申请任何敏感权限,是权限配置最简单的一类插件。

5.2.1 插件 HAR 的权限

插件的 ohos/src/main/module.json5 只声明 HAR 模块信息,不带 requestPermissions:

{
  "module": {
    "name": "hash_password",
    "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 读取 displayVersion 属于公开系统信息,不需要 user_grant 权限,因此无需 reason / usedScene 等声明。README.OpenHarmony_CN.md 中也明确写了"不申请任何敏感权限,无需额外权限配置"。

5.3 注册并导出插件

pubspec.yaml 通过以下配置声明 OHOS 插件类:

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: HashPasswordPlugin

插件的 ohos/index.ets 需要导出实现:

import HashPasswordPlugin from './src/main/ets/components/plugin/HashPasswordPlugin';
export default HashPasswordPlugin;

执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码,example/ohos/entry/.../GeneratedPluginRegistrant.ets 中会出现:

import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import HashPasswordPlugin from 'hash_password';

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

通常不应手工编辑该文件,因为下次构建可能覆盖它。本例只注册了 HashPasswordPlugin 一个插件;缺少它会导致 platformVersion 抛出 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.lakshay.hash_password_example(与 Android 包名 com.lakshay.hash_password 对应),调试签名 profile 的 bundleName 必须与此一致,否则签名包无法安装。EntryAbility 保持模板原样:本例的演示闭环全部由 Dart 完成,宿主没有注册任何额外通道。

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

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

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

文件应写清楚的内容
README.OpenSource上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖
README.md原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息
README.OpenHarmony_CN.md简介、安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题
README.OpenHarmony.md与中文说明对应的英文文档
CHANGELOG.OpenHarmony.mdOHOS 新增能力、适配版本、兼容限制与测试范围
LICENSE / NOTICE保留上游许可证;NOTICE 按许可证和原项目要求保留或补充
example/README.md依赖方式、运行目录、签名、操作步骤与效果图
pubspec.yaml、ohos/oh-package.json5核对包名、版本、插件注册、仓库地址、许可证和依赖
.gitignore忽略构建缓存及本机签名材料,不漏提交必要源码和配置

本仓库的适配提交 60c35a8 中已经包含 README.OpenHarmony_CN.md、README.OpenHarmony.md、CHANGELOG.OpenHarmony.md(记录 0.0.3-ohos-1.0.0-beta.1)、docs/hash_password_ohos_adaptation_blog.md 适配记录,以及 docs/test-evidence/ 下的真机截图与运行日志,但发布前仍需修正几处:

  1. ohos/oh-package.json5 的 license 字段仍是脚手架默认值 Apache-2.0、version 为 1.0.0,与上游 LICENSE(MIT,Copyright © 2021 Lakshay Chaudhary)及包版本 0.0.3 不一致,应改为一致的声明;
  2. README.OpenHarmony_CN.md / README.OpenHarmony.md 中的安装地址是占位符 https://atomgit.com/your-namespace/hash_password.git,需替换为实际发布仓库;
  3. README 版本对应表中的分支名写作 main,而仓库默认分支实际是 master,应统一;
  4. 已提交的 example/ohos/build-profile.json5 中包含本机调试签名的绝对路径与密钥口令(storeFile 指向 ~/.ohos/config/...),正式对外发布前应还原为占位配置,避免泄露本机材料。

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 ohos platform support for hash_password"
git tag 0.0.3-ohos-1.0.0-beta.1
git remote -v
git branch --show-current
git push -u origin master
git push origin 0.0.3-ohos-1.0.0-beta.1

本例的适配提交为 60c35a8,提交信息为 feat: add ohos platform support for hash_password,内容涵盖 ohos 平台实现、宿主工程、README/CHANGELOG 与真机测试证据;Dart 层除 pubspec.yaml 的平台声明与 SDK 约束外零改动。DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置(storeFile 等指向本机绝对路径),提交前需要从暂存内容中移除或还原为占位(见 6.1 第 4 条)。推送时,origin 应指向自己有写权限的仓库;上游仓库在 GitHub,无权限直接推送时先推送到自己的镜像(本例配套 AtomGit 仓库地址在 README 中为占位符,需先创建并替换),再推送 TAG。

推送后在托管平台发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行说明或运行图。目标分支和评审流程以接收仓库要求为准。

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

仓库自带 example/,可以直接用来调试插件和体验密码哈希与平台版本读取。

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

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

dependencies:
  flutter:
    sdk: flutter
  hash_password:
    path: ../

../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。注意 example 的 environment.sdk 已同步提升为 ">=2.17.0 <4.0.0"(见 9.6)。

7.2 通过 AtomGit 引入插件

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

dependencies:
  flutter:
    sdk: flutter
  hash_password:
    git:
      url: https://atomgit.com/<your-namespace>/hash_password.git
      ref: 0.0.3-ohos-1.0.0-beta.1

url 使用自己有写权限的实际发布仓库——本仓库 README 当前写入的是 your-namespace 占位地址,替换后方可安装。使用自己的适配版本时,先推送 TAG,再将 url 改为对应仓库,ref 改为自己的 TAG。注意上游 GitHub 仓库的 master 分支不包含 ohos/ 目录,直接引用上游会构建失败。

从插件根目录执行:

cd example
flutter pub get
flutter pub deps

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

7.3 调用接口实现密码哈希

仓库中的 example/lib/main.dart 已经是一个完整的演示页 HashPasswordDemoApp,包含平台版本卡片、密码输入、SHA 算法选择(ChoiceChip)、编码方式选择、长度限制开关、生成按钮、结果卡片和 Password_Hasher 组件式用法卡片。最小接入代码如下:

import 'package:flutter/material.dart';
import 'package:hash_password/hash_password.dart';
import 'package:hash_password/hashing_functionalities.dart';

class HashPage extends StatefulWidget {
  const HashPage({super.key});

  
  State<HashPage> createState() => _HashPageState();
}

class _HashPageState extends State<HashPage> {
  final TextEditingController _controller = TextEditingController();
  String _platformVersion = '';
  String _result = '';

  
  void initState() {
    super.initState();
    _loadVersion();
  }

  Future<void> _loadVersion() async {
    final String? version = await HashPassword.platformVersion;
    if (mounted) setState(() => _platformVersion = version ?? '未知');
  }

  void _generate() {
    final String hash =
        Hashing_Functionalities().input_hash(_controller.text, '256');
    final String encoded =
        Hashing_Functionalities().number_system_convert('Base64', hash);
    final String short =
        Hashing_Functionalities().final_encrypted_password(encoded, 16);
    setState(() => _result = short);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('hash_password 示例')),
      body: Column(
        children: [
          Text('平台版本:$_platformVersion'),
          TextField(controller: _controller, obscureText: true),
          TextButton(onPressed: _generate, child: const Text('生成加密密码')),
          Text('结果:$_result'),
        ],
      ),
    );
  }
}

启动后页面顶部的平台版本卡片会显示原生通道返回的 OpenHarmony x.y,验证插件已正确接入;输入口令点击生成,即可得到 SHA-256 + Base64 + 截断 16 位的结果。完整 Demo 在此基础上增加了 SHA-384/512 与 Hex/Base58 切换、长度上限输入、结果元信息展示,以及用 Password_Hasher 包裹输入框的实时哈希演示。

7.4 页面退出时的资源处理

HashPassword 是无状态静态入口,没有需要取消的订阅;页面退出时只需处理好自己的资源:

  • TextEditingController 在 dispose 中统一释放(完整 Demo 持有 _passwordController、_restrictController、_widgetController 三个控制器);
  • 异步回调中的 setState 前检查 mounted,避免页面已销毁后更新状态;
  • 原生侧没有订阅和缓存资源,不需要 Dart 侧配合清理;哈希计算是同步纯函数,随调用结束即释放。

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

8.1 分别验证插件与 example

从插件仓库根目录执行:

flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze

本仓库当前的 flutter analyze 报告 37 条问题(9 条 warning + 28 条 info),但全部来自上游既有代码风格,与 OHOS 适配无关:lib/hashing_functionalities.dart 和 lib/password_hasher.dart 的未使用 import、Hashing_Functionalities / Password_Hasher 等非 lowerCamelCase 命名、Password_Hasher 的非 final 字段(must_be_immutable),以及模板测试中已废弃的 setMockMethodCallHandler。由于适配原则是 Dart 层零改动,这些告警原样保留;接入方如需清理,建议先与上游沟通。

flutter test 在本机因 flutter_tester 进程连接问题(WebSocketException: Invalid WebSocket upgrade request)无法加载测试文件,属于本机环境问题而非代码缺陷,可在 CI 或其他机器复跑验证。测试本身只有模板的 getPlatformVersion 用例(mock 返回 '42')。example/test/widget_test.dart 已改写为示例页渲染断言(标题、平台版本卡片、算法/编码选项、按钮均存在)。

Dart 测试只能覆盖通道封装,getPlatformVersion 的真实返回和真机渲染还需要在鸿蒙设备上验证。

8.2 确认设备连接

hdc list targets
flutter devices

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

8.3 配置签名

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

  1. 用 DevEco Studio 打开 example/ohos,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs;
  4. 为 default product 选择或生成签名;
  5. 确认设备、应用包名、证书和 Profile 匹配;
  6. 再回到终端执行 Flutter 构建或运行。

签名材料保存在本机,公开仓库中只保留构建所需的通用配置。

8.4 运行示例

以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:

flutter run -d <device-id>

也可以先构建 HAP:

flutter build hap --debug

典型产物位于:

example/build/ohos/hap/

本例真机构建记录:Hvigor assembleHap 任务约 16.2 秒完成,产物为 entry-default-signed.hap(debug 签名包约 101 MB)。真机安装应选择与当前设备匹配的已签名产物。构建失败时按第九节的检查项排查签名与 SDK 配置后重试。

8.5 在设备上测试密码哈希流程

  1. 安装并启动应用,确认首页显示 hash_password Demo 标题,平台版本卡片显示 OpenHarmony 开头的系统版本号——这验证了原生通道已接通;
  2. 在密码输入框输入一段口令,依次选择 SHA-256 / SHA-384 / SHA-512,点击 生成加密密码;
  3. 切换编码方式 Hex / Base64 / Base58,确认同一哈希值转换出不同编码的结果;
  4. 打开 限制输出长度 并输入上限(如 16),确认结果被截断到指定位数;关闭开关后确认返回完整结果;
  5. 查看"组件式用法(Password_Hasher)"卡片:在输入框中输入文本,确认每次输入都实时生成 SHA-256 + Hex + 截断 12 位的结果;
  6. 与 Android/iOS 设备上相同输入、相同参数的输出对比,确认哈希结果完全一致(纯 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 页面中完成 平台版本查询 与 密码哈希生成:前者经原生通道返回系统版本,后者完全在 Dart 侧完成。

真机运行截图(OpenHarmony-6.1.1.120):


获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容

操作预期表现
在浏览器或图库中复制一张图片,点击「从剪贴板读取」
直接点击「选择本地图片」
剪贴板为空或内容不是图片时读取
OpenHarmony 首次读取弹出剪贴板权限授权框,允许后成功读取(本次使用允许 / 始终允许 / 不允许)

以下是操作的视屏,可以参考一下:

Example 启动授权 Example 启动授权 Example 启动授权


运行日志(节选,归档于 docs/test-evidence/)显示 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 的版本是否匹配。

处理顺序:

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

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

当前工程的 compatibleSdkVersion 是 API 18,因此 API 24 在安装版本门槛上是满足的;但设备还必须满足签名要求;本插件不依赖特殊系统能力或权限。

9.2 DevEco Studio 中看不到 entry 模块

插件的 ohos/ 目录是 HAR 模块,可安装应用的 entry 模块位于 example/ohos/entry。

请直接使用 DevEco Studio 打开:

encrypt_password/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;
  • default product 和 target 已正确关联;
  • 当前账号、证书和调试设备状态有效。

9.4 能安装但平台版本获取失败

platformVersion 返回 null 或抛 MissingPluginException 时按以下顺序检查:

  1. example/ohos/entry/.../GeneratedPluginRegistrant.ets 是否注册了 HashPasswordPlugin——重新执行 flutter pub get 与构建让其重新生成;
  2. pubspec.yaml 的 ohos.pluginClass 是否拼写为 HashPasswordPlugin,与 getUniqueClassName() 返回值一致;
  3. 通道名是否三端一致(hash_password),方法名是否为 getPlatformVersion;
  4. 确认运行的是 OHOS 设备/模拟器而不是其他平台——Android/iOS 上由各自的原生实现应答;
  5. 查看原生日志:注册失败时 GeneratedPluginRegistrant 会输出 Tried to register plugins with FlutterEngine failed,据此定位加载问题。

9.5 MissingPluginException

这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从仓库根目录执行:

cd example
flutter clean
flutter pub get
flutter run -d <device-id>

如果仍然出现,检查自动生成的插件注册文件中是否包含 HashPasswordPlugin,同时核对 pubspec.yaml、ohos/index.ets 和 oh-package.json5。

9.6 example 编译报错:super.key 需要 Dart 2.17

现象:构建 example 时报错,提示 super.key 之类语法需要 Dart 2.17 及以上。

原因:上游 pubspec.yaml 的 environment.sdk 约束是 ">=2.12.0 <3.0.0",过于陈旧;而重写后的 example 使用了 super.key、ChoiceChip 等需要 Dart 2.17 的语法。

处理方式:把插件根与 example 的 pubspec.yaml 约束同步提升为 ">=2.17.0 <4.0.0"(本仓库适配提交已包含该改动),再执行 flutter pub get。这也是适配老插件的常见前置步骤:先看 SDK 约束是否支撑现有工程语法。

9.7 编译成功但安装失败

常见原因包括:

  • HAP 未签名或使用了错误的 Profile;
  • 设备未加入调试设备列表;
  • 包名与签名 Profile 不匹配;
  • 安装包的 compatibleSdkVersion 高于设备 API;
  • 手机上已经安装了使用不同证书签名的同包名应用。

根据安装错误码区分签名、版本和包名冲突,再处理对应配置。

9.8 flutter create 不认识 ohos,或包名不合法

先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name hash_password(是 Dart 包名,不是仓库名 encrypt_password)。生成后注意清理模板多出来的 federated 骨架等文件(见 3.4),再核对 ArkTS 实现与两端契约一致。

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

先检查 url 是否指向已包含 OHOS 适配的仓库:上游 GitHub 仓库的 master 分支不包含 ohos/ 目录,直接引用会构建失败,应使用配套发布仓库(本仓库 README 中为占位地址,需先替换为实际仓库)。再确认 ref 写的是已推送的 TAG(如 0.0.3-ohos-1.0.0-beta.1),TAG 未推送时 Git 依赖会解析失败;注意该仓库默认分支是 master,README 版本表中的 main 是笔误。私有仓库还需在本机配置 Git 认证。

9.10 改了本地 ArkTS,Demo 为什么没变化

先检查 example/pubspec.yaml:Git 依赖读取远程提交,不会自动读取本地插件改动。本地联调切回 path: ../;测试远程版本则先提交推送,再更新依赖并核对 pubspec.lock 的 resolved-ref。原生代码变动后停止应用并重新构建运行,不能只做 Dart 热重载。

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/hash_password_ohos_adaptation_blog.md 的踩坑复盘。

处理方式任选其一:

  1. 显式传入 --org com.lakshay,跳过 iOS 工程探测直接生成 ohos 平台(本仓库实际采用的规避方式);
  2. 临时把 ios/ 与 example/ios/ 移出仓库,生成 ohos 平台后再恢复;
  3. 升级 Xcode 到支持该参数的版本。

相关链接

Logo

一站式 AI 云服务平台

更多推荐