Flutter第三方插件鸿蒙适配实战:KeyHash签名指纹获取能力移植、问题修复与示例应用真机落地
开发工具: 华为云码道
本文配套仓库: GunFZ/FlutterKeyHash
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/FlutterKeyHash
key_hash 将鸿蒙的应用签名证书指纹读取能力封装为 Flutter 插件,应用可以通过 KeyHash.getKeyHash 获取当前应用的签名指纹。本文以 8c9d08922be314886719af33c5bf4d73756f4265 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 真机运行。

插件目前支持 ohos 平台,上游源码位于 GitHub 仓库。文中的代码以上游 main 分支为适配基线,OHOS 改动记录在本地提交 14a1e6f8c09ca0a03611ebccf2f11cefc8e67b31,并按 v0.1.0 打 TAG 发布。

| 操作 | 预期表现 |
|---|---|
| 点击右上角眼睛图标(显示 / 隐藏) | |
| 切换 KeyHash 结果明文 / 掩码隐藏展示状态,切换图标样式 | |
| 点击「刷新」按钮 | 重新读取应用签名证书,计算并更新 KeyHash;更新耗时、获取次数;新增一条历史记录,界面实时刷新 |
| 点击「复制」按钮 | 将无空格的完整 KeyHash 字符串复制到剪贴板,底部弹出复制成功提示 |
| 长按获取历史条目 | 复制对应历史记录内的 KeyHash 值 |
| 点击「清空」 | (历史模块) 清空所有 KeyHash 获取历史列表,重置历史面板 |
| 页面初始化加载 | 渲染全部卡片,展示应用包信息、KeyHash 结果面板、获取历史、接口依赖信息;首次加载暂无历史记录 |
| 获取 KeyHash 成功 | 展示 64 位 SHA-256 指纹,显示字符长度、本次耗时、累计获取次数;填充历史记录 |
| 签名证书变更后刷新 | 刷新后得到新 KeyHash 指纹,更新历史列表 |
以下是操作的视屏,可以参考一下:
一、插件简介与适配目标
KeyHash 用于读取当前应用签名证书的指纹。业务层不需要自己解析签名材料,也不需要按平台分别实现指纹计算。
例如,接入 Facebook、Kakao 等第三方登录时,平台会要求配置应用签名指纹用于校验;上线前把各个平台的 KeyHash 填入后台即可完成校验配置。
签名指纹通过一次方法调用获取:Dart 侧发起 getKeyHash 调用,原生侧读取自身应用的签名信息并同步返回字符串结果,不涉及持续监听或事件推送。
二、环境准备
环境搭建参考社区文档: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 分支 | oh-3.44.9-dev | CPF-Flutter 对应开发分支 |
| Dart SDK | 3.12.2 | Dart 语言与包管理环境 |
| HarmonyOS 开发套件 | 7.0.0(API 26) | 开发套件版本及对应的 API 级别 |
compileSdkVersion | 工程未显式声明 | 由 DevEco 工程默认值决定 |
targetSdkVersion | 工程未显式声明 | 由 DevEco 工程默认值决定 |
compatibleSdkVersion | 5.1.0(18) | 当前工程声明的最低兼容版本 |
| 插件版本 | 0.0.2 | pubspec.yaml 中的包版本 |
| 原生语言 | ArkTS | HarmonyOS 插件实现 |
| 插件产物 | HAR | 被应用 entry 模块依赖 |
2.1 开发套件版本与工程中的 SDK 版本配置
7.0.0(API 26) 和 5.1.0(18) 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:
7.0.0(API 26)表示本机 HarmonyOS 开发套件版本为7.0.0,对应 API 26。5.1.0(18)是本文工程中compatibleSdkVersion的属性值,声明最低兼容 API 18。compileSdkVersion和targetSdkVersion未在本例的example/ohos/build-profile.json5中显式声明,编译时使用 DevEco 工程的默认值。
对应的 product 配置为:
{
"name": "default",
"compatibleSdkVersion": "5.1.0(18)",
"runtimeOS": "HarmonyOS"
}

