harmony-安装app的脚本

644 阅读6分钟

概述

现在鸿蒙手机安装harmony.app包有很大限制,他并不能像Android那样直接在手机上安装apk,也不像IOS可以传多个ipa,AppGallery Connect只能同时上传3个包,也就是说QA只能同时测3个不同的包,这样大大限制了开发效率和测试效率,大致解决方案

  1. 给QA开通git权限,下载 DevEco Studio ,让QA直接run对应的分支的包
  2. 直接让QA拿着手机让研发给跑包
  3. 上传AppGallery Connect,华为审核通过之后,直接扫码安装,缺点是:只能同时测试3个,还需要审核

但是上面这三种方案都对研发和QA不太友好,所以悄悄的写了个安装脚本,让QA直接运行即可,

  • 无需配置hdc环境变量
  • 无需下载DevEco Studio
  • 像Android 使用 adb 安装apk一样
  • 脚本下载hdc文件只有1.7M
  • 如果需要手动签名额外需要hap-sign-tool.jar和java两个文件【不建议在脚本中手动签名,请在打包服务器中,比如jenkins】,当然了测试签名还是可以放在脚本中的

编写 Shell 脚本,先上图

成功 image.png 错误

image.png

首先在 DevEco Studio 运行,看看执行了哪些命令

使用 DevEco Studio 创建一个工程,然后一个 basic的hsp 和 login的har,让entry依赖这两个mudule,

build task in 1 s 364 ms
Launching com.nzy.installapp
$ hdc shell aa force-stop com.nzy.installapp
$ hdc shell mkdir data/local/tmp/5588cff7d2344a0db70a270bb22aa455
$ hdc file send /Users/xxx/DevEcoStudioProjects/InstallApp/feature/login/build/default/outputs/default/login-default-signed.hsp "data/local/tmp/5588cff7d2344a0db70a270bb22aa455" in 54 ms
$ hdc file send /Users/xxx/DevEcoStudioProjects/InstallApp/entry/build/default/outputs/default/entry-default-signed.hap "data/local/tmp/5588cff7d2344a0db70a270bb22aa455" in 34 ms
$ hdc shell bm install -p data/local/tmp/5588cff7d2344a0db70a270bb22aa455  in 217 ms
$ hdc shell rm -rf data/local/tmp/5588cff7d2344a0db70a270bb22aa455
$ hdc shell aa start -a EntryAbility -b com.nzy.installapp in 148 ms
Launch com.nzy.installapp success in 1 s 145 ms

上面的命令的意思是

  • $ hdc shell aa force-stop [bundleName] 强制停止 bundleName 的进程
  • $ hdc shell mkdir data/local/tmp/5588cff7d2344a0db70a270bb22aa455 给手机端创建临时目录
  • $ hdc file send hsp 临时目录:把所有的hsp 发送到临时目录
  • $ hdc file send hap 临时目录:把hap 发送到临时目录
  • hdc shell bm install -p 临时目录:安装临时目录中的所有hsp和hap
  • $ hdc shell rm -rf 临时目录:删除手机端的临时目录
  • $ hdc shell aa start -a EntryAbility -b [bundleName]:启动bundleName的EntryAbility的页面 大家或许有疑惑,明明创建了 HAR,但是本次安装没有 HAR,因为 HAR 会被编译打包到所有依赖该模块的 HAP 和 HSP

咱们可以根据上面的流程大致写一下

脚本方案

  1. 检测hdc文件是否存在,不存在使用cur下载
  2. 检测是否连接手机,并且只有一个手机
  3. 检测传入app的路径是否存是以.app结尾,并且文件存在
  4. 创建手机端临时目录
  5. 解压.app到电脑端,复制里面的所有hsp和hap到 临时目录,如果需要手动签名可以在这一步去签名
  6. 安装临时目录的所有文件
  7. 删除手机临时目录以及电脑端解压app的目录

hdc文件

我们可以从华为官网下载 Command Line Tools,竟然有2.3G,这让脚本下载到猴牛马月,下载下来hdc在command-line-tools/sdk/default/openharmony/toolchains 当然了,我们可以精简文件,我发现只需要 hdc和libusb_shared.dylib 两个文件,所以直接把这两个文件打包的一个zip放在了gitee上(大约1.7M),放到cdn上供我们的脚本去下载,这样我们可以使用cur去下载,当然这个最好放在自己公司的cdn上,方便下载

首先创建install.sh的脚本

首先定义几个常量

  • hdcZip:下载下来的zip名
  • hdcTool:解压出来放到本文件夹
  • hdcPath:使用hdc命令的path
  • bundleName:自己的bundleName
  • entryAbility:要打开的Ability
# 下载下来的文件
hdcZip="tools.zip"
# 解压的文件夹 ,解压默认是和 install.sh 脚本在同一个目录
hdcTool="tools"
# hdc文件路径"
hdcPath="tools/hdc"
#包名
bundleName="com.nzy.installapp"
# 要打开的Ability
entryAbility="EntryAbility"

