上一篇写了怎么把 Redis 编译成 HNP 包,这次说说后面踩的坑。说实话,我以为编译完就差不多了,结果打包和签名这块把我折腾得够呛。

从 HNP 到 HAP:我以为很简单

编译出 redis.hnp 后,我以为接下来就是打包成 HAP,然后就能在鸿蒙 PC 上跑了。事实证明我想多了。

鸿蒙的应用打包流程是这样的:HNP → HAP → APP。HNP 是原生包,HAP 是应用包,APP 是最终的上架包。每一步都有坑等着你。

第一个坑:HAP 打包参数

我一开始直接用 app_packing_tool 打包 HAP,结果报错说找不到 module.json。我:???这啥玩意儿?

查了半天才知道,HAP 打包需要一堆中间文件:

  • module.json - 模块配置
  • pack.info - 打包信息
  • resources.index - 资源索引
  • 还有一堆其他的

这些文件都是 DevEco Studio 构建项目时自动生成的。我一开始想手动创建,结果格式不对,打包一直失败。

后来我干脆在 DevEco Studio 里创建了一个空的鸿蒙项目,然后把 HNP 文件放进去,让 DevEco Studio 帮我生成这些文件。虽然有点绕,但至少能用了。

签名:最让人抓狂的部分

HAP 打包成功后,我以为就完事了。结果上传到 App Gallery Connect 的时候,提示错误码 991:非法软件包。

我:???啥意思?

查了一下,错误码 991 表示软件包未签名。我这才意识到,HAP 文件需要签名才能上架。

第一次尝试:直接用 app_packing_tool 签名

我看了文档,发现 app_packing_tool 的 hap 模式不支持签名参数。文档里说:

HAP 模式不支持签名参数,需要在打包后单独签名

行吧,那就单独签名。我找到了 hap-sign-tool.jar,开始研究怎么用。

签名参数:一堆乱七八糟的东西

hap-sign-tool 需要的参数有:

  • keystoreFile - 密钥库文件
  • keystorePwd - 密钥库密码
  • keyAlias - 密钥别名
  • keyPwd - 密钥密码
  • appCertFile - 应用证书
  • profileFile - 配置文件
  • signAlg - 签名算法
  • compatibleVersion - 兼容版本

这些信息都在 build-profile.json5 里,但格式是 JSON5(支持注释和尾随逗号)。我一开始用 Python 的 json 模块解析,结果报错说格式不对。

我:???JSON5 不是 JSON 吗?

查了一下才知道,JSON5 是 JSON 的超集,标准 JSON 解析器不支持。我只好用 grepsed 硬提取,但这样很容易出错。

后来我写了个 Node.js 脚本,用 json5 包来解析,这才搞定。

密码加密:又一个坑

build-profile.json5 提取出密码后,我发现密码是加密的(以 000000 开头)。我直接拿这个加密密码去签名,结果报错:

ERROR: 11014003 Init keystore failed Error Message: keystore password was incorrect

我:???密码不对?

查了半天才知道,DevEco Studio 会把密码加密存储,但 hap-sign-tool 需要明文密码。我当时就懵了,这怎么搞?

最后我只好让用户通过环境变量提供明文密码:

export HAP_STORE_PASSWORD='你的明文密码'
export HAP_KEY_PASSWORD='你的明文密码'

虽然不太优雅,但至少能用了。如果用户不知道明文密码,就只能用 DevEco Studio 构建,或者手动签名。

compatibleVersion:差点把我搞崩溃

签名的时候还遇到一个问题:compatibleVersion 参数不能为空。

我一开始从 module.json 里提取 minCompatibleVersionCode,结果发现这个字段可能不存在。我又尝试从 minAPIVersion 提取,但 minAPIVersion 的格式是 60001021(6.0.1(21)),需要提取主要版本号。

我一开始用正则表达式提取,结果提取错了。最后用 cut -c1 提取第一位数字,这才搞定。

就这么一个小问题,我折腾了大半天。有时候真的觉得,这些工具的设计能不能再友好一点?

APP 打包:我以为很简单

HAP 签名成功后,我以为 APP 打包就简单了。结果又踩了坑。用 app_packing_tool 打包 APP 后,发现 APP 文件里包含了 redis.cerredis.p7b 文件。我:???这啥玩意儿?

