用途:作为最基本的常规测试,全自动,用模拟器,4.4-7.0,开发包/混淆后的包会不会首屏闪退

会提到的小技术:

  1. 怎么全自动检测 APP 安装后首屏启动有没有闪退
  2. 怎么自动管理一堆 4.4-7.0 的模拟器, 然后拿个开发包逐个系统自动安装、启动、授权、甚至简单的交互

为什么做这个

Appetizer 质量监控对 APK 进行 DEX 代码插桩,然后插装包运行时能监控质量(crash, http,卡顿等等),DEX 代码插桩是一个很底层的技术,大家有个疑问,插桩后会不会闪退啊,你们怎么确保这个事情的。我们的流程是这样的:

  1. 我们的质量监控代码托管在 GitHub 上,凡是 commit 到 master(意味发版),触发 webhook 到我们的持续化集成服务器,自动 pull,build
  2. 我们保存了生产中的 APK 文件,会自动用这个新质量监控插件,全部插桩一遍,如果插桩失败(基本没有)会立即发邮件
  3. 我们的持续化集成服务器没有显示器(叫 headless server),会自动启动 4.4-7.0 模拟器 N 台,然后每一个 APK 进行安装、启动、授权,检测有没有首屏闪退
  4. 所有闪退或者其他问题,会自动出报告,搜集 logcat,apk 信息,发邮件,然后才由人工参与,这个全自动过程结果如下: 标题里,2017 年 5.13 做的在 api level 23 的模拟器上的结果,每行是一个 apk,成功的有截图 (png),或者无法安装(not_installed)或者闪退(bad),点击即是 log 我们用这套全自动流程回归,并确保我们的质量,目前 5.0+ 系统上闪退率为 0,这里看到很多无法安装是因为用了 x86 模拟器;4.4 比较挫,在修复;整个流程吃满 12 核 cpu,32G 内存的小服务器,需要 50 分钟左右

全自动管理一堆模拟器

一般大家都用 Android Studio,点击 AVD Manager,额然后。。。 不,我们要全自动化
看一下所有 Android 工具链都是可以命令行执行的,从安装系统镜像,到造一个模拟器配置,到创建 sd 卡,到启动 emulator,因为 Google 内部,也有一套类似我们的全自动模拟器 headless 链。但是!但是!这套命令行特别难用,每次都要看一遍文档,所以我做了个包装,代码我放在这里了:https://github.com/appetizerio/haem

Headless Android Emulator Manager (haem)
Terminology:  只要记住四个概念
target - something like android-19 android-23 目标平台,比如android-19, android-23
abi - x86 x86_64 armeabi-v7a or arm64-v8 ABI,就是x86还是ARM,就这四种选择
avd - an arbitrary name for an Android Virtual Device (AVD)  模拟器配置名
port - every emulator listens on a local port, which can be inferred from its adb serialno, e.g., emulator-5444 模拟器的端口,比如emulator-5444端口对应就是5444
Usage: haem.py [OPTIONS] COMMAND [ARGS]... 好了,很简单,命令,参数和git一样

Options:
  --help  Show this message and exit.

Commands:
  check  确认你的环境能否跑模拟器,没有参数
  create 创建一个模拟器配置 参数 AVD TARGET 见上
  delete 删除一个模拟器配置 参数  AVD
  install 安装一个TARGET
  list 列举现在已经创建的模拟器配置
  running 用adb devices报告现在运行的模拟器
  start 启动模拟器 参数AVD
  stop 停止模拟器 参数PORT

如果对细节感兴趣的,看看这个的代码就知道了,Python 的,都是 stackoverflow 上行之有效的方法(比如好好退出我找了一阵子)

有了这个,一个脚本启动一麻袋模拟器(要记得自己的内存)

怎么全自动检测 APP 安装后首屏启动有没有闪退

这个,有些坑

安装

adb 安装再普通不过了,注意了,>=21(5.0)的时候要动态授权,所以最好在安装时用上-g,代码如下,不翻译了

# d 是设备串号,outpath是apk路径
opts = "-g " if int(apilevel) >= 21 else "" # grant all runtime permissions for api>=21
install_info = subprocess.check_output('adb -s %s install %s%s' % (d, opts, outpath), shell=True)

安装可能失败,找 install_info 里面有没有 Failure

启动

清了 logcat 再启动,这个 Monkey 命令就帮你省了 activity 名(很多人会用 am)

# stage 2.2: clear logcat
subprocess.call('adb -s %s logcat -c' % (d, ), shell=True)
# launch it
subprocess.call("adb -s %s shell monkey -p %s 1" % (d, pkg), shell=True)

启动后 12 秒一般就够了,然后抓回来 logcat,-d打印到屏幕走人

logcat_info = subprocess.check_output('adb -s %s logcat -d' % (d, ), shell=True)

检测有没有闪退

先贴代码

# d是设备串号,pkg是apk的包名
wininfo = subprocess.check_output('adb -s %s shell dumpsys window windows' % (d, ), shell=True)
# stage 2.4: check if the app activity is focused
launched = False
for l in wininfo.splitlines():
    if 'mCurrentFocus' in l and 'Application Error' in l:
        launched = False
        break
    if 'mCurrentFocus' in l or 'mFocusedApp' in l:
        launched = launched or pkg in l

首先 dumpsys window windows这个命令会输出目前系统一层层的窗口层次(比如有 launcher,弹出框什么的),不详细解释了,有兴趣的可以去看一下,比较直观
重点是 mCurrentFocus(当前最上面的那个框)和 mFocusedApp(当前最上面的那个 APP)
有几种可能:

  1. APP 启动了,正常,那么应该顶层就是它这行应该都包含 APP 包名
  2. APP 启动了,闪退了,啥都没,回到桌面了,那么 mCurrentFocus 应该变成了桌面
  3. APP 启动了,碰到个坑,crash 了,弹框说"应用程序已经退出",这是个坑,这时两行还都是 APP 包名,和情况 1 差别是 mCurrentFocus 那行会告诉说有一个弹出框,名字叫 Application Error 因为安装时会授权 runtime permission,所以避免了很多首屏弹框要权限 好了,聪明的你整理一下逻辑就是上面那些了 针对 APP 还可以在启动后稍微自动点几下来到主界面,都是脚本活

后记


↙↙↙阅读原文可查看相关链接,并与作者交流