开发工具: 华为云码道

本文配套仓库: GunFZ/FlutterKeyHash
鸿蒙适配后仓库https://atomgit.com/oh-flutter/FlutterKeyHash

本文配套仓库:https://atomgit.com/oh-flutter/FlutterKeyHash(TAG:v0.1.0,分支:main),文中示例代码位于仓库 example/ 目录。
在这里插入图片描述

什么是 KeyHash? 在 Facebook、Kakao 等第三方登录场景中,平台要求开发者提供应用签名证书的指纹哈希值(KeyHash),用于校验调用方身份——只有指纹匹配的应用才能完成登录授权。Android 平台取签名证书 SHA-1 摘要后 Base64 编码,iOS 平台取系统版本(桩实现),鸿蒙平台则取签名证书的 SHA-256 指纹。获取这个值传统上需要通过 keytool 命令行手动查询,而 key_hash 插件将其封装为一个 Dart 静态方法调用,应用内一键获取。

把应用签名证书的指纹哈希值取出来,是 Facebook 登录、Kakao 登录等第三方身份校验场景下的高频需求:开发者需要在应用内动态获取签名指纹,注册到第三方平台完成密钥校验。鸿蒙应用同样需要这个能力。本文介绍如何使用鸿蒙化适配后的 Flutter 三方库 key_hash,用一个 KeyHash.getKeyHash 静态方法在鸿蒙 App 内获取签名证书的 SHA-256 指纹,零权限、即取即用,并附上 OpenHarmony-6.1.1.120 真机的完整实测记录。

一、最终运行效果

应用启动后,页面自动展示应用包信息(应用名称、包名、版本、构建号、当前平台)和 KeyHash 结果(按 8 字符分组的 SHA-256 指纹)。点击"刷新"按钮重新获取,点击"复制"按钮将指纹复制到剪贴板。历史记录卡片展示最近 8 次获取结果与耗时。

验证点结果
应用启动,Flutter 页面正常渲染包信息与 KeyHash 结果通过
KeyHash 结果为 64 位 SHA-256 指纹字符串(十六进制)通过
点击"刷新"按钮,重新获取指纹,历史列表新增一条记录通过
点击"复制"按钮,指纹复制到剪贴板,SnackBar 提示通过
点击眼睛图标,切换 KeyHash 显示/隐藏通过
历史列表展示最近 8 次获取结果、时间戳与耗时通过
深色模式切换正常,所有卡片自适应通过
全程无需申请任何敏感权限通过

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

鸿蒙技术点:FlutterPage 与 XComponent 渲染管线
鸿蒙侧的 Flutter 渲染入口是 FlutterPage 组件,它在 Index.ets 中被 @Entry 组件的 build() 方法直接使用。FlutterPage 内部封装了 XComponent——OpenHarmony 提供的底层渲染画布组件。XComponent 通过 NAPI 桥接 C++ 引擎层,将 Flutter 的 Skia 渲染管线挂载到鸿蒙的渲染树中,使 Dart 层的 Widget 树(包括包信息卡片、KeyHash 卡片、历史列表等全部组件)在鸿蒙设备上完整渲染。FlutterAbility 作为容器 Ability,管理 FlutterEngine 的生命周期,在 configureFlutterEngine 中注册所有平台插件。

二、key_hash 是什么

key_hash 原库(pub.dev 0.0.2,作者 GunFZ)是一个获取应用签名 KeyHash 的 Flutter 插件,广泛应用于 Facebook、Kakao 等第三方登录的密钥校验场景。原库支持 Android 与 iOS 平台:Android 端对签名证书做 SHA-1 摘要后 Base64 编码,iOS 端为桩实现返回系统版本。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持:Dart 层 API 零改动,新增 OHOS 平台原生插件实现,通过 bundleManager 读取自身签名证书的 SHA-256 指纹。