本机使用 API 26 的开发套件编译,工程声明最低兼容 API 18。安装后能否读到签名指纹,取决于应用是否完成签名,以及设备上安装的是否为带签名信息的安装包。
三、从源码仓库开始准备适配工程
3.1 将上游源码同步到自己的工作仓库
适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。
在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。
key_hash 的上游仓库位于 GitHub,目前还没有 AtomGit 配套仓库。可以按上面的流程把 GitHub 上游导入 AtomGit 作为自己的工作仓库,也可以直接在 GitHub 上 Fork。下面使用 GitHub 上游地址拉取代码;需要提交修改时,使用自己有写权限的仓库或 Fork。
3.2 将代码拉取到宿主机
在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:
git clone https://github.com/GunFZ/FlutterKeyHash.git
cd FlutterKeyHash
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 FlutterKeyHash/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml、lib/ 和 example/。Git 仓库名是 FlutterKeyHash,Dart 包名是 key_hash。
本文的适配以 OHOS 适配前的上游提交为起点。需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:
git switch --detach 8c9d08922be314886719af33c5bf4d73756f4265
适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

图 1:拉取 GunFZ/FlutterKeyHash 并检查远程与当前提交。
3.3 在仓库根目录创建适配分支
接着在 FlutterKeyHash/ 根目录创建分支。分支名统一使用 feat/ohos_库名称_版本号,库名称取 pubspec.yaml 的 name,版本号取此次适配的基线版本。本例为:
git switch -c feat/ohos_key_hash_0.0.2
git branch --show-current
如果该分支已存在,使用 git switch feat/ohos_key_hash_0.0.2 切换即可。

图 2:在 master 创建适配分支,并建议补充 TAG 0.0.2-ohos-1.0.0-beta.1。
3.4 自动补全 OHOS 适配结构
分支创建后,仍在同一个插件根目录执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件:
flutter create --template=plugin --platforms=ohos --project-name key_hash .
git status --short
git diff -- pubspec.yaml lib example
--template=plugin指定插件模板。--platforms=ohos指定需要补全的平台。--project-name key_hash使用 Dart 包名,避免把仓库目录名作为包名。- 最后的
.表示在当前插件目录补全工程,不是另建一层key_hash/。
该命令生成 OHOS 平台脚手架,业务逻辑需要在 ArkTS 中实现。生成后通过 diff 检查 pubspec.yaml、lib/ 和 example/ 的变化,保留已有 API、其他平台注册项及依赖配置。不同 Flutter OH 版本生成的模板可能略有差异。
如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:
cd example
flutter create --platforms=ohos .
cd ..
配套仓库已经包含 ohos/ 和 example/ohos/,直接运行示例时可以跳过结构补全。新建插件则使用 flutter create --org com.nutpi --template=plugin --platforms=ohos key_hash;已有插件使用上面的 . 在当前目录补全。

图 3:执行 flutter create --platforms=ohos 后新增的 ohos/ 与 example/ohos/ 目录。
3.5 适配后的项目目录
适配后的关键目录如下:
FlutterKeyHash/
├── lib/
│ └── key_hash.dart
├── ohos/
│ ├── index.ets
│ ├── oh-package.json5
│ └── src/main/
│ ├── ets/components/plugin/KeyHashPlugin.ets
│ └── module.json5
├── example/
│ ├── lib/main.dart
│ └── ohos/entry/
├── test/
├── docs/
└── pubspec.yaml
项目根目录如下,其中包含 ohos/、example/ 和 docs/:
| 文件 | 主要职责 |
|---|---|
lib/key_hash.dart | 为业务应用提供最简的 KeyHash 获取入口 |
KeyHashPlugin.ets | 注册 Flutter 通道并调用 HarmonyOS 原生能力 |
插件 module.json5 | 声明 HAR 模块(无需权限) |
示例 entry module.json5 | 声明宿主应用 Ability、设备类型和权限场景 |
example/lib/main.dart | 展示包信息、KeyHash 结果、刷新、复制和获取历史 |

