开发工具: 华为云码道

本文配套仓库: 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 结果面板、获取历史、接口依赖信息;首次加载暂无历史记录
获取 KeyHash 成功展示 64 位 SHA-256 指纹,显示字符长度、本次耗时、累计获取次数;填充历史记录
签名证书变更后刷新刷新后得到新 KeyHash 指纹,更新历史列表

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

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


一、插件简介与适配目标

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 SDK3.44.9+ohos-0.0.1-canary1Flutter 编译与 OHOS 平台工具链
Flutter 分支oh-3.44.9-devCPF-Flutter 对应开发分支
Dart SDK3.12.2Dart 语言与包管理环境
HarmonyOS 开发套件7.0.0(API 26)开发套件版本及对应的 API 级别
compileSdkVersion工程未显式声明由 DevEco 工程默认值决定
targetSdkVersion工程未显式声明由 DevEco 工程默认值决定
compatibleSdkVersion5.1.0(18)当前工程声明的最低兼容版本
插件版本0.0.2pubspec.yaml 中的包版本
原生语言ArkTSHarmonyOS 插件实现
插件产物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.getKeyHashkey_hash / getKeyHashbundleManager.getBundleInfoForSelfSync(flags) 读取 bundleInfo.signatureInfo.fingerprint保持 getKeyHash 方法名,参数为 packageName,返回签名字符串

原生端需要保持通道名、方法名和参数一致。系统能力不可用时,通过错误回调通知 Dart 层。

4.1 跨端架构与调用时序

Flutter 侧和 HarmonyOS 侧之间使用一条通道:

  1. MethodChannel('key_hash'):负责发起 getKeyHash 调用并接收返回值。

Flutter 页面

KeyHash 对外 API

MethodChannel key_hash

ArkTS KeyHashPlugin

BundleManager 自身 bundle 信息

String 签名指纹

MethodChannel 既发起命令,也返回结果。key_hash 没有 EventChannel,也没有持续推送的状态。

4.1.1 一次完整调用的时序
HarmonyOS BundleManager KeyHashPlugin.ets KeyHash Flutter App HarmonyOS BundleManager KeyHashPlugin.ets KeyHash Flutter App await KeyHash.getKeyHash invokeMethod(getKeyHash, packageName) getBundleInfoForSelfSync(flags) bundleInfo.signatureInfo.fingerprint result.success(fingerprint) Future<String> 完成

4.2 返回模型:签名指纹字符串

key_hash 没有跨端枚举或状态模型,原生直接返回一个字符串,lib/key_hash.dart 把它原样交给业务层。各平台返回值的语义如下:

平台实现返回值
AndroidKeyHash.kt:对签名证书做 SHA-1 摘要后 Base64 编码签名证书指纹(Base64)
iOSSwiftKeyHashPlugin.swift:桩实现"iOS " + 系统版本
OHOSKeyHashPlugin.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.mdOHOS 新增能力、适配版本、兼容限制与测试范围
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 模块配置自动签名:

  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/ohos/entry/build/default/outputs/default/

目录中通常包含已签名和未签名 HAP。真机安装应选择与当前设备匹配的已签名产物。

8.5 在设备上测试获取流程

  1. 打开应用,确认页面加载完成后显示 KeyHash 结果和长度、耗时信息;
  2. 确认包信息卡片中的包名与 example/ohos 中的应用包名一致;
  3. 在 hilog 中查找 KeyHashPlugin --> onAttachedToEngine,确认插件已注册;
  4. 在 hilog 中查找 KeyHashPlugin --> getKeyHash fingerprint,比对日志中的指纹和页面显示是否一致;
  5. 点击“刷新”,确认结果稳定,历史记录新增一条;
  6. 点击“复制”,确认剪贴板内容与显示一致;
  7. 切换深色模式并反复刷新,确认无崩溃、无异常日志。

页面显示的指纹来自原生读取的签名证书,日志与页面一致才说明调用链路完整。

8.6 鸿蒙设备运行效果

完成适配后,Flutter 应用能够读取当前应用的签名证书指纹。下面是在华为 nova 14(TLR-AL00,OpenHarmony 6.1.1.120 / API 24)上的运行效果:


KeyHash 示例页 真机运行效果 真机运行效果 真机运行效果 真机运行效果 真机运行效果

操作预期表现
点击右上角眼睛图标(显示 / 隐藏)
切换 KeyHash 结果明文 / 掩码隐藏展示状态,切换图标样式
点击「刷新」按钮重新读取应用签名证书,计算并更新 KeyHash;更新耗时、获取次数;新增一条历史记录,界面实时刷新
点击「复制」按钮将无空格的完整 KeyHash 字符串复制到剪贴板,底部弹出复制成功提示
长按获取历史条目复制对应历史记录内的 KeyHash 值
点击「清空」(历史模块) 清空所有 KeyHash 获取历史列表,重置历史面板
页面初始化加载渲染全部卡片,展示应用包信息、KeyHash 结果面板、获取历史、接口依赖信息;首次加载暂无历史记录
获取 KeyHash 成功展示 64 位 SHA-256 指纹,显示字符长度、本次耗时、累计获取次数;填充历史记录
签名证书变更后刷新刷新后得到新 KeyHash 指纹,更新历史列表

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

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

对应的原生日志为:

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 的版本是否匹配。

处理顺序:

  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 打开:

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

9.4 能安装但拿不到 KeyHash,或返回为空

按以下顺序检查:

  1. packageName 是否取到:key_hash 依赖 package_info_plus,确认依赖解析到 AtomGit 的 OHOS 分支 br_package_info_plus-v9.0.0_ohos,并且 pubspec.lock 中的 resolved-ref 正确;
  2. 应用是否已正确签名:bundleInfo.signatureInfo.fingerprint 只有在安装包带签名信息时才可读,未签名或签名异常时会进入错误分支;
  3. hilog 中是否出现 KeyHashPlugin --> onAttachedToEngine;
  4. hilog 中是否出现 KeyHashPlugin --> getKeyHash fingerprint;
  5. 页面是否进入了错误分支,如果抛出了 PlatformException,按错误码和消息定位;
  6. 与系统工具或 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 热重载不会重建原生渲染。


相关链接

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