几个对使用者友好的特点:

  • 零权限bundleManager.getBundleInfoForSelfSync 是查询自身 bundle 信息的系统能力,不需要在 module.json5 中申请任何权限;
  • 一行调用await KeyHash.getKeyHash 即可获取签名证书指纹,Dart 层内部自动通过 package_info_plus 获取包名并传入原生通道;
  • 跨平台一套代码:Android、iOS、鸿蒙共用同一套 Dart API,运行在哪端就按哪端的方式获取签名信息;
  • 用途明确:获取的指纹可直接注册到 Facebook 开发者后台、Kakao 开发者后台等第三方平台,完成密钥校验配置。

接口说明

名称描述类型参数类型返回值必填鸿蒙平台支持
KeyHash.getKeyHash获取应用签名证书指纹静态属性Future

各平台返回值语义

平台返回值语义
Android签名证书 SHA-1 的 Base642Wq+xxx...=
iOS"iOS " + 系统版本桩实现
OpenHarmony / HarmonyOS签名证书 SHA-256 指纹64 位十六进制字符串

三、环境准备

本文所有实测均在以下环境完成:

版本说明
Flutter(ohos 版)3.44.9-ohos-0.0.1-canary1CPF-Flutter flutter_flutter
OpenHarmony SDK26.0.0API 26 类型定义,真机为 API 24
DevEco Studiohvigor / ohpm / hdc 工具链构建与签名
真机TLR-AL00(华为 nova 14)OpenHarmony 6.1.1.120(API 24),ohos-arm64
编译 SDK5.1.0(18)宿主工程 compatibleSdkVersion 同值,保留带括号的旧格式

鸿蒙技术点:compatibleSdkVersion 与 API Level 的对应关系
compatibleSdkVersion 是鸿蒙工程 build-profile.json5 中的关键字段,声明应用的最低兼容 API 版本。鸿蒙的 API 版本与系统版本一一对应:5.1.0(18) 对应 API 18,6.1.0(23) 对应 API 23,6.1.1.120 对应 API 24。真机安装时,系统会校验应用的 compatibleSdkVersion 不高于设备实际 API 版本,否则报"此应用暂不支持在当前设备安装"。本文 example 工程设为 5.1.0(18),在 API 24 真机上可正常安装运行。

两点提醒:

  • 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
  • 若在真机上安装应用报"此应用暂不支持在当前设备安装",是宿主工程的 compatibleSdkVersion 高于设备 API 导致的,与插件无关,处理方式见 FAQ Q3。

四、引入依赖

进入工程目录,在 pubspec.yaml 中添加 git 依赖:

dependencies:
  key_hash:
    git:
      url: https://atomgit.com/oh-flutter/FlutterKeyHash.git
      # ref: 根据下方表格选择不同框架适配的 TAG 版本
      ref: 0.0.2-ohos-1.0.0-beta.1

执行命令拉取依赖:

flutter pub get

TAG 命名规则:原库版本-ohos-版本号-beta.x

Flutter 框架版本TAG 名称分支名
3.440.0.2-ohos-1.0.0-beta.1main

说明:key_hash 内部依赖 package_info_plus 鸿蒙适配版(br_package_info_plus-v9.0.0_ohos 分支),flutter pub get 会自动拉取该依赖。该 TAG 已在 Flutter 3.44.9-ohos-0.0.1-canary1 + OpenHarmony-6.1.1.120(API 24)真机上实测通过。compatibleSdkVersion 设为 5.1.0(18) 即可在 API 24 真机安装运行。

五、代码接入

5.1 导入库

import 'package:key_hash/key_hash.dart';
import 'package:package_info_plus/package_info_plus.dart';

导入后即可使用 KeyHash 类的 getKeyHash 静态属性。package_info_plus 用于获取应用包信息(名称、包名、版本、构建号),与 key_hash 内部依赖同源。

5.2 获取签名证书指纹

final String keyHash = await KeyHash.getKeyHash;
print('KeyHash: $keyHash');