定义打印

  • printInfo:打印正常信息
  • printError:打印错误信息,并且会调用exit 1
function printInfo() {
    # ANSI 转义码颜色 绿色
    local message=$1
    printf "\e[32m%s\e[0m\n" "$message" # Info
}

function printError() {
    # ANSI 转义码颜色 红色
    local message=$1
    printf "\e[31m%s\e[0m\n" "错误:$message"
    # 退出程序
    exit 1
}

检查和下载hdc

if [ ! -f "${hdcPath}" ]; then
      # 不存在开始下载
      printInfo "首次需要下载hdc工具,2M"
       URL="https://gitee.com/zhiyangnie/install-shell/raw/master/tools.zip"
      # 下载到当前目录的 tools.zip
      # 使用 curl 下载
      curl -o  "$hdcZip" "$URL"
     if [ $? -eq 0 ]; then
          printInfo "下载成功,准备解压${hdcZip}..."
          # 解压ZIP文件
          unzip -o "$hdcZip" -d "${hdcTool}"
          # 检查解压是否成功
          if [ $? -eq 0 ]; then
              printInfo "${hdcZip}解压成功"
              # 删除zip
              rm "$hdcZip"
          else
              printError "${hdcZip} 解压失败,请手动解压"
          fi
      else
          printError "下载失败,请检查网络"
        fi
  fi

判断hdc是否可用以及连接手机数量

# 判断是否连接手机且仅有一个手机
devicesList=$(${hdcPath} list targets)

# 判断是否hdc 可用
if [ -z "$devicesList" ]; then
  # 开始下载zip
  print_error "hdc 不可用 ,请检查本目录是否存在 ${hdcPath}"
fi

# 判断是否连接手机,如果有 [Empty] 表明 一个手机也没连接
if [[ "$devicesList" == *"[Empty]"* ]]; then
    printError "未识别到手机,请连接手机,打开开发者选项和USB调试"
fi


# 判断连接手机的个数
deviceCount=$(${hdcPath} list targets | wc -l)
if [ "$deviceCount" -ne 1 ]; then
    printError "错误:连接的手机个数是 ${deviceCount} 个,请连接一个手机"
fi

printInfo "连接到手机,且仅有一个手机 ${devicesList}"

检测传入app的路径是否存是以.app结尾,并且文件存在

# 传过来的参数是 ,获取输入的 app 文件
appFile="$1"

# 判读传过来的路径文件是否以.app 结尾
if [[ ! "${appFile}" =~ .app ]]; then
    printError "请传入正确的包路径,文件要 .app 结尾"
fi

# 判断文件是否存在
if [ ! -e "$appFile" ]; then
    printError "不存在改文件 $appFile 。请确认"
fi

开始安装

#------------------------------开始安装----------------------------------
# 开始安装
printInfo "开始安装应用, ${bundleName}"
# 1.先kill当前app的进程
$hdcPath shell aa force-stop "$bundleName"

# hdc shell mkdir data/local/tmp/c3af89b189d2480395ce746621ce6385
# 2.创建随机文件夹
randomHex=$(xxd -l 16 -p /dev/urandom)
randomFile="data/local/tmp/$randomHex"
mkDirSuccess=$($hdcPath shell mkdir "$randomFile" 2>&1)
if [ -n "$mkDirSuccess" ]; then
  printError "手机中:随机创建文件夹 ${randomFile} 失败 , $mkDirSuccess"
else
  printInfo "手机中:创建随机文件夹 ${randomFile} 成功"
fi
# 3.解压.app中
# 在本地创建 tmp 临时文件夹
tmp="tmp"
# 存在先删除
if [ -d "${tmp}" ]; then
  rm -rf "$tmp"
fi
mkdir -p "$tmp"
# 解压.app ,使用 unUse 主要是 不想打印那么多的解压日志
unUse=$(unzip -o "$appFile" -d "$tmp")
if [ $? -eq 0 ]; then
    printInfo "解压app成功"
else
    printError "解压app失败,请传入正确的app。$appFile , "
fi


