安卓研发部门,会使用持续集成。开发和测试经常需要下载并安装CI中的apk,进行功能对比或验证问题。

**看起来简单的事情,往往挺麻烦的**

场景重现

让我们复现一下场景:

我们点击了ci中的apk链接,下载apk到Download目录。作为开发,我们一般会使用adb install来安装下载的apk。所以,我们打开了terminal, 输入adb install, 再输入apk路径,或打开findle把apk拖到terminal中作为路径。这时候,惊喜来了,由于debug版本apk已经存在,我们遇到了install错误。我们打算手动卸载手机里面的apk了,我们发现,从桌面删除不了应用,需要进入设置-应用管理里面删除。删除完成后,总算成功安装了,却发现,由于下载目录里面文件比较多,导致我们安装了错误的apk文件,然后,我们打算用adb uninstall来卸载,却发现忘了包名的全称。好不容易安装成功了,我们还需要打开手机的应用列表,从中找到app图标来点击。

一个看似很简单的事情,却消耗了我们不少时间,关键,还影响了心情

Don’t be a hater

记住遇到问题,不要抱怨,不要躲避
如果我们有这样一个shell脚本,它能:

  1. 按时间排序输出下载目录中的apk列表
  2. 让用户选择要安装的apk
  3. 安装对应的apk
  4. 自动启动应用

这个问题就迎刃而解了

分步实现

输出下载目录中的apk列表

1
2
3
4
5
6
7
8
9
10
11
12
function list_all_apk() {
echo "found apk file in ""${dir}"
i=0
for file in `ls -t /Users/xiaoxinpeng/Downloads/`
do
if [ -n "`echo $file | sed -n /^.*\\.apk\$/p`" ];then
echo " [${i}] ""${file}"
apks[i]="${file}"
let i=i+1
fi
done
}

这里牵涉到的知识点:

  1. ls -t 按时间排序返回文件名数组
  2. 使用for…in…do…done关键字对该列表迭代
  3. 判断字符串非空使用 -n
  4. 使用sed匹配文件名满足apk后缀
  5. shell中数组的使用let赋值

让用户选择要安装的apk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function let_user_select_apk_to_install() {
read ANSWER
case $ANSWER in
"")
echo "un selected, reinstall [0] apk"
reinstall_lastest_apk ${apks} $1
;;
*)
echo
apk_file="/Users/xiaoxinpeng/Downloads/${apks[${ANSWER}]}"
install_apk "${apk_file}"
;;
esac
}

涉及的知识点:

  1. 使用read读取用户输入
  2. shell中case的用法,注意*)相当于default
  3. shell中数组的值${apks[${ANSWER}]}

安装apk

安装apk比较简单,注意这里是先使用adb uninstall进行旧apk卸载 ,另外使用echo不加任何参数,可以输出空行

1
2
3
4
5
6
7
8
9
10
11
function install_apk() {
echo "uninstall old apk ..."
adb uninstall "com.sds.android.ttpod"

echo
echo "install new apk: ""$1"
adb install "${apk_file}"
echo "finish..."

#adb shell am start -n "com.sds.android.ttpod/com.ali.music.funzone.app.SplashActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
}

另一个技巧,是使用command来检测用户是否安装了某个程序。这里用来做adb是否安装的检测:

1
2
3
function check_has_adb() {
command -v adb >/dev/null 2>&1 || { echo >&2 "require adb in android sdk, but it's not in you PATH. Aborting."; exit 2;}
}

上面这代码看起来简单,其实涉及了两个重要知识点

  1. 管道的使用。这个||管道是仅当前面的表达式返回非零(失败)退出值时,才处理跟在此符号后的列表。 管道&&仅当前面的管道返回退出值零 (成功) 时,才处理跟在此符号后的列表. 管道|前面的命令会作为后面命令的输入。
  2. 输出重定向。这里的2>&1是将标准出错重定向到标准输出,这里的标准输出已经重定向到了dev/null文件,即将标准出错也输出到dev/null文件

启动应用

1
2
3
4
function start_app() {
sleep 1
adb shell am start -n com.sds.android.ttpod/com.ali.music.entertainment.presentation.view.splash.SplashActivity
}

使用adb shell am来启动应用

总结

这个shell,实现了自动安装用户选择的apk到手机,里面包含了非常多的知识点,比如

case语法、if判断字符串非空、sed匹配、||管道、输出重定向、数组、数值赋值、判断软件是否存在