概述
现在鸿蒙手机安装harmony.app包有很大限制,他并不能像Android那样直接在手机上安装apk,也不像IOS可以传多个ipa,AppGallery Connect只能同时上传3个包,也就是说QA只能同时测3个不同的包,这样大大限制了开发效率和测试效率,大致解决方案
- 给QA开通git权限,下载 DevEco Studio ,让QA直接run对应的分支的包
- 直接让QA拿着手机让研发给跑包
- 上传AppGallery Connect,华为审核通过之后,直接扫码安装,缺点是:只能同时测试3个,还需要审核
但是上面这三种方案都对研发和QA不太友好,所以悄悄的写了个安装脚本,让QA直接运行即可,
无需配置hdc环境变量
无需下载DevEco Studio
像Android 使用 adb 安装apk一样
脚本下载hdc文件只有1.7M
如果需要手动签名额外需要hap-sign-tool.jar和java两个文件【不建议在脚本中手动签名,请在打包服务器中,比如jenkins】
,当然了测试签名还是可以放在脚本中的
编写 Shell 脚本,先上图
成功 错误
首先在 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
咱们可以根据上面的流程大致写一下
脚本方案
- 检测hdc文件是否存在,不存在使用cur下载
- 检测是否连接手机,并且只有一个手机
- 检测传入app的路径是否存是以.app结尾,并且文件存在
- 创建手机端临时目录
- 解压.app到电脑端,复制里面的所有hsp和hap到 临时目录,
如果需要手动签名可以在这一步去签名
- 安装临时目录的所有文件
- 删除手机临时目录以及电脑端解压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}"
- Store file:选择密钥库文件,文件后缀为.p12,该文件为生成密钥和证书请求文件中生成的.p12文件。
- Store password:输入密钥库密码,该密码与生成密钥和证书请求文件中填写的密钥库密码保持一致。
- Key alias:输入密钥的别名信息,与生成密钥和证书请求文件中填写的别名保持一致。
- Key password:输入密钥的密码,与生成密钥和证书请求文件中填写的Store Password保持一致。
- Sign alg:签名算法,固定为SHA256withECDSA。
- Profile file:选择申请调试证书和调试Profile文件中生成的Profile文件,文件后缀为.p7b。
- Certpath file:选择申请调试证书和调试Profile文件中生成的数字证书文件,文件后缀为.cer。
对hsp和hap签名
手动签名需要hap-sign-tool.jar,在command-line-tools/sdk/default/openharmony/toolchains/libs/hap-sign-tool.jar并且需要java文件,也都放到项目中了 在脚本中解压app之后,发送到手机之前,对所有的hsp和hap签名
代码如下
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.表明你的真机需要在添加你的设备,参考注册调试设备
这是真机的效果
在shell文件夹运行 ./install.sh ./InstallApp-default-signed.app