printInfo "遍历解压发送到 手机的$randomFile"
# 4.遍历 tmp 文件夹中的文件发送到 randomFile 中
for item in "${tmp}"/*; do
    if [ -f "$item" ]; then
      # 发送 以 .hsp 或 .hap 结尾。
      if [[ "$item" == *.hsp || "$item" == *.hap ]]; then
          $hdcPath file send "$item" "$randomFile"
      fi
    fi
done
printInfo "成功发送到 手机的$randomFile "

# 5. 使用 install
# hdc shell bm install -p data/local/tmp/c3af89b189d2480395ce746621ce6385

installStatus=$($hdcPath shell bm install -p "$randomFile" 2>&1)
if [[ "$installStatus" == *"successfully"* ]]; then
  printInfo "┌────────────────────────────────────────────────────────"
  printInfo "│ ✅ 安装成功 "
  printInfo "└────────────────────────────────────────────────────────"
  ${hdcPath} shell aa start -a "${entryAbility}" -b "$bundleName"
else
   printf "\e[31m%s\e[0m\n" "┌────────────────────────────────────────────────────────"
   printf "\e[31m%s\e[0m\n" "│❌ 安装错误"
   echo "$installStatus" | while IFS= read -r line; do
   printf "\e[31m%s\e[0m\n" "│${line}"
   done
   printf "\e[31m%s\e[0m\n" "│错误码:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/bm-tool-V5"
   printf "\e[31m%s\e[0m\n" "└────────────────────────────────────────────────────────"

fi

删除文件

# 删除 手机端的 $randomFile
${hdcPath} shell rm -rf "$randomFile"
# 删除本地的tmp文件夹
rm -rf "$tmp"

使用

进入install.sh父目录,执行

./install.sh [包的路径]

注意点

如果自己编写的的时候,如果执行 ./install.sh 的时候 报错 zsh: permission denied: ./install.sh,证明 这个shell脚本没有运行权限,可以使用ls -l install.sh 检测 权限,如果是 -rw-r--r-- 是没有权限的,然后执行 chmod +x install.sh ,就会加上权限,然后在执行ls -l install.sh ,可以看到-rwxr-xr-x,然后就可以 执行 ./install.sh了

地址

真机上安装需要正式手动签名

当我们签名之后的app,虽然这个app是签名的,但是里面的hap和hsp是没有签名的,所以我们要用脚本把hap和shp都要进行手动签名。一般是打包工具比如Jenkins 来做这个工作,因为正式签名不会让研发拿到,更不会让QA拿到。 需要手动签名,参考:

签名

appCertFile="sign/install.cer"
profileFile="sign/install.p7b"
keystoreFile="sign/install.p12"
keyAlias="zhiyang"
keyPwd="a123456A"
keystorePwd="a123456A"
java -jar tools/lib/hap-sign-tool.jar sign-app -keyAlias "${keyAlias}" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "${appCertFile}" -profileFile "${profileFile}" -inFile "${inputFile}" -keystoreFile "${keystoreFile}" -outFile "${outputFile}" -keyPwd "${keyPwd}" -keystorePwd "${keystorePwd}"

对hsp和hap签名

手动签名需要hap-sign-tool.jar,在command-line-tools/sdk/default/openharmony/toolchains/libs/hap-sign-tool.jar并且需要java文件,也都放到项目中了 在脚本中解压app之后,发送到手机之前,对所有的hsp和hap签名

image.png 代码如下

signHapAndHsp(){
  appCertFile="sign/install.cer"
  profileFile="sign/install.p7b"
  keystoreFile="sign/install.p12"
  keyAlias="zhiyang"
  keyPwd="a123456A"
  keystorePwd="a123456A"
  javaFile="lib/java"
  hapSignToolFile="lib/hap-sign-tool.jar"
  local item=$1
  #遍历文件夹,拿到所有的hsp和hap去签名
  for item in "${tmp}"/*; do
      if [ -f "$item" ]; then
        # 发送 以 .hsp 或 .hap 结尾。
        if [[ "$item" == *.hsp || "$item" == *.hap ]]; then
          # 开始签名
            local inputFile="${item}"
            outputFile=""
            if [[ "$inputFile" == *.hap ]]; then
              outputFile="${inputFile%.hap}-sign.hap"
            else
              outputFile="${inputFile%.hsp}-sign.hsp"
            fi
            signStatus=$(java -jar tools/lib/hap-sign-tool.jar sign-app -keyAlias "${keyAlias}" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "${appCertFile}" -profileFile "${profileFile}" -inFile "${inputFile}" -keystoreFile "${keystoreFile}" -outFile "${outputFile}" -keyPwd "${keyPwd}" -keystorePwd "${keystorePwd}" -signCode "1" 2>&1)
            signStatus=$(${javaFile} -jar "${hapSignToolFile}" sign-app -keyAlias "${keyAlias}" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "${appCertFile}" -profileFile "${profileFile}" -inFile "${inputFile}" -keystoreFile "${keystoreFile}" -outFile "${outputFile}" -keyPwd "${keyPwd}" -keystorePwd "${keystorePwd}" -signCode "1" 2>&1)
            if [[ "$signStatus" == *"failed"* || $signStatus == *"No such file or directory"* ]]; then
              printError "签名失败,${signStatus}"
            else
              printInfo "签名成功,${inputFile} , ${outputFile} , ${signStatus}"
              #删除以前未签名的
              rm -f "$inputFile"
            fi
        fi
      fi
  done
  printInfo "签名完成,${signStatus}"

}

注意点

如果报错是 code:9568322 error: signature verification failed due to not trusted app source.表明你的真机需要在添加你的设备,参考注册调试设备

image.png

这是真机的效果

在shell文件夹运行 ./install.sh ./InstallApp-default-signed.app

image.png

使用我demo,如果要写手动签名脚本,需要更换sign文件夹,自己去申请签名,并且要更换bundleName,因为你的设备并没有在我的华为Profile里面添加