ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Mac上Android命令行工具:CI与脚本化实战

Mac上Android命令行工具:CI与脚本化实战 简介面向 macOS 系统的 Android 官方命令行工具包省去完整 Android Studio 的安装步骤适合在终端或 CI 环境中完成 SDK 管理的开发者。工具包含 sdkmanager 与 avdmanager用于下载 SDK 组件、创建虚拟设备还提供 apkanalyzer、lint、retrace、profgen、resourceshrinker、screenshot2 等辅助命令覆盖 APK 深度分析、静态代码检查、混淆堆栈还原、性能配置生成、资源压缩与设备截图录屏等多种场景工具链较为完整。压缩包共 104 个文件以 93 个 jar 工具库为核心内置 Kotlin 编译器、R8 混淆器等依赖另附可执行脚本、readme 与 properties 配置说明整体体积 146.49MB解压后即可着手配置轻量级 Android 开发环境。目前已有 199 人浏览学习适合希望摆脱图形界面束缚、在服务器或 CI 流水线中完成构建和检查任务的中高级开发者。1. 不装 Android StudioMac 上的 Android 命令行工具才是 CI 与脚本化首选很多人想到 Android 开发环境第一反应就是去下载 Android Studio。实际场景里如果只是希望 Jenkins 上多一台 macOS 打包机或者写个脚本定时拉取最新 SDK、解析 APK 的权限变化Android Studio 的 GUI 反而碍事。Google 单独放出的 commandlinetools-mac-11076708_latest.zip 就是为这种无界面诉求准备的sdkmanager、avdmanager、apkanalyzer、lint、R8 都收在一个包内不需要 IDE 也能完成从 SDK 安装到模拟器创建的完整流程。这套工具同时也随 Android Studio 发行只是 IDE 把命令藏在菜单背后自己下载 zip 反而能对版本做锁点CI 上不会因为 GUI 升级而漂移。下面按我这次在 Mac 上实际拆包和跑命令的顺序记录新手可以直接照做老手可以重点看目录层级和 JDK 版本带来的坑。2. commandlinetools-mac-11076708 解包之后目录层级、PATH 配置与内部 jar 分工2.1 目录层级为什么必须是 cmdline-tools/latest下载回来的 zip 解压后第一层不是 bin而是一个cmdline-tools目录。如果直接把它丢到~/Library/Android/sdk/cmdline-tools/让脚本变成cmdline-tools/bin/sdkmanagersdkmanager 经常会报 Cannot locate SDK 或无法识别自身版本。新版命令行工具要求最终的目录结构是$ANDROID_HOME/cmdline-tools/latest/bin也就是把解压出来的cmdline-tools整体改名为latest再放到 SDK 根目录下的cmdline-tools文件夹里。这个命名看起来奇怪但它是工具内部通过相对路径查找lib、source.properties的基础。我在 Mac 上的标准操作是mkdir -p ~/Library/Android/sdk/cmdline-tools unzip commandlinetools-mac-11076708_latest.zip -d /tmp/android-cmdline-tools mv /tmp/android-cmdline-tools/cmdline-tools ~/Library/Android/sdk/cmdline-tools/latest第一行先建好 SDK 根目录第二行解压到临时目录第三行把解压出来的cmdline-tools移动到目标位置并命名为latest。移动完成后~/Library/Android/sdk就是 ANDROID_HOMEcmdline-tools/latest/bin下面才有 sdkmanager 和 apkanalyzer。需要注意Mac 上的unzip解压时如果目标临时目录已存在同名目录建议先清空或换一个目录名避免把旧文件混进去这类问题很难通过报错定位因为症状通常是工具能启动但版本列表残缺。source.properties文件里记录了Pkg.Revision11076708这也解释了 zip 文件名中间那一串数字的来历。如果你同时装了好几份命令行工具靠这个文件就能快速确认当前latest指向哪个版本。2.2 bin 下有哪几个可直接执行的工具将 PATH 配好之前可以先看一眼bin目录里有哪些可执行脚本。下表列的是这个 zip 自带的常用命令它们都是 shell 包了一层 Java 调用的入口。命令作用是否需要先装 SDK 组件sdkmanager安装、卸载、查询 SDK 包否avdmanager创建、删除、管理安卓虚拟设备需要 system imageapkanalyzer读取 APK 元数据、权限、dex 统计信息否lint对 APK 或工程执行静态检查视工程结构而定retrace反混淆 R8/ProGuard 生成的 mapping 文件需要 mapping 文件screenshot2连接设备截图替代旧版 screenshot需要 adb 或设备其中screenshot2在 CI 场景里很实用可以不装额外工具截取模拟器画面retrace则适合线上问题追查。注意avdmanager和sdkmanager都需要一个正常的 JDK 环境apkanalyzer也一样它们本质上是java -jar的封装。2.3 lib 下那些 jar 是干什么的lib 目录不像 bin 那么直观但里面几个 jar 值得认识排错时会有方向感。r8.jar是新版 D8/R8 合并后的编译器/混淆器Android Studio 里的 shrink 功能底层就是它kotlin-compiler-mvn.jar和kotlin-reflect-1.9.0.jar说明工具链内部有模块是用 Kotlin 1.9 构建的所以你的系统里最好有 JDK 17否则这些 jar 解析不了高版本字节码bcprov-jdk15on-1.67.jar是 Bouncy Castle 加密库SDK 安装时的签名和证书校验往往会用到guava-31.1-jre.jar是 sdkmanager 的常见依赖proto.jar则参与 protobuf 解析。不要手动 classpath 加载这些 jar它们内部版本是配好的一整套手动替换任何其中一个都可能导致NoSuchMethodError。2.4 配置 PATH 并确认版本为了让之后的命令少打长路径我会在~/.zshrc里加两行导出export ANDROID_HOME$HOME/Library/Android/sdk export PATH$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATHplatform-tools随后会由 sdkmanager 安装提前写进 PATH 不影响当前 shell只是让以后 adb 可用。保存后执行source ~/.zshrc然后运行command -v sdkmanager确认路径如果输出的是~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager说明环境变量已经生效。若你使用 bash 而非 zsh把同样的内容加到~/.bash_profile即可核心逻辑相同。提示不要在 PATH 里同时放多个cmdline-tools目录。Mac 上如果之前装过 Android Studio~/Library/Android/sdk/cmdline-tools可能已存在直接覆盖 latest 前先确认没有别的项目在依赖它。3. sdkmanager 安装 platform-tools、build-tools参数、许可与 JDK 坑3.1 --sdk_root 指定安装位置sdkmanager 本质上是一个包管理器它读取远端仓库的 XML 元数据再根据包标识符下载对应组件例如platforms;android-34。默认情况下它使用环境变量ANDROID_HOME作为安装目录但我一般会显式加--sdk_root因为在不同 Mac 或 CI 节点上环境变量未必都设置在同一路径显式传参可以减少一次猜路径的过程。命令顺序上先查再装比较稳妥# 先观察远程仓库有哪些目标平台 sdkmanager --list | grep platforms;android-3[34] # 安装基础组件platform-tools 提供 adbplatforms 提供编译用 jarbuild-tools 提供打包工具 sdkmanager --sdk_root$ANDROID_HOME --install platform-tools platforms;android-34 build-tools;34.0.0grep里的platforms;android-3[34]可以匹配android-33和android-34两类平台包方便在安装前确认远程仓库里到底有哪些版本。第二行同时安装platform-tools、platforms;android-34、build-tools;34.0.0其中 platform-tools 提供 adb 和 fastbootplatforms 提供 android.jarbuild-tools 提供 aapt2、zipalign 等编译工具。如果项目 compileSdk 是 35把标识符改成platforms;android-35即可不需要再回 Android Studio 里点一遍。3.2 安装常用 SDK 包除了上面三个基础组件还有几个包会在后面的模拟器流程里用到可以一起写入安装命令# emulator 是模拟器本体system-images 是对应架构的系统镜像 sdkmanager --sdk_root$ANDROID_HOME --install emulator system-images;android-34;google_apis;arm64-v8a这里emulator是模拟器本体system-images;android-34;google_apis;arm64-v8a是带有 Google API 的系统镜像。如果你在 Apple Silicon Mac 上跑镜像标识符末尾一定要选arm64-v8aIntel Mac 则用x86_64后缀。选错了镜像模拟器启动时会直接提示x86_64 emulation currently requires hardware acceleration这不是配置问题而是镜像和宿主架构不匹配。3.3 自动接受许可证的 CI 写法--install在第一次执行时往往会停下来等用户接受 license交互式环境里手动按y就行但 Jenkins 或 GitHub Actions 这些非交互环境会卡住。常见做法是用yes管道把 y 持续喂给 sdkmanager# 非交互环境必须用 yes 管道否则进程会一直挂在等待输入 yes | sdkmanager --sdk_root$ANDROID_HOME --licenses--licenses会输出所有未接受的协议yes命令不断输出 y最终 SDK 根目录下会生成licenses/android-sdk-license等文件。之后再次执行--install就不会再触发 license 提示。如果你只想单独接受某个包对应的协议sdkmanager --licenses同样适用在脚本里只需确认返回码为 0不要依靠终端打印内容判断。下面是 sdkmanager 几个最常用参数的速查表参数作用典型用法--list列出所有可安装包sdkmanager --list--install安装一个或多个包sdkmanager --install platform-tools--uninstall卸载包sdkmanager --uninstall build-tools;34.0.0--licenses接受或查看许可yes | sdkmanager --licenses--sdk_root指定 SDK 根路径sdkmanager --sdk_root$ANDROID_HOME --install ...--channel指定渠道3 为预发布sdkmanager --channel3 --list--channel3在需要提前验证 alpha/beta 工具链时会用到正式开发不建议开因为预发布包之间兼容性不够稳定。3.4 版本不匹配时先查 JAVA_HOME在 Mac 上使用 commandlinetools 最容易踩的坑是 JDK 版本不正确。commandlinetools 11076708 对应的构建要求是 JDK 17如果你机器的默认 java 是 JDK 11 或 JDK 8运行 sdkmanager 会看到类似UnsupportedClassVersionError或直接提示Error: Could not create the Java Virtual Machine。这时候先不要怀疑命令写错执行下面的命令检查当前环境java -version /usr/libexec/java_home -Vjava -version看默认版本/usr/libexec/java_home -V列出 Mac 上所有已安装的 JDK并显示它们的安装路径。如果系统里有 JDK 17就把它设为当前 shell 的 JAVA_HOMEexport JAVA_HOME$(/usr/libexec/java_home -v 17) export PATH$JAVA_HOME/bin:$PATH确认java -version输出 17 之后再跑sdkmanager --version正常会打印类似11076708的版本号。这里要注意/usr/libexec/java_home -v 17要求本机确实安装了 17 的 JDK否则会报错找不到匹配版本没有 17 就先用 Homebrew 安装 OpenJDK 17再回到这条命令。4. apkanalyzer 分析 APK权限审查、DEX 方法数和脚本化输出4.1 基本信息命令SDK 装好、build-tools 也就绪后拿到一个 APK 可以先跑 apkanalyzer 看摘要。它读取 APK 的二进制 AndroidManifest 和 dex 文件输出比 aapt2 更接近人可读的格式很适合写进自动化脚本。基本命令如下cd $ANDROID_HOME/cmdline-tools/latest/bin # 摘要包名、版本、最低和目标 Android 版本 ./apkanalyzer apk summary app-release.apk # 直接输出 APK 字节数适合体积监控 ./apkanalyzer apk file-size app-release.apk # 完整权限列表发版前必看 ./apkanalyzer manifest permissions app-release.apk第一条输出应用的包名、版本号、最低和目标 Android 版本第二条直接给出 APK 在磁盘上的大小第三条把所有声明的权限列出来。实际工作中我经常把这三条合起来写成一个检查任务每次发版前自动跑一遍比对上一版的权限是否有新增系统权限增加往往意味着合规流程也要更新这比等到上架审核时被驳回要省事得多。4.2 权限提取与白名单脚本如果要检查某次构建是否意外引入了敏感权限可以用下面的小脚本# 遍历 release 目录下所有 APK导出权限列表并做 diff 前的基础清洗 for apk in app/build/outputs/apk/release/*.apk; do echo $apk apkanalyzer manifest permissions $apk | grep -oE android\.permission\.[A-Z_] | sort -u done逻辑是先遍历指定目录下所有 release APK再用apkanalyzer manifest permissions导出权限列表grep -oE把android.permission.INTERNET这种完整权限名提取出来sort -u去重后输出。这样即使一次构建生成了多个渠道包也能快速看出渠道间的权限差异。更严格的做法是维护一个白名单文件然后在脚本里做差集比较发现白名单之外的新权限就返回非零状态码让 CI 任务失败。权限名在不同版本的系统里可能有同名但不同含义的情况比如READ_PHONE_NUMBERS是 Android 11 后才细分的因此建议把targetSdkVersion和权限输出一起保存排查时才不会因为 API 级别差异误判。4.3 用 dex packages 评估方法数APK 体积之外方法数也是老 Android 项目需要关注的数据。DEX 格式对 method 引用数有 65536 的限制执行下面的命令可以看到 dex 里的包级统计# --defined-only 只看当前 APK 定义的类和方法不看外部引用 ./apkanalyzer dex packages --defined-only app-release.apk | head -50--defined-only只统计当前 APK 定义的类和方法不含外部依赖引用输出的表格里通常会有包名、类数、方法数、引用数这几列。head -50是防止碎片化小包太多刷屏。如果某个包方法数异常高例如第三方 SDK 动辄几万就要评估是否需要 R8 做裁剪或者将 debug/release 构建产物分开查看r8.jar就在命令行工具内部配合minifyEnabled true后方法数通常会明显下降。数值只是一个结果真正的瓶颈往往在依赖树里那个最大的库上所以构建时可以顺带开--stacktrace看看依赖解析日志。4.4 apkanalyzer 与 aapt2 的边界很多人会纠结 apkanalyzer 和 build-tools 里的 aapt2 有什么区别。我的使用习惯是只是读取 APK 元数据、权限、dex 统计用 apkanalyzer输出更稳定且适合脚本处理如果要修改资源、生成 R.java 或者执行资源链接才需要 aapt2。前者偏“读”后者偏“写”在 CI 流水线里两个工具常常会同时出现。下面这张表是我常用的 apkanalyzer 子命令子命令返回内容常见用途apk summary应用 id、版本、SDK 版本发版检查apk file-sizeAPK 字节数体积趋势监控manifest permissions完整权限列表权限合规检查manifest printAndroidManifest XML 文本查看组件声明dex packages包、类、方法统计方法数评估dex referencesdex 外部引用定位 multidex 原因如果 manifest print 输出中的 XML 不缩进可以再管道给xmllint --format -格式化Mac 上自带 xmllint不需要额外安装。分析线上 APK 时建议使用与 release 包完全一致的签名包upload 包经过二次签名后元数据可能和原始包有细微差别。5. avdmanager 创建 AVD 并用无窗口模拟器做冒烟验证5.1 选择合适的 system image模拟器相关组件可以由 sdkmanager 安装AVD 的创建和销毁则交给 avdmanager。先查一下远程仓库里有哪些镜像避免手拼标识符# 先确认远程镜像标识符再确认本地设备定义 sdkmanager --list | grep system-images;android-34 avdmanager list device第一条命令列出 Android 34 的所有系统镜像第二条列出 avdmanager 内置的预设备定义比如 pixel_5、pixel_6。Apple Silicon Mac 上选google_apis;arm64-v8aIntel Mac 选google_apis;x86_64。default或aosp_atd镜像少了 Google API 的某些测试依赖做普通冒烟测试无所谓要跑 Play 服务相关用例就选google_apis。5.2 创建 AVD 并静默启动镜像安装完成后创建 AVD 的命令如下# echo no 表示不创建自定义硬件配置文件全部用默认硬件参数 echo no | avdmanager create avd \ -n ci_avd \ -k system-images;android-34;google_apis;arm64-v8a \ -d pixel_5echo no是为了回答“是否创建自定义硬件配置文件”一般不需要自定义选择默认硬件参数就行。创建好后启动模拟器时我会加几个无界面参数# 无窗口模式关闭音频和启动动画强制软件渲染 $ANDROID_HOME/emulator/emulator -avd ci_avd -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -no-window让模拟器在后台运行适合没有显示器的 CI 节点-no-audio关闭音频-no-boot-anim缩短启动动画时间-gpu swiftshader_indirect强制软件渲染避免 Mac 上 GPU 驱动导致的闪烁或黑屏。如果本地想窗口运行去掉-no-window和-gpu swiftshader_indirect即可。5.3 用 sys.boot_completed 做启动探活模拟器进程是后台起的真正的启动完成不能靠在终端里看输出需要轮询系统属性adb wait-for-device # boot_completed 变成 1 才说明系统完全启动否则继续等 until [ $(adb shell getprop sys.boot_completed | tr -d \r) 1 ]; do sleep 2 done adb shell input keyevent 82adb wait-for-device只能保证 adb 能看到设备不能保证系统已经启动完成sys.boot_completed变成1才说明桌面已经就绪。tr -d \r是去掉 adb 输出里的回车符防止判断条件永不成立。最后input keyevent 82模拟按下菜单键用来解锁部分模拟器启动后的锁屏。此时用下面的命令确认当前前台 Activityadb shell dumpsys activity activities | grep mResumedActivitydumpsys activity activities输出 Activity 栈信息mResumedActivity这一行会带有当前正在前台运行的组件名。如果看到的是Launcher而不是你要测试的应用说明模拟器启动正常但应用还没被拉起需要补一步adb shell am start -n 包名/Activity全路径。sys.boot_completed到达 1 只是一个门槛真正的冒烟验证还是要以应用能否切到前台为准。本文还有配套的精品资源点击获取
返回列表