getKeyHash 是一个返回 Future<String> 的静态属性。内部流程分两步:先通过 PackageInfo.fromPlatform() 获取当前应用的包名,再通过 MethodChannel('key_hash').invokeMethod('getKeyHash', packageName) 调用原生侧获取签名证书指纹。

代码逐段分析:getKeyHash 内部流程
KeyHash.getKeyHash 的实现分三个阶段。第一阶段,PackageInfo.fromPlatform() 异步获取当前应用的 PackageInfo 对象,从中取出 appNamepackageNameversionbuildNumber——这些信息由 package_info_plus 插件通过自身的 MethodChannel 从原生侧获取。第二阶段,将 packageName 作为参数传入 MethodChannel('key_hash').invokeMethod('getKeyHash', packageName),触发原生侧的签名指纹查询。第三阶段,原生侧返回指纹字符串,Dart 层将其作为 Future<String> 的结果返回。整个过程对调用者透明——一行 await KeyHash.getKeyHash 即可完成。

5.3 展示包信息与指纹

Future<void> _loadAll() async {
  final Stopwatch watch = Stopwatch()..start();
  try {
    final PackageInfo info = await PackageInfo.fromPlatform();
    final String keyHash = await KeyHash.getKeyHash;
    watch.stop();
    if (!mounted) return;
    setState(() {
      _packageInfo = info;
      _keyHash = keyHash;
      _lastElapsedMs = watch.elapsedMilliseconds;
    });
  } on PlatformException catch (e) {
    _onError('PlatformException(${e.code}): ${e.message}');
  } catch (e) {
    _onError(e.toString());
  }
}

Stopwatch 计时从 PackageInfo.fromPlatform() 开始到 KeyHash.getKeyHash 返回结束,测量整个获取流程的耗时。PlatformException 捕获原生侧返回的错误(如签名信息查询失败),其他异常由通用 catch 兜底。

代码逐段分析:Stopwatch 耗时测量
Stopwatch 是 Dart 标准库 dart:async 中的高精度计时器。..start() 在构造时立即启动,watch.stop() 停止计时,watch.elapsedMilliseconds 返回经过的毫秒数。demo 用它测量从调用 PackageInfo.fromPlatform()KeyHash.getKeyHash 返回的总耗时,在历史列表中展示每次获取的耗时,并追踪最快记录。实测在 TLR-AL00 真机上,整个获取流程耗时通常在个位数毫秒级别。

5.4 指纹分组展示

String _groupedHash(String hash) {
  final StringBuffer buffer = StringBuffer();
  for (int i = 0; i < hash.length; i++) {
    if (i > 0 && i % 8 == 0) {
      buffer.write(' ');
    }
    buffer.write(hash[i]);
  }
  return buffer.toString();
}

SHA-256 指纹是 64 位十六进制字符串,一整行显示不利于阅读。_groupedHash 方法每 8 个字符插入一个空格,将指纹分为 8 组展示,如 A1B2C3D4 E5F6G7H8 ...。使用 StringBuffer 避免字符串拼接的 O(n²) 性能问题。

5.5 复制到剪贴板

Future<void> _copyText(String text, {String? label}) async {
  if (text.isEmpty) return;
  await Clipboard.setData(ClipboardData(text: text));
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('${label ?? '内容'}已复制到剪贴板')),
  );
}

Clipboard.setData 是 Flutter 服务层提供的剪贴板操作,在鸿蒙平台上通过 Flutter Engine 的平台通道映射到系统剪贴板服务。复制成功后弹出 SnackBar 提示用户。label 参数区分复制的是 KeyHash 还是历史记录中的某条指纹。

鸿蒙技术点:Clipboard 在鸿蒙上的行为一致性
Clipboard.setData / Clipboard.getData 是 Flutter 服务层提供的跨平台剪贴板 API。在鸿蒙平台上,Flutter Engine 将其映射到 @ohos.pasteboard 系统服务。Dart 层的 ClipboardData 对象通过 MethodChannel 传递到原生侧,由 flutter_ohos 框架内部桥接到 pasteboard.getPasteboard().setData()。整个流程对 Dart 层透明——开发者无需感知鸿蒙剪贴板 API 的细节,Clipboard.setData 在鸿蒙上与 Android/iOS 行为完全一致。

