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

上一篇梳理了从零到上架的全部流程。本文深入技术细节,聚焦鸿蒙(HarmonyOS)应用签名机制——它和 Android 的签名有什么本质不同?每种证书文件的作用是什么?ECDSA 算法为何成为鸿蒙的首选?我们将以 E-Brufen 项目的实际签名配置为解剖样本,逐层拆解这套兼顾安全性与移动端性能的签名体系。


一、鸿蒙签名体系的设计哲学:为什么它长成了这个样子?

在深入具体文件之前,我们先理解鸿蒙签名体系的顶层设计逻辑。做过 Android 开发的同学都知道,Android 的签名主要依赖 JKS 或 PKCS#12 密钥库 + 自签名证书,只要开发者持有私钥,就可以无限期地对 APK 进行签名,系统只校验“应用是否被篡改”,对“开发者身份”的认证相对松散。

而鸿蒙(尤其是鸿蒙 NEXT 纯血生态)参考了 Apple iOS 的 双重认证(Two-factor authentication) 模型,引入了 Provisioning Profile(描述文件) 机制。这意味着:

  1. 身份认证与打包签名分离:你的代码签名私钥(.p12)证明“你能签名”,但华为 CA 签发的证书(.cer)证明“你是合法开发者”,而描述文件(.p7b)则绑定“你这个应用能跑在哪台设备上、能开哪些权限”。
  2. 动态管控与吊销能力:如果开发者违规,华为可以不需开发者更换私钥,直接吊销其证书或描述文件,从而实现云端实时管控。
  3. 硬件级安全信任根:鸿蒙设备内置华为根证书(Root CA),所有应用签名的验证链条最终追溯到设备固件中的信任根,杜绝了中间人伪造。

理解了这三点,你就能明白为什么 E-Brufen 的签名配置远比 Android 的 build.gradle 签名配置复杂——它不是“拿着钥匙开门”,而是“拿着身份证+门禁卡+钥匙”的三重验证


二、四文件链式关系:从私钥到可安装包

E-Brufen 项目根目录下的四个文件,构成了一个完整的信任传递链条。我们先给出全景关系图(逻辑拓扑):

导出公钥与身份信息

提交至 AppGallery Connect

签发

绑定包名/权限/设备

最终签名打包

firstproject.p12
(私钥 + 自签名证书)

firstproject.csr
(证书签名请求)

华为 CA 根证书

22.cer
(CA 签名的公钥证书)

123456Release.p7b
(Provisioning Profile)

E-Brufen.hap
(可安装应用)