查了一下才知道,我在打包 APP 的时候传了 --signature-path--certificate-path 参数,结果这些文件被直接打包进 APP 了,而不是用于签名。然后感觉又无解了,不签名如何上架呢?

最终解决方案

问了一下群友,发现有一种非常hack的方法可以让DevEco Studio正常打包带 hnphvigor 项目:就是直接修改 hvigor-ohos-plugin 的源码!具体修改方式如下:

修改 DevEco Studio\tools\hvigor\hvigor-ohos-plugin\src\builder\inner-java-command-builder\packing-tool-options.js

在文件最下面,添加 addHnpPath(t) 函数:

addHnpPath(t) {
  return this.addFieldAndPath("--hnp-path", t);
}
force(t) {
  return (
    this.commandList.push("--force"),
    this.commandList.push(t.toString()),
    this
  );
}
修改 DevEco Studio\tools\hvigor\hvigor-ohos-plugin\src\tasks\base\base-pack-hap-task.js

generateCommand 函数中填下以下代码:

let hnpPath = path_1.default.resolve(process.cwd(), "hnp");
if (fse.existsSync(hnpPath)) {
  a.addHnpPath(hnpPath);
}

修改后 generateCommand 函数如下(由于代码仓限制脚本内个别变量名有所修改):

generateCommand(e, t) {
    var s, o;
    const a = new packing_tool_options_js_1.PackingToolOptions();
    a.addCalledJarFile(this.sdkInfo.getPackageTool()),
      a .addMode(this.mode)
        .force(!0)
        .addLibPath(this.libPath)
        .addJsonPath(this.packageHapJsonPath)
        .addResourcesPath(this.priorResourcePath || this.resourcePath)
        .addIndexPath(this.indexPath)
        .addPackInfoPath(e)
        .addOutPath(t);
    let hnpPath = path_1.default.resolve(process.cwd(), "hnp");
    if (fse.existsSync(hnpPath)) {
      a.addHnpPath(hnpPath);
    }
    const i =
      hvigor_config_loader_1.HvigorConfigLoader.getInstance().getPropertiesConfigValue(
        common_const_js_1.HvigorConfigConst.OHOS_PACK_COMPRESS_LEVEL
      );
    return (
      i &&
        fse.existsSync(
          path_1.default.resolve(this.pathInfo.getModulePath(), "libs")
        ) &&
        a.addCompressLevel(i, this.sdkInfo.getSdkVersion()),
      this.targetService.getAnBuildMode() &&
        (fse.existsSync(this.anBuildOutputPath) &&
          a.addAnPath(this.anBuildOutputPath),
        fse.existsSync(this.apDirPath) && a.addDirList([this.apDirPath])),
      fse.existsSync(this.rpcidSc) && a.addSysCapPath(this.rpcidSc),
      fse.existsSync(this.jsAssetsPath) && a.addJsPath(this.jsAssetsPath),
      fse.existsSync(this.etsAssetsPath) && a.addEtsPath(this.etsAssetsPath),
      fse.existsSync(this.nodeModulesPath) &&
        a.addDirList([this.nodeModulesPath]),
      fse.existsSync(this.pkgContextInfoPath) &&
        (null ===
          (o =
            null === (s = this.targetService.getBuildOption()) || void 0 === s
              ? void 0
              : s.strictMode) || void 0 === o
          ? void 0
          : o.useNormalizedOHMUrl) &&
        a.addPkgContextInfoPath(this.pkgContextInfoPath),
      a.build()
    );
  }

修改后重启DevEco然后打包即可。

原帖地址:https://developer.huawei.com/consumer/cn/forum/topic/0202196674965008231

最后

这次折腾花的时间比编译还多。编译虽然遇到各种兼容性问题,但至少知道问题在哪,怎么解决。打包和签名这块,很多问题都是隐式的,不试不知道。

不过现在至少能打包出正确的 APP 文件了,也算是个进步。如果后续在真机上测试有问题,我再写第三篇。

代码和脚本都在 GitHub 上,虽然还有很多不完善的地方,但至少能用了。如果有人也想在鸿蒙 PC 上打包应用,可以参考一下。

当然,如果遇到问题,欢迎提 Issue。如果觉得哪里可以改进,也欢迎提 PR。


项目地址:https://github.com/ohosvscode/redis

如果这篇文章对你有帮助,或者你也踩过类似的坑,欢迎在评论区聊聊。

Logo

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

更多推荐