5.6 历史记录列表

final List<HistoryEntry> _history = <HistoryEntry>[];

// 在 _loadAll 成功后插入记录
_history.insert(
  0,
  (hash: keyHash, time: DateTime.now(), elapsedMs: watch.elapsedMilliseconds),
);
if (_history.length > _maxHistory) {
  _history.removeRange(_maxHistory, _history.length);
}

HistoryEntry 是一个 record 类型 ({String hash, DateTime time, int elapsedMs}),记录每次获取的指纹值、时间戳和耗时。新记录插入列表头部,超过最大数量(8 条)时移除尾部。列表支持长按条目复制对应指纹。

5.7 实战:在登录页面展示签名指纹

实际业务中常见的场景是应用首次启动或登录页面需要展示签名指纹,供开发者注册到第三方平台。下面是一个可直接使用的组件:

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

  
  State<KeyHashDisplay> createState() => _KeyHashDisplayState();
}

class _KeyHashDisplayState extends State<KeyHashDisplay> {
  String _keyHash = '';
  bool _loading = false;

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

  Future<void> _fetchKeyHash() async {
    setState(() => _loading = true);
    try {
      _keyHash = await KeyHash.getKeyHash;
    } catch (e) {
      _keyHash = '获取失败: $e';
    }
    if (!mounted) return;
    setState(() => _loading = false);
  }

  
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('签名证书指纹', style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            if (_loading)
              const CircularProgressIndicator()
            else
              SelectableText(
                _keyHash,
                style: const TextStyle(fontFamily: 'monospace', fontSize: 14),
              ),
            const SizedBox(height: 12),
            OutlinedButton.icon(
              onPressed: _loading ? null : () async {
                final hash = await KeyHash.getKeyHash;
                await Clipboard.setData(ClipboardData(text: hash));
              },
              icon: const Icon(Icons.copy),
              label: const Text('复制指纹'),
            ),
          ],
        ),
      ),
    );
  }
}

获取失败时(如签名信息查询异常),错误信息直接展示在卡片中。SelectableText 允许用户长按选择并复制指纹文本。该组件可直接嵌入登录页面或应用设置页面。

六、运行与验证

以下为 demo 工程的真机实测记录。仓库中 example 的 Bundle Name 为 std.gunfz.key_hash_example

设备项
机型TLR-AL00(华为 nova 14)
系统版本OpenHarmony-6.1.1.120
API 版本24
构建环境Flutter 3.44.9-ohos-0.0.1-canary1

6.1 验证一:自动获取签名指纹

安装、启动 demo:

# 构建 hap 后安装
hdc install entry-default-signed.hap

# 启动 demo
hdc shell aa start -b std.gunfz.key_hash_example -a EntryAbility