文件 格式 核心载荷 保密级别 可公开
firstproject.p12 二进制(PKCS#12) 非对称加密私钥 + 自签名证书链 绝密(等效根密码) ❌ 绝不提交
firstproject.csr 文本(PEM / Base64) 公钥 + X.509 身份信息(CN/O/OU/L/ST/C) 公开(不含私钥) ✅ 可上传审核
22.cer 二进制(DER)/ 文本(PEM) 华为 CA 签发的数字证书(含公钥与有效期) 半公开(泄露无安全风险) ✅ 可随仓库分发
123456Release.p7b 二进制(PKCS#7) Provisioning Profile(包名+证书指纹+权限+设备白名单) 半公开 ✅ 可随仓库分发

关键认知纠偏:很多新手以为 .p12 就是证书,其实 .p12保险柜(包含私钥),而 .cer 才是身份证。丢掉了 .p12,你就永远失去了对该应用的签名权,华为也无法帮你恢复——因为私钥不出硬件,这是密码学的铁律。


三、CSR(Certificate Signing Request):申请证书的“介绍信”

3.1 为什么要生成 CSR?

在向华为 CA 申请证书之前,你必须证明两件事:

  1. 你拥有一个合法的密钥对(公钥和私钥匹配)。
  2. 你声称的身份信息(公司/组织/地区)是真实可信的

CSR 就是承载这两个证明的标准化载体(遵循 PKCS#10 标准)。它包含 Subject(主题可分辨名称)公钥材料,以及使用对应私钥对以上信息计算出的数字签名

3.2 E-Brufen 的 CSR 生成命令深度拆解

# 第一步:生成密钥库(含密钥对)
keytool -genkeypair \
  -alias "e_brufen_key" \          # 别名,在 .p12 中唯一标识该密钥
  -keyalg EC \                     # 椭圆曲线算法(非 RSA)
  -sigalg SHA256withECDSA \        # 签名算法
  -keysize 256 \                   # 256 位密钥(对应 prime256v1 / secp256r1)
  -keystore firstproject.p12 \     # 输出文件名
  -storetype PKCS12 \              # 标准格式,兼容 OpenSSL
  -validity 3650 \                 # 有效期 10 年(仅密钥库本身,最终证书有效期由 CA 定)
  -dname "CN=E-Brufen Dev, OU=Dev, O=E-Brufen, L=Shenzhen, ST=Guangdong, C=CN"

# 第二步:生成 CSR 文件
keytool -certreq \
  -alias "e_brufen_key" \
  -keystore firstproject.p12 \
  -storetype PKCS12 \
  -file firstproject.csr \
  -sigalg SHA256withECDSA

参数细节深度解读:

  • -keyalg EC 为什么不是 RSA?
    鸿蒙推荐 EC 是因为在同等安全强度下(128-bit 安全级别),EC 256 的密钥长度仅为 256 bit,而 RSA 需要 3072 bit 才能达到同等安全。更短的密钥意味着更快的签名计算、更小的签名体积(对于 HAP 包体积极其友好)以及更低的功耗。

  • -dname 中的 CN/O/OU/L/ST/C 分别代表什么?
    这是 X.500 标准目录结构。其中 CN(Common Name) 最重要,在鸿蒙体系中它通常填写开发者的姓名或团队名。这些信息最终会出现在 .cer 证书的主体(Subject)字段中,用户在安装应用时虽然不会直接看到,但在企业证书或 MDM 管理中会被严格校验。

  • -validity 3650 是否意味着证书能用 10 年?
    不是!这个 -validity 参数只作用于 .p12 自签名根证书的有效期。当你把 CSR 提交给华为后,华为 CA 签发的 .cer 证书有效期由华为策略决定:调试证书通常 3 个月到 1 年,发布证书最长 3 年。这里的 10 年是为了保证你的本地密钥库不会过快过期,避免频繁重建。

3.3 CSR 的肉眼观察(PEM 格式)

生成后的 firstproject.csr 是一段 Base64 编码的文本,用文本编辑器打开会看到:

-----BEGIN CERTIFICATE REQUEST-----
MIIB...(一大串字符)...
-----END CERTIFICATE REQUEST-----

你可以使用 OpenSSL 解析其内部结构来验证:

openssl req -in firstproject.csr -noout -text

你会看到输出的公钥信息(Subject Public Key Info)清晰地标注了 prime256v1 曲线参数。这是确认你的 CSR 确实是 EC 密钥的最直接手段。


四、P12(PKCS#12):数字身份的“保险柜”

4.1 PKCS#12 格式的优越性

相比于 Android 历史上默认使用的 JKS(Java KeyStore,专有格式),PKCS#12 是 IETF 标准(RFC 7292),具有跨平台、跨语言的特性。这意味着你不仅可以在 keytool 中使用它,还可以在 OpenSSL、Python Cryptography、Node.js Crypto 中无缝读取。

E-Brufen 的 build-profile.json5 中对 .p12 的引用如下:

{
  "storeFile": "D:/Flutter/firstproject/firstproject.p12",
  "storePassword": "00000019314A5E64E7AE...",  // 保险柜密码
  "keyAlias": "e_brufen_key",                 // 钥匙名称
  "keyPassword": "0000001994B09538837E...",   // 这把钥匙的独立密码
}

安全概念辨析:

  • storePassword(保险柜密码):打开 .p12 文件的大门,读取整个内容列表。
  • keyPassword(钥匙密码):从保险柜中取出具体那一把私钥的密码。
  • 在大多数配置中,这两个密码可以相同,但为了安全,建议设置不同密码。如果设置了不同密码,攻击者即使拿到 .p12 并猜中了保险柜密码,若拿不到钥匙密码,依然无法提取私钥。

4.2 查看 P12 内部结构的命令

如果你忘记了别名,或者想确认密钥是否存在,可以使用以下命令:

keytool -list -v -keystore firstproject.p12 -storetype PKCS12

输入密码后,你会看到类似输出:

Alias name: e_brufen_key
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=E-Brufen Dev, OU=Dev, O=E-Brufen, L=Shenzhen, ST=Guangdong, C=CN
Signature algorithm name: SHA256withECDSA
Subject Public Key Algorithm: EC (256 bits)

务必确认 Entry typePrivateKeyEntry,而不是 trustedCertEntry。后者只包含公钥证书,无法用于签名打包。

4.3 构建机上的 .p12 管理策略

在持续集成(CI)场景下,我们不能把 storePassword 明文写在 build-profile.json5 中提交到 Git(即使项目是私有仓库,也不建议)。E-Brufen 的解决方案是:

  1. 在项目根目录创建 local.properties(已加入 .gitignore)。
  2. 写入动态变量:
    sign.storePassword=your_strong_password
  3. 在 CI 流水线(如 GitHub Actions)中,通过 Secrets 注入这些变量,构建脚本在打包前使用 sed 替换 build-profile.json5 中的占位符。

五、CER(Certificate):华为 CA 盖过章的“身份证”

5.1 两级信任链的数学基础

当你将 CSR 上传至 AppGallery Connect 并完成开发者实名认证后,华为的证书签发系统会做两件事:

  1. 验证 CSR 签名:使用 CSR 中的公钥解密签名,比对哈希值,确认请求者确实持有私钥。
  2. 签发证书:使用华为的中间 CA 私钥对你的公钥进行数字签名,生成 22.cer

此时,.cer 的结构包含:

  • TBS(To Be Signed):待签名数据,包含你的公钥、身份信息、有效期。
  • 签名算法:华为 CA 使用的也是 SHA256withECDSA
  • CA 签名值:用华为根证书私钥签名的密文。

5.2 为什么我们要信任这个 .cer?

设备在安装 E-Brufen 时,鸿蒙内核会执行以下校验逻辑:

1. 提取 .hap 文件中的签名块(Signature Block),获取开发者签名。
2. 用 .cer 中的开发者公钥验证开发者签名 → 确保证书未被篡改且匹配。
3. 用设备内置的华为根证书(Root CA)公钥验证 .cer 上的 CA 签名。
4. 校验通过 → 信任该应用来源合法。

这是一个 “签名链”(Certificate Chain)。如果缺少中间 CA 证书(通常 .p7b 已经携带了链信息),设备就无法完成这一步,安装会报 INSTALL_FAILED_INVALID_SIGNATURE

5.3 证书编码格式:DER 与 PEM

AppGallery Connect 下载的 .cer 通常是二进制 DER 格式。在鸿蒙开发中,如果你需要查看内容,可以将其转为 PEM 格式阅读:

openssl x509 -inform DER -in 22.cer -out 22.pem -text

在 E-Brufen 的实际构建中,鸿蒙工具链同时支持 DER 和 PEM。但为了保持跨平台一致性,我们通常保留为原始 DER 格式不动,避免换行符或字符集问题导致解析失败。


六、P7B(PKCS#7):串联一切的“任命状”

6.1 Provisioning Profile 的三大核心职能

如果把 .p12 比作私章.cer 比作身份证,那么 .p7b 就是岗位任命书,它明确了你这个“开发者身份”可以做什么、在哪里做。

在 E-Brufen 的 123456Release.p7b 文件中,二进制数据包含了以下结构化信息(可通过安全解码器查看):

  1. 应用包名(Bundle Name):必须严格等于 com.flutter.brufen。如果这里写的是 com.example.brufen,即使你的代码中 app.json5 改过来了,签名时也会因包名不匹配导致构建失败。
  2. 开发者证书指纹(SHA-256):与 22.cer 中的公钥哈希严格对应。一个 .p7b 可以包含多个证书指纹(用于团队协作),但单应用通常只绑一个。
  3. 能力权限列表(ACL):比如 ohos.permission.INTERNETohos.permission.KEEP_BACKGROUND_RUNNING 等。如果你的应用在 module.json5 中申请了一个权限,但该权限未在 .p7b 中预先授权(对于受限敏感权限),签名虽然能过,但运行时系统会直接拒绝授予。
  4. 设备白名单(Device Allowlist):调试 .p7b 会绑定具体的设备 UDID,只有这些特定设备能安装该调试包。发布 .p7b 则没有设备限制(或限制为“所有商用设备”)。

6.2 调试 Profile 与发布 Profile 的区别

维度 调试 Profile(Debug) 发布 Profile(Release)
设备限制 绑定 1-100 台特定测试机 UDID 无限制(可安装至所有设备)
有效期 通常 3 个月,过期需重新生成 通常 1-3 年
调试能力 允许获取堆栈、断点调试、查看日志 禁止调试(android:debuggable=false 等效)
权限限制 允许使用受限权限(需在 Profile 中勾选) 必须通过应用审核才能获取敏感权限
安装方式 可通过 DevEco Studio 直接运行或 hdc install 只能通过 AppGallery 分发或企业内测平台

E-Brufen 在开发阶段使用的是 调试 Profile,而在执行 flutter build hap --release 准备上架时,我们切换为 发布 Profile


七、SHA256withECDSA 算法深潜:为什么是它?

7.1 椭圆曲线数字签名算法(ECDSA)的数学原理简述

ECDSA 基于椭圆曲线离散对数问题(ECDLP)的难解性。E-Brufen 使用的曲线是 secp256r1(NIST P-256),其参数定义如下:

  • 基点 G:椭圆曲线上的一个公共参考点。
  • 阶 n:G 的标量乘法周期(一个极大的素数)。
  • 私钥 d:一个在 [1, n-1] 区间内随机选取的整数(256 bit)。
  • 公钥 QQ = d * G(椭圆曲线上的点乘运算,不可逆)。

签名流程(生成 (r, s)):

  1. 计算待签名数据(如 HAP 文件的字节流)的 SHA-256 哈希值 z
  2. 生成一个一次性随机数 k关键k 必须绝对随机且永不重复,否则私钥会通过两次签名被轻易算出——这是索尼 PS3 私钥泄露的历史教训)。
  3. 计算椭圆曲线点 (x1, y1) = k * G
  4. 计算 r = x1 mod n。若 r = 0,重选 k
  5. 计算 s = k^(-1) * (z + r * d) mod n。若 s = 0,重选 k
  6. 最终签名对即为 (r, s)

验签流程(设备安装时):

  1. 计算 HAP 哈希 z
  2. 计算 u1 = z * s^(-1) mod nu2 = r * s^(-1) mod n
  3. 计算椭圆曲线点 (x2, y2) = u1 * G + u2 * Q
  4. 验证 r == x2 mod n 是否成立。成立则签名合法。

7.2 ECDSA vs RSA 的硬核性能对比表(实测数据)

为了让你直观感受选择 EC 的优势,假设对 1MB 数据进行签名/验签(基于 OpenSSL 基准测试):

指标 RSA-2048 RSA-3072 ECDSA-256 (secp256r1)
密钥生成速度 较慢(约 0.4s) 很慢(约 1.2s) 极快(约 0.01s)
签名速度 中等(约 0.3ms) 较慢(约 0.8ms) 快(约 0.15ms)
验签速度 快(约 0.05ms) 中等(约 0.12ms) 较慢(约 0.4ms,但设备端极少只验一个签名
签名长度(字节) 256 384 70
安全等级(bits) 112 128 128
功耗消耗(相对值) 很高 低(约 1/3)

结论:对于 HAP 包签名,虽然 ECDSA 的验签速度略慢于 RSA-2048,但它带来的包体积缩减(签名块从 256 字节降至 70 字节)和密钥生成效率优势,在移动端分发场景下是压倒性的。特别是当鸿蒙设备需要预装大量应用时,节省的存储空间和验证功耗非常可观。

7.3 随机数 k 的重要性:E-Brufen 如何保证安全?

在生成签名时,鸿蒙的签名工具(jarsignerhapsigner)底层调用了操作系统的安全随机数生成器(如 /dev/urandom 或硬件 TRNG)。E-Brufen 在 CI 构建时,不干预这一底层过程,确保 k 的熵值充足,杜绝因随机数重复导致私钥泄露的风险。


八、build-profile.json5 签名配置深度解析

8.1 配置结构全景

E-Brufen 最终的签名配置如下(敏感密码已做脱敏处理):

{
  "app": {
    "signingConfigs": [
      {
        "name": "default_release",
        "type": "HarmonyOS",              // 鸿蒙特有,区别于 Android
        "material": {
          "storeFile": "./certificates/firstproject.p12",
          "storePassword": "${env.STORE_PASS}", // 通过环境变量注入
          "keyAlias": "e_brufen_key",
          "keyPassword": "${env.KEY_PASS}",
          "signAlg": "SHA256withECDSA",   // 与 keytool 生成时一致
          "profile": "./certificates/123456Release.p7b",
          "certpath": "./certificates/22.cer"
        }
      },
      {
        "name": "debug",
        "type": "HarmonyOS",
        "material": {
          // 调试配置指向调试证书路径...
        }
      }
    ],
    "products": [
      {
        "name": "default",
        "signingConfig": "default_release", // 引用上面的配置
        "compatibleSdkVersion": "6.1.0(23)",
        "runtimeOS": "HarmonyOS",
        "targetSdkVersion": "6.1.0(23)"
      }
    ],
    "buildModeSet": [
      { "name": "debug", "signingConfig": "debug" },
      { "name": "release", "signingConfig": "default_release" }
    ]
  }
}

8.2 关键字段的底层机理

  • type: "HarmonyOS":千万不要填成 Android。这会改变内部签名块的组织结构(鸿蒙使用 OpenHarmony 特定的 signature.tmf 格式)。
  • signAlg:必须与 keytool 生成时的 -sigalg 完全一致。如果 keytool 用的是 SHA256withECDSA,这里也必须是,否则 keytool 无法从 .p12 中正确加载私钥。
  • storeFile 路径:支持绝对路径和相对路径(相对于项目根目录)。在 E-Brufen 中我们使用相对路径 ./certificates/,便于团队协作者直接 clone 项目后使用(但需自行准备密码环境变量)。
  • SDK 版本号 6.1.0(23):括号中的 23 代表 API Level。鸿蒙 NEXT 的 API Level 不再与 Android 版本号混淆,需严格对照官方发布表。

8.3 Build Mode 与 Signing Config 的动态绑定

buildModeSet 允许你为 debug/profile/release 等不同构建模式指定不同的签名配置。在 E-Brufen 的开发流程中,如果执行 flutter run(Debug 模式),它默认会启用 debug 配置;当执行 flutter build hap --release 时,它会自动切换至 default_release。这避免了手动注释/取消注释配置的麻烦。


九、开发→调试→发布的签名演变全流程

回顾 E-Brufen 真实的 Git 提交记录,签名配置经历了以下演进:

阶段一:本地模拟器调试(无需真实签名)

  • 使用 DevEco Studio 自带的 自动签名(Auto Sign) 功能。
  • 该功能会自动生成一个临时的调试 .p12 和调试 .cer,并内置在用户目录的 .ohos 文件夹中。
  • 此时 build-profile.json5 中甚至不需要配置签名项,工具链自动注入。

阶段二:真机调试与团队内测(申请调试证书)

  • 硬件设备需注册至华为开发者后台,获取 UDID。
  • 生成 firstproject.csr,在 AppGallery Connect 申请 调试证书(Debug Cer)调试描述文件(Debug P7B)
  • build-profile.json5 中显式配置 debug 签名配置,指向刚刚下载的两个文件。
  • 提交记录 ab44498chore: remove INTERNET permission, update app metadata for AppGallery release —— 此时在权限和元数据对齐后,开始正式引入签名配置。

阶段三:发布上架前夜(申请发布证书)

  • 在 AppGallery Connect 提交应用审核,申请 发布证书(Release Cer)
  • 注意:发布证书必须使用与调试证书完全相同的 CSR 申请,或者是同一把私钥生成的 CSR。这意味着你可以保留 firstproject.p12 不变,仅重新在后台生成新的 .cer
  • 下载发布 Profile(无设备限制)。
  • 修改 build-profile.json5release 配置的 certpathprofile 路径。
  • 提交记录 891c03d3081c82:在生成程序化图标和修复引用后,最终完成了 Release 构建的签名收尾。

阶段四:构建机自动化打包

  • CI 脚本在拉取代码后,首先解密由仓库 Secrets 传入的 firstproject.p12 文件(仓库中存储的是加密版本)。
  • 使用 sed 替换 build-profile.json5 中的密码占位符为环境变量。
  • 执行 flutter build hap --release,产出已签名的 .hap 文件。

十、常见签名故障排查与解决方案

在实际配置中,E-Brufen 开发团队遇到过以下典型问题,并汇总了解决方案:

错误现象(构建输出) 根本原因 权威解决方案
keytool error: java.lang.Exception: Alias <aaa> does not exist -alias 参数名与 keytool -list 显示的不一致 执行 keytool -list -keystore firstproject.p12 查看真实别名,复制粘贴替换。
Failed to load private key from keystore: DerValue.getBigInteger .p12 密码错误,或密钥库损坏 确保 storePasswordkeyPassword 无多余空格。尝试用 OpenSSL 转换格式:openssl pkcs12 -in firstproject.p12 -out temp.pem -nodes
INSTALL_FAILED_INVALID_PROFILE (设备安装时) .p7b 描述文件中不包含该设备的 UDID 去 AppGallery Connect 重新编辑描述文件,添加当前测试机的 UDID,重新下载并替换。
SHA256withECDSA signature algorithm mismatch build-profile.json5 中的 signAlgkeytool 生成时不匹配 检查 keytool -genkeypair 时的 -sigalg 参数,默认可能是 SHA1withECDSA,务必统一为 SHA256withECDSA
Certificate chain is not complete .p7b 未携带中间 CA 证书,或 .cer 文件不完整 在 AppGallery Connect 下载 .p7b 时,务必勾选 “包含证书链” 选项。或使用 OpenSSL 手动补齐:openssl pkcs7 -in profile.p7b -print_certs
Invalid package name in profile: expected com.flutter.brufen, got com.example 项目 app.json5 中的 bundleName 与描述文件绑定的包名不一致 统一修改 app.json5 中的 bundleName,然后重新生成 CSR 并重新申请 Profile。仅修改代码不重新生成 Profile 无效。

十一、密钥与证书的安全生命周期管理

11.1 私钥的存储与备份

  • 绝对禁止:将 firstproject.p12 上传至 GitHub、GitLab 或任何云端代码仓库。
  • 离线冷备份:E-Brufen 团队将 .p12 文件存入加密的 USB 盘,并锁入物理保险柜,同时在 KeePassXC 中记录密码。
  • 密码强度storePasswordkeyPassword 长度至少 20 位,混合大小写、数字与特殊符号,且两者不相同。

11.2 证书过期与轮换预警

由于发布证书有效期为 3 年,而 .p12 有效期为 10 年,E-Brufen 设置了日历提醒:在证书到期前 90 天重新申请新的 .cer.p7b(无需更换私钥)。这样可以保证用户无需卸载重装应用,仅通过应用商店更新即可无缝过渡(因为公钥未变,签名验证通过)。

11.3 私钥泄露的应急响应

如果怀疑私钥泄露(如办公电脑被入侵),应立即:

  1. 在 AppGallery Connect 后台吊销(Revoke) 当前发布证书。
  2. 使用全新的 .p12(生成全新的密钥对)重新走一遍签名申请流程。
  3. 向华为申请紧急应用更新,因为旧版本将无法再上传更新(旧证书已被吊销)。

十二、总结:签名即身份,管理即安全

E-Brufen 的签名配置过程,本质上是一次对移动应用安全模型的深度实践。我们从四个核心文件的纠缠关系出发,深入到了椭圆曲线密码学的底层计算,最终回归到 build-profile.json5 的工程化落地。

回顾全文,这套体系的精髓在于:

  1. 分离关注点:私钥(.p12)、身份(.cer)、授权(.p7b)各司其职,互不混淆。
  2. 算法选型的前瞻性:采用 ECDSA-256 不仅是为了当下,更是为了在未来量子计算威胁到来前,预留向更高级别曲线(如 ECDSA-521)平滑演进的空间(尽管目前的设备算力尚不支持,但体系架构已兼容)。
  3. 工程化的严谨性:密码注入、路径管理、CI/CD 集成,每一个环节都必须符合“零信任”安全原则。

对于每一位准备拥抱鸿蒙 NEXT 生态的 Flutter 或 ArkTS 开发者而言,吃透这套签名机制,远比照搬别人项目里的 signingConfigs 配置重要得多——因为它不仅是构建流程的拦路虎,更是应用安全的第一道,也是最后一道防线


上一篇AppGallery 应用发布全流程:从签名到上架


E-Brufen Dev, Flutter & 鸿蒙开发者。项目地址:AtomGit - E‑Brufen 。若你在签名配置中遇到任何诡异报错,欢迎在评论区留下日志,我们共同探讨。

Logo

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

更多推荐