图 4:适配后的 FlutterKeyHash 项目目录,包含 lib/、ohos/、example/ohos/ 与 docs/。
四、Dart 接口与通道分析
OHOS 实现需要遵循 Dart 层已有的方法、参数和返回值约定。先阅读 lib/key_hash.dart,再在 ohos/src/main/ets/components/plugin/KeyHashPlugin.ets 中实现对应的原生调用。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。
本例的对应关系如下:
| Dart 入口或模型 | 通道协议 | OHOS 实现 | 应保持的行为 |
|---|---|---|---|
KeyHash.getKeyHash | key_hash / getKeyHash | bundleManager.getBundleInfoForSelfSync(flags) 读取 bundleInfo.signatureInfo.fingerprint | 保持 getKeyHash 方法名,参数为 packageName,返回签名字符串 |
原生端需要保持通道名、方法名和参数一致。系统能力不可用时,通过错误回调通知 Dart 层。
4.1 跨端架构与调用时序
Flutter 侧和 HarmonyOS 侧之间使用一条通道:
MethodChannel('key_hash'):负责发起getKeyHash调用并接收返回值。
MethodChannel 既发起命令,也返回结果。key_hash 没有 EventChannel,也没有持续推送的状态。
4.1.1 一次完整调用的时序
4.2 返回模型:签名指纹字符串
key_hash 没有跨端枚举或状态模型,原生直接返回一个字符串,lib/key_hash.dart 把它原样交给业务层。各平台返回值的语义如下:
| 平台 | 实现 | 返回值 |
|---|---|---|
| Android | KeyHash.kt:对签名证书做 SHA-1 摘要后 Base64 编码 | 签名证书指纹(Base64) |
| iOS | SwiftKeyHashPlugin.swift:桩实现 | "iOS " + 系统版本 |
| OHOS | KeyHashPlugin.ets:读取签名证书 SHA-256 指纹 | 64 位十六进制字符串 |
三个平台的格式并不相同。OHOS 返回的是 SHA-256 的十六进制指纹,与 Android 的 SHA-1/Base64 不能直接互换,业务方在填写第三方后台时需要按平台分别配置。
4.3 公开 API 与平台接口
lib/key_hash.dart 只暴露一个静态 getter,没有平台接口抽象,也没有回调参数:
class KeyHash {
static const MethodChannel _channel = const MethodChannel('key_hash');
static Future<String> get getKeyHash async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
print(packageName);
final String version1 =
await _channel.invokeMethod('getKeyHash', packageName);
return version1;
}
}
调用方式和使用时机如下:
- 业务层直接
await KeyHash.getKeyHash,得到Future<String>; - 内部先用 package_info_plus 取到当前应用的
packageName,再把它传给原生; - 原生读取签名信息并返回指纹字符串;
appName、version、buildNumber目前未被使用,属于上游保留的取数代码,flutter analyze会给出未使用变量提示。
4.4 Dart 通道协议分析
4.4.1 通道名称必须两端完全一致
static const MethodChannel _channel = const MethodChannel('key_hash');
通道名称属于跨语言协议。Dart 和 ArkTS 任何一端拼写不一致,都会出现“方法未实现”或返回值异常等问题。
4.4.2 获取实现
static Future<String> get getKeyHash async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
final String packageName = packageInfo.packageName;
final String version1 =
await _channel.invokeMethod('getKeyHash', packageName);
return version1;
}
packageName 由 package_info_plus 在本端取得,再作为参数传给原生;invokeMethod 的返回值就是原生读到的签名字符串。原生侧抛错时,invokeMethod 会以 PlatformException 的形式向上抛出,由调用方捕获处理。
4.4.3 调用结束与资源回收
getKeyHash 是一次性调用:没有 EventChannel、没有订阅,也没有需要取消的监听。每次调用相互独立,返回值到达后本次调用即结束,业务层不需要额外做清理。
五、补全 OHOS 原生实现与工程配置
5.1 在 KeyHashPlugin.ets 中实现原生能力
以 getKeyHash 为例:业务仍调用 KeyHash.getKeyHash,Dart 通道仍发送同名的 getKeyHash 命令。需要补全的是 KeyHashPlugin.ets 中的对应分支:调用 bundleManager.getBundleInfoForSelfSync 读取签名指纹,通过 result.success 回传结果,失败时通过 result.error 返回错误。这样业务页面沿用原有接口即可在 OHOS 上取到指纹。
KeyHashPlugin 实现 FlutterPlugin 和 MethodCallHandler。下面列出类中的主要成员和方法,完整文件还包含 getUniqueClassName() 等插件接口。
原生插件位于:
ohos/src/main/ets/components/plugin/KeyHashPlugin.ets
5.1.1 引入 Flutter 和 HarmonyOS 能力
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
import Log from '@ohos/flutter_ohos/src/main/ets/util/Log';
import bundleManager from '@ohos.bundle.bundleManager';
其中:
FlutterPlugin负责接入 Flutter Engine 生命周期;MethodChannel接收 Dart 发来的命令并回传结果;bundleManager提供查询自身 bundle 信息与签名指纹的能力;Log用于原生侧诊断日志。
5.1.2 连接 Flutter Engine
const TAG: string = 'KeyHashPlugin';
const CHANNEL_NAME = 'key_hash';
const METHOD_GET_KEY_HASH = 'getKeyHash';
export default class KeyHashPlugin implements FlutterPlugin, MethodCallHandler {
private channel: MethodChannel | null = null;
onAttachedToEngine(binding: FlutterPluginBinding): void {
Log.d(TAG, 'onAttachedToEngine');
this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
this.channel.setMethodCallHandler(this)
}
}
key_hash 只需要一条 MethodChannel,不需要 EventChannel 和 EventSink,因为签名指纹是调用即返回,不涉及连续状态。
5.1.3 读取签名指纹
const bundleFlags: number = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO;
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
const fingerprint: string = bundleInfo.signatureInfo.fingerprint;
这里用 GET_BUNDLE_INFO_WITH_SIGNATURE_INFO 标志请求签名信息,再读 signatureInfo.fingerprint,得到签名证书的 SHA-256 十六进制指纹。查询的是应用自身的 bundle 信息,因此不需要申请任何权限。
5.1.4 处理 MethodChannel 命令
onMethodCall(call: MethodCall, result: MethodResult): void {
if (call.method == METHOD_GET_KEY_HASH) {
// 见 5.1.3 与 5.1.5:读取 fingerprint 后 result.success
} else {
result.notImplemented()
}
}
方法名集中在文件顶部的常量里,Dart 和 ArkTS 两端都使用 getKeyHash。未识别的方法走 result.notImplemented(),避免通道收到未实现命令时出现难以定位的异常。
5.1.5 异常处理与错误回传
try {
// 与 Android 端 Facebook key hash(签名证书 SHA-1 的 Base64)语义等价:
// OHOS 端取签名证书的 SHA-256 指纹(signatureInfo.fingerprint),
// 无需申请任何权限(查询自身 bundle 信息)。
const bundleFlags: number = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO;
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
const fingerprint: string = bundleInfo.signatureInfo.fingerprint;
Log.d(TAG, 'getKeyHash fingerprint: ' + fingerprint);
result.success(fingerprint);
} catch (err) {
Log.d(TAG, 'getKeyHash failed: ' + JSON.stringify(err));
result.error('getKeyHash_failed', JSON.stringify(err), null);
}
result.error 把错误码和消息传回 Dart,触发 PlatformException;Log 则保留原生侧的诊断信息,便于在 hilog 中定位。
5.1.6 Engine 解绑时释放资源
onDetachedFromEngine(binding: FlutterPluginBinding): void {
Log.d(TAG, 'onDetachedFromEngine');
if (this.channel != null) {
this.channel.setMethodCallHandler(null);
this.channel = null;
}
}
Flutter Engine 销毁时解除通道的 Handler 引用,覆盖业务侧未主动处理的情况。
5.2 声明插件和宿主权限
key_hash 只读取自身应用的签名信息,不需要任何运行时权限,因此在插件 HAR 和宿主 entry 中都无需声明与能力相关的敏感权限。
5.2.1 插件 HAR 无需权限
插件的 ohos/src/main/module.json5 中没有 requestPermissions,只声明模块基本信息:
{
"module": {
"name": "key_hash",
"type": "har",
"deviceTypes": ["default"]
}
}
5.2.2 应用 entry 的权限
示例应用的 example/ohos/entry/src/main/module.json5 只保留了 Flutter 模板自带的 ohos.permission.INTERNET,它与 key_hash 的能力无关:
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
key_hash 既不读取传感器,也不访问用户数据,因此不需要 DETECT_GESTURE 之类的敏感权限,接入应用时也不会出现运行时授权弹窗。INTERNET 属于普通权限,无需额外填写权限原因资源。
5.3 注册并导出插件
pubspec.yaml 通过以下配置声明 OHOS 插件类:
flutter:
plugin:
platforms:
ohos:
pluginClass: KeyHashPlugin
插件的 ohos/index.ets 需要导出实现:
import KeyHashPlugin from './src/main/ets/components/plugin/KeyHashPlugin';
export default KeyHashPlugin;
执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码。本例生成的 GeneratedPluginRegistrant.ets 同时注册了 package_info_plus 的 PackageInfoPlugin 和 key_hash 的 KeyHashPlugin。通常不应手工编辑 GeneratedPluginRegistrant.ets,因为下次构建可能覆盖它。
注册异常的排查步骤见第九节 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"]
}
]
}
]
}
本例的 product 没有显式声明 compileSdkVersion 和 targetSdkVersion,编译时使用 DevEco 工程的默认值。配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。
六、补全交付文件并提交适配分支
6.1 除代码外还要补全哪些文件
代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:
| 文件 | 应写清楚的内容 |
|---|---|
README.OpenSource | 上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖 |
README.md | 原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息 |
README.OpenHarmony_CN.md | 简介、AtomGit 安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题 |
README.OpenHarmony.md | 与中文说明对应的英文文档 |
CHANGELOG.OpenHarmony.md | OHOS 新增能力、适配版本、兼容限制与测试范围 |
LICENSE / NOTICE | 保留上游许可证;NOTICE 按许可证和原项目要求保留或补充 |
example/README.md | 依赖方式、运行目录、签名、操作步骤与效果图;覆盖获取、刷新与复制 |
pubspec.yaml、ohos/oh-package.json5 | 核对包名、版本、插件注册、仓库地址、许可证和依赖 |
.gitignore | 忽略构建缓存及本机签名材料,不漏提交必要源码和配置 |
README.OpenSource 记录库本身的来源与版本。本例的包名为 key_hash,版本为 0.0.2,上游采用 MIT 许可证;Flutter 和 HarmonyOS SDK 版本写入环境说明。当前仓库还缺少 README.OpenHarmony_CN.md、README.OpenHarmony.md 和 CHANGELOG.OpenHarmony.md,提交前需要按仓库模板补全。另外 ohos/oh-package.json5 中标注的许可证是 Apache-2.0,与 LICENSE 的 MIT 不一致,需要统一。
6.2 提交前检查
提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:
git branch --show-current
git diff --check
git status --short
git diff --stat
git diff
检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。
6.3 提交并推送
文档和代码整理完成后,在根目录暂存并提交。文件名按项目实际情况调整:
git add ohos pubspec.yaml example test .gitignore
git add README.md README.OpenSource README.OpenHarmony_CN.md
git add README.OpenHarmony.md CHANGELOG.OpenHarmony.md
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: add OHOS support for key_hash 0.0.2"
git remote -v
git branch --show-current
git push -u origin feat/ohos_key_hash_0.0.2
DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除。推送时,origin 应指向自己导入或 Fork 的仓库(AtomGit 或 GitHub),当前分支为 feat/ohos_key_hash_0.0.2。
推送后在对应托管平台发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。
七、使用根目录 example 演示接入
仓库自带 example/,可以直接用来调试插件和查看签名指纹读取结果。
7.1 本地适配时使用路径依赖
当前 example/pubspec.yaml 的依赖是:
dependencies:
flutter:
sdk: flutter
key_hash:
path: ../
../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。
7.2 通过 Git 仓库引入插件
业务应用通过 Git 引入时,将 key_hash 的 path 配置替换为下面的 Git 依赖。这里固定到本文使用的提交:
dependencies:
flutter:
sdk: flutter
key_hash:
git:
url: https://github.com/GunFZ/FlutterKeyHash.git
ref: 14a1e6f8c09ca0a03611ebccf2f11cefc8e67b31
使用自己的适配版本时,先推送分支,再将 url 改为对应仓库,ref 改为 feat/ohos_key_hash_0.0.2。正式发布后可固定到 tag 或 commit。
需要注意,key_hash 还依赖 OHOS 分支的 package_info_plus(来自 https://atomgit.com/CPF-Flutter/flutter_plus_plugins.git 的 br_package_info_plus-v9.0.0_ohos),引入插件时会一并被解析。
从插件根目录执行:
cd example
flutter pub get
flutter pub deps
检查 example/pubspec.lock 中 key_hash 的来源为 git,并核对 url、ref 和 resolved-ref;同时确认 package_info_plus 解析到 AtomGit 的 OHOS 分支。还要检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。
7.3 调用接口获取并显示 KeyHash
下面的页面展示获取按钮和当前的签名指纹,可用于 example/lib/main.dart。仓库中的完整 Demo 还提供包信息、获取历史、复制和深色模式。
import 'package:flutter/material.dart';
import 'package:key_hash/key_hash.dart';
void main() {
runApp(const MaterialApp(home: KeyHashPage()));
}
class KeyHashPage extends StatefulWidget {
const KeyHashPage({super.key});
State<KeyHashPage> createState() => _KeyHashPageState();
}
class _KeyHashPageState extends State<KeyHashPage> {
String _keyHash = '';
bool _loading = true;
String? _error;
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
setState(() {
_loading = true;
_error = null;
});
try {
final String hash = await KeyHash.getKeyHash;
if (!mounted) return;
setState(() {
_keyHash = hash;
_loading = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_error = e.toString();
_loading = false;
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('KeyHash')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (_loading)
const Center(child: CircularProgressIndicator())
else if (_error != null)
Text('获取失败:$_error')
else
SelectableText(
_keyHash,
style: const TextStyle(fontFamily: 'monospace'),
),
const SizedBox(height: 16),
FilledButton(
onPressed: _loading ? null : _load,
child: const Text('刷新'),
),
],
),
),
);
}
}
7.4 页面退出时的异步处理
异步回调先检查 mounted,避免页面销毁后继续调用 setState。getKeyHash 是一次性调用,没有订阅需要取消,dispose 中无需额外清理。
多个页面都需要 KeyHash 时,可以在应用启动阶段获取一次并缓存,各页面直接读取缓存值,避免重复调用。
八、验证、构建与鸿蒙设备运行效果
8.1 分别验证插件与 example
从插件仓库根目录执行:
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
flutter test
接口测试应验证 packageName 的获取、getKeyHash 的参数传递和字符串返回值以及异常路径。本例的 test/key_hash_test.dart 通过 mock key_hash 通道和 package_info_plus 通道,验证 KeyHash.getKeyHash 返回 mock 值。Widget 测试应匹配实际保留的 Demo 页面;如果替换成第七节最小页面,也要相应调整原来的 UI 断言。
当前仓库的运行结果为:flutter analyze 0 error、5 warning(lib/key_hash.dart 中未使用变量、author 字段弃用、Git 依赖提示)、8 info(setMockMethodCallHandler 弃用);本机 flutter test 因 flutter_tester 的 WebSocketException 无法启动,属于本地测试环境问题,不是被测代码缺陷。Dart 测试覆盖接口和页面逻辑,签名指纹读取行为还需要在鸿蒙设备上验证。
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/ohos/entry/build/default/outputs/default/
目录中通常包含已签名和未签名 HAP。真机安装应选择与当前设备匹配的已签名产物。
8.5 在设备上测试获取流程
- 打开应用,确认页面加载完成后显示 KeyHash 结果和长度、耗时信息;
- 确认包信息卡片中的包名与
example/ohos中的应用包名一致; - 在 hilog 中查找
KeyHashPlugin --> onAttachedToEngine,确认插件已注册; - 在 hilog 中查找
KeyHashPlugin --> getKeyHash fingerprint,比对日志中的指纹和页面显示是否一致; - 点击“刷新”,确认结果稳定,历史记录新增一条;
- 点击“复制”,确认剪贴板内容与显示一致;
- 切换深色模式并反复刷新,确认无崩溃、无异常日志。
页面显示的指纹来自原生读取的签名证书,日志与页面一致才说明调用链路完整。
8.6 鸿蒙设备运行效果
完成适配后,Flutter 应用能够读取当前应用的签名证书指纹。下面是在华为 nova 14(TLR-AL00,OpenHarmony 6.1.1.120 / API 24)上的运行效果:
| 操作 | 预期表现 |
|---|---|
| 点击右上角眼睛图标(显示 / 隐藏) | |
| 切换 KeyHash 结果明文 / 掩码隐藏展示状态,切换图标样式 | |
| 点击「刷新」按钮 | 重新读取应用签名证书,计算并更新 KeyHash;更新耗时、获取次数;新增一条历史记录,界面实时刷新 |
| 点击「复制」按钮 | 将无空格的完整 KeyHash 字符串复制到剪贴板,底部弹出复制成功提示 |
| 长按获取历史条目 | 复制对应历史记录内的 KeyHash 值 |
| 点击「清空」 | (历史模块) 清空所有 KeyHash 获取历史列表,重置历史面板 |
| 页面初始化加载 | 渲染全部卡片,展示应用包信息、KeyHash 结果面板、获取历史、接口依赖信息;首次加载暂无历史记录 |
| 获取 KeyHash 成功 | 展示 64 位 SHA-256 指纹,显示字符长度、本次耗时、累计获取次数;填充历史记录 |
| 签名证书变更后刷新 | 刷新后得到新 KeyHash 指纹,更新历史列表 |
以下是操作的视屏,可以参考一下:
对应的原生日志为:
KeyHashPlugin --> onAttachedToEngine
KeyHashPlugin --> getKeyHash fingerprint: CAA8F9671D45061C714033DBAC23FAF04C35C0CBE46A4D2174FCA7BD846CD6DA
指纹由设备上的签名证书决定,更换证书或重新签名后结果会变化,需要在目标应用上重新读取。
九、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 打开:
FlutterKeyHash/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 能安装但拿不到 KeyHash,或返回为空
按以下顺序检查:
packageName是否取到:key_hash 依赖 package_info_plus,确认依赖解析到 AtomGit 的 OHOS 分支br_package_info_plus-v9.0.0_ohos,并且pubspec.lock中的resolved-ref正确;- 应用是否已正确签名:
bundleInfo.signatureInfo.fingerprint只有在安装包带签名信息时才可读,未签名或签名异常时会进入错误分支; hilog中是否出现KeyHashPlugin --> onAttachedToEngine;hilog中是否出现KeyHashPlugin --> getKeyHash fingerprint;- 页面是否进入了错误分支,如果抛出了
PlatformException,按错误码和消息定位; - 与系统工具或 DevEco 中查看到的签名指纹比对,确认插件返回值与安装包一致。
9.5 MissingPluginException
这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从仓库根目录执行:
cd example
flutter clean
flutter pub get
flutter run -d <device-id>
如果仍然出现,检查自动生成的插件注册文件中是否包含 KeyHashPlugin 和 PackageInfoPlugin,同时核对 pubspec.yaml、ohos/index.ets 和 oh-package.json5。
9.6 重复调用会不会累积状态
getKeyHash 是无状态的一次性调用,没有 EventChannel、没有订阅,也没有需要释放的监听。多次点击“刷新”只会重新读取一次签名指纹,不会累积回调或造成重复推送。并发调用同样不会互相干扰,但结果在同一个签名证书下是一致的。
9.7 编译成功但安装失败
常见原因包括:
- HAP 未签名或使用了错误的 Profile;
- 设备未加入调试设备列表;
- 包名与签名 Profile 不匹配;
- 安装包的
compatibleSdkVersion高于设备 API; - 手机上已经安装了使用不同证书签名的同包名应用。
根据安装错误码区分签名、版本和包名冲突,再处理对应配置。
9.8 flutter create 不认识 ohos,或包名不合法
先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name key_hash;仓库名 FlutterKeyHash 不能直接作为 Dart 包名。生成后检查 diff,再补充 ArkTS 业务实现。
9.9 Git 依赖提示找不到分支或无权限
先检查 URL 是否指向已同步的目标仓库,再确认 feat/ohos_key_hash_0.0.2 已推送。分支尚未推送时,可以先使用第七节的提交号。私有仓库还需在本机配置 Git 认证。如果报错来自 package_info_plus,核对 AtomGit 的 br_package_info_plus-v9.0.0_ohos 分支是否可访问。
9.10 改了本地 ArkTS,Demo 为什么没变化
先检查 example/pubspec.yaml:Git 依赖读取远程提交,不会自动读取本地插件改动。本地联调切回 path: ../;测试远程版本则先提交推送,再更新依赖并核对 pubspec.lock 的 resolved-ref。原生代码变动后停止应用并重新构建运行,不能只做 Dart 热重载。
9.11 示例页在真机上白屏
原示例把包名等超长文本放在 ListTile 的 trailing 中,在 OHOS 上出现渲染异常,页面白屏且没有明显报错。改成用 Column 做纵向排布(标签在上、取值在下),取值使用 SelectableText 整行展示后,长文本可以正常换行显示,也支持长按选择。仓库中的 example/lib/main.dart 已按该布局实现。遇到白屏时先停止应用再重新运行,只做 Dart 热重载不会重建原生渲染。
相关链接
更多推荐





所有评论(0)