应用启动后,initState 调用 _loadAll() 自动获取包信息和签名指纹。页面展示:

  • 应用包信息卡片:应用名称、包名、版本、构建号、当前平台(ohos OpenHarmony-6.1.1.120
  • KeyHash 结果卡片:64 位 SHA-256 指纹字符串,按 8 字符分组展示,底部显示长度、耗时和获取次数

6.2 验证二:刷新与历史记录

点击"刷新"按钮重新获取:

# 模拟点击刷新按钮
hdc shell uitest uiInput click 542 1600

刷新后,KeyHash 结果更新(同一签名证书的指纹值不变),历史列表新增一条记录,显示指纹值(省略号截断)、时间戳和耗时。多次刷新后,历史列表保留最近 8 条,超出部分自动移除。最快耗时记录在列表上方显示。

在这里插入图片描述

6.3 验证三:复制与显示/隐藏切换

# 模拟点击复制按钮
hdc shell uitest uiInput click 800 1600

# 模拟点击眼睛图标切换显示/隐藏
hdc shell uitest uiInput click 950 400

点击"复制"按钮后,SnackBar 提示"KeyHash 已复制到剪贴板"。点击眼睛图标后,指纹区域从明文切换为掩码 •••• •••• •••• •••• ••••(已隐藏),再次点击切换回明文。

在这里插入图片描述

6.4 验证四:深色模式切换

# 模拟点击深色模式切换按钮
hdc shell uitest uiInput click 1000 120

AppBar 右上角图标点击后,整个页面在浅色与深色主题之间切换,所有卡片、文本、按钮颜色自适应。ThemeData 使用 ColorScheme.fromSeed 生成,浅色与深色种子色均为 indigo。

在这里插入图片描述

6.5 验证五:插件注册日志

通过 hdc hilog 抓取运行日志:

hdc shell hilog -r
hdc shell aa start -b std.gunfz.key_hash_example -a EntryAbility
hdc shell hilog | grep -E "KeyHashPlugin|getKeyHash"

日志输出:

KeyHashPlugin: onAttachedToEngine
KeyHashPlugin: getKeyHash fingerprint: A1B2C3D4E5F6...

插件由 GeneratedPluginRegistrant 注册成功,MethodChannel('key_hash') 通道就绪,getKeyHash 方法返回签名证书的 SHA-256 指纹。

在这里插入图片描述

实测结论:

验证点结果
应用启动,Flutter 页面正常渲染包信息与 KeyHash 结果通过
KeyHash 结果为 64 位 SHA-256 指纹字符串(十六进制)通过
点击"刷新"按钮,重新获取指纹,历史列表新增一条记录通过
点击"复制"按钮,指纹复制到剪贴板,SnackBar 提示通过
点击眼睛图标,切换 KeyHash 显示/隐藏通过
深色模式切换正常,所有卡片自适应通过
插件注册日志确认 KeyHashPlugin 挂载成功通过
全程无需申请任何敏感权限通过

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

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


七、工作原理

整个调用链路如下:

Dart: KeyHash.getKeyHash
  → PackageInfo.fromPlatform()  // 通过 package_info_plus 获取包名
    → MethodChannel('dev.fluttercommunity.plus/package_info').invokeMethod('getAll')
      → ArkTS: PackageInfoPlugin 应答,返回 appName/packageName/version/buildNumber
  → MethodChannel('key_hash').invokeMethod('getKeyHash', packageName)
    → ArkTS: KeyHashPlugin.onMethodCall('getKeyHash')
      → bundleManager.getBundleInfoForSelfSync(GET_BUNDLE_INFO_WITH_APPLICATION | GET_BUNDLE_INFO_WITH_SIGNATURE_INFO)
        → bundleInfo.signatureInfo.fingerprint  // SHA-256 指纹
          → result.success(fingerprint)
            → Dart 层收到 Future<String> 完成

Dart 侧通过 KeyHash.getKeyHash 发起调用,内部先经 package_info_plus 获取包名(虽然原生侧实际不使用该参数,但保持通道契约与 Android/iOS 一致),再通过 MethodChannel('key_hash') 调用原生侧的 getKeyHash 方法。

鸿蒙技术点:bundleManager 签名信息查询
鸿蒙侧的 KeyHashPlugin 通过 @ohos.bundle.bundleManager 查询自身 bundle 信息。getBundleInfoForSelfSync 是同步方法,接收一个 BundleFlag 位掩码参数:GET_BUNDLE_INFO_WITH_APPLICATION 获取应用基础信息,GET_BUNDLE_INFO_WITH_SIGNATURE_INFO 获取签名信息。返回的 BundleInfo 对象中,signatureInfo.fingerprint 字段包含签名证书的 SHA-256 指纹——这是鸿蒙系统在应用安装时从签名证书中提取并缓存的指纹值。整个查询过程是应用对自身信息的查询,不涉及其他应用,因此无需任何权限。

鸿蒙侧插件实现(ArkTS)核心代码:

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';

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;

  getUniqueClassName(): string {
    return "KeyHashPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    Log.d(TAG, 'onAttachedToEngine');
    this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
    this.channel.setMethodCallHandler(this)
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    Log.d(TAG, 'onDetachedFromEngine');
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null);
      this.channel = null;
    }
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method == METHOD_GET_KEY_HASH) {
      try {
        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);
      }
    } else {
      result.notImplemented()
    }
  }
}

代码逐段分析:BundleFlag 位掩码
BundleFlagbundleManager 模块中定义的枚举常量,用于指定 getBundleInfoForSelfSync 返回的 BundleInfo 中包含哪些信息字段。GET_BUNDLE_INFO_WITH_APPLICATION(值 0x00000001)使返回结果包含 applicationInfo 字段,GET_BUNDLE_INFO_WITH_SIGNATURE_INFO(值 0x00000010)使返回结果包含 signatureInfo 字段。两个标志通过位或运算 | 组合,一次性获取应用基础信息和签名信息。若不设置 GET_BUNDLE_INFO_WITH_SIGNATURE_INFOsignatureInfo 字段为 null,无法读取指纹。

代码逐段分析:getBundleInfoForSelfSync 与 getBundleInfo 的区别
bundleManager 提供两种查询方法:getBundleInfo 需要传入 bundleName 参数并要求 ohos.permission.GET_BUNDLE_INFO 权限,可查询任意应用;getBundleInfoForSelfSync 是同步方法,仅查询自身应用信息,不需要任何权限。key_hash 只需获取自身签名指纹,因此使用后者——这是该插件零权限的根本原因。Sync 后缀表示同步调用,在当前线程直接返回结果,不经过 Promise/callback。

鸿蒙技术点:GeneratedPluginRegistrant 多插件注册
GeneratedPluginRegistrant.ets 由 Flutter 工具链根据 pubspec.yaml 中的 ohos 平台配置自动生成。key_hash 依赖 package_info_plus,两者都声明了 ohos 平台插件类。因此生成的注册代码中同时注册了两个插件:new PackageInfoPlugin()new KeyHashPlugin(),依次添加到 FlutterEngine 的插件列表中。EntryAbility.configureFlutterEngine 中一行 GeneratedPluginRegistrant.registerWith(flutterEngine) 即完成全部插件注册。注册顺序由依赖解析顺序决定,开发者无需手动管理。

鸿蒙技术点:FlutterPlugin 与 MethodCallHandler 接口
KeyHashPlugin 实现了 FlutterPluginMethodCallHandler 两个接口。FlutterPluginonAttachedToEngine 在插件挂载到引擎时被调用,创建 MethodChannel('key_hash') 并设置自身为回调处理器;onDetachedFromEngine 在卸载时清理通道并置 null。MethodCallHandleronMethodCall 处理来自 Dart 层的方法调用——收到 getKeyHash 时通过 bundleManager 读取签名证书指纹并回传,未实现的方法返回 notImplemented()。所有方法调用包裹在 try/catch 中,异常时返回错误码 getKeyHash_failed

八、常见问题

Q1:KeyHash 返回的指纹值和 keytool 查到的不一样?

key_hash 在鸿蒙平台返回的是签名证书的 SHA-256 指纹(signatureInfo.fingerprint),格式为 64 位十六进制字符串。而 keytool -list -printcert -jarfile xxx.hap 查到的是证书的完整指纹列表,可能包含 SHA-1 和 SHA-256 两种。两者取的都是同一签名证书的指纹,但摘要算法不同——鸿蒙系统默认缓存的是 SHA-256。若第三方平台要求 SHA-1 格式,需要通过 keytool 命令行获取。

Q2: getKeyHash 报 PlatformException 怎么办?

PlatformException 通常由原生侧的 try/catch 捕获到异常后返回,错误码为 getKeyHash_failed。常见原因:应用未正确签名(Debug 模式下使用自动签名通常不会有此问题)、bundleManager 查询失败(系统内部错误)。检查 hdc hilog 中的 KeyHashPlugin: getKeyHash failed 日志可获取详细错误信息。确保应用已正确签名安装,且 compatibleSdkVersion 不高于设备 API 版本。

Q3:真机安装 demo 时提示"此应用暂不支持在当前设备安装"?

这是宿主工程的 compatibleSdkVersion 高于真机 API 版本导致的安装校验失败,与插件无关。将 build-profile.json5 中的 compatibleSdkVersion 调整为不高于真机 API 的版本(如 5.1.0(18),注意保留带括号的旧格式)即可。本文配套仓库的 example 已用此配置在 OpenHarmony-6.1.1.120(API 24)真机上安装实测通过。

Q4:为什么 Dart 层传入 packageName 但原生侧不使用?

Dart 层通过 package_info_plus 获取包名后传入 invokeMethod('getKeyHash', packageName),这是为了保持与 Android/iOS 端的通道契约一致。Android 端的 KeyHash.kt 实际上也不依赖传入的包名(它直接从 PackageManager 获取签名信息),鸿蒙端同样直接通过 getBundleInfoForSelfSync 查询自身信息,参数仅用于契约对齐。这保证了同一份 Dart 代码在三端行为一致。

Q5:package_info_plus 获取失败会影响 KeyHash 吗?

会。KeyHash.getKeyHash 内部先调用 PackageInfo.fromPlatform() 获取包名,若 package_info_plus 插件未正确注册或获取失败,getKeyHash 会抛出异常。确保 pubspec.yaml 中正确引入了 package_info_plus 鸿蒙适配版,且 GeneratedPluginRegistrant 中同时注册了 PackageInfoPluginKeyHashPlugin

Q6:指纹值在 Debug 和 Release 模式下不同吗?

是的。Debug 模式使用 Debug 证书签名,Release 模式使用 Release 证书签名,两者的指纹值不同。切换构建模式后,getKeyHash 返回的指纹值会变化。在注册到第三方平台时,需要同时注册 Debug 和 Release 两种指纹,或仅注册 Release 指纹用于生产环境。

九、结语

回顾一下:在 pubspec.yaml 中以 git TAG 引入 key_hash,调用 await KeyHash.getKeyHash 一行代码即可在鸿蒙 App 内获取签名证书的 SHA-256 指纹。鸿蒙侧通过 bundleManager.getBundleInfoForSelfSync 查询自身签名信息,零权限、零侵入,获取的指纹可直接注册到 Facebook、Kakao 等第三方平台完成密钥校验。同一份 Dart 代码在 Android、iOS、鸿蒙三端行为一致——Android 返回 SHA-1 Base64,iOS 返回系统版本,鸿蒙返回 SHA-256 指纹。已在 OpenHarmony-6.1.1.120(API 24)真机完整实测,自动获取、刷新、复制、显示/隐藏切换、深色模式全部通过。

使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。

相关链接

欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:


附:key_hash 核心能力对照表

能力维度实现方式鸿蒙表现跨平台一致性
签名指纹获取KeyHash.getKeyHashSHA-256 指纹,64 位十六进制API 一致,返回值格式各平台不同
包名获取package_info_plus鸿蒙适配版,正常返回三端一致
原生查询bundleManager.getBundleInfoForSelfSync同步返回,无需权限Android 用 PackageManager,iOS 为桩实现
签名信息signatureInfo.fingerprintSHA-256 指纹Android 为 SHA-1+Base64,iOS 为系统版本
通道契约MethodChannel('key_hash') / getKeyHash契约对齐 Android/iOS三端完全一致
错误处理try/catch + result.error错误码 getKeyHash_failed遵循各平台错误处理规范
权限要求getBundleInfoForSelfSync 无需权限Android/iOS 同样无需权限
插件注册GeneratedPluginRegistrant 自动注册同时注册 PackageInfoPlugin + KeyHashPlugin与 Android/iOS 机制一致
Logo

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

更多推荐