简介:这份资源是面向 Linux 平台的 Android 命令行工具包,适合不想安装完整 Android Studio、却需要构建与调试 Android 应用的开发者,尤其是习惯命令行工作流的中高级用户。压缩包共 101 个文件,以 91 个 jar 库为核心,辅以 sdkmanager、avdmanager、apkanalyzer、lint、retrace、profgen、screenshot2 等可执行脚本及说明文档,整体约 127.32MB,覆盖 SDK 包管理、虚拟设备创建、APK 分析、代码检查与混淆还原等常用环节。借助其中的 sdkmanager,可按需下载其他 SDK 组件,灵活搭建轻量开发环境。目前已有 225 人学习下载,可作为搭建持续集成、自动化构建或服务器端 Android 工具链的基础素材,帮助读者快速获得一套可直接调用的命令行工具集,减少对图形界面的依赖。
1. 从一串版本号说起:commandlinetools-linux-9477386-latest.zip 到底是什么
如果你在 CI 流水线里见过commandlinetools-linux-9477386-latest.zip这个文件名,大概率是在配 Android 构建环境时被它卡过一次。它不是一个普通压缩包,而是 Android SDK 的命令行工具集(Command-line Tools),版本号 9477386 对应的是某个具体的构建号,latest只是官方下载页的固定命名习惯。这个包解决的核心问题是:在没有 Android Studio 图形界面的 Linux 服务器、容器或远程开发机上,只靠命令行完成 SDK 组件安装、许可证接受、构建工具拉取和 APK 打包。适合谁?适合需要把 Android 构建塞进 Docker 镜像、Jenkins/GitLab CI 流水线,或者单纯不想在服务器上装几个 G 图形界面的后端和运维同学。它不包含模拟器,也不包含 IDE,只给你sdkmanager、avdmanager、adb、fastboot这几个关键命令。
2. 拆开这个压缩包:目录结构、组件与选型理由
2.1 解压后到底有什么
把commandlinetools-linux-9477386-latest.zip解压到任意目录,你会看到cmdline-tools/下有一个bin/和一个lib/。bin/里就是sdkmanager、avdmanager、lint、retrace等脚本,lib/里是它们依赖的 jar 包。注意,这个包本身不包含platform-tools(也就是adb),也不包含任何 Android 平台或构建工具,那些都需要通过sdkmanager联网下载。所以解压完直接敲adb是找不到命令的,这是新手第一个容易懵的点。
2.2 为什么不用 Android Studio 自带的 SDK
Android Studio 安装时会顺带装一份 SDK,但那份 SDK 的路径通常绑定在用户目录下,且体积巨大。在 CI 场景里,你不可能在每个构建节点装一个完整的 IDE。命令行工具包只有几十 MB,解压即用,配合sdkmanager按需拉取指定版本的platforms和build-tools,能把镜像体积压到最小。另一个理由是版本可控:9477386这个构建号对应的工具版本是固定的,不会因为某天官方更新latest导致流水线突然行为不一致。我一般会在 Dockerfile 里显式写死这个文件名,而不是用latest去下载,避免缓存失效和版本漂移。
2.3 版本号 9477386 意味着什么
这个数字是 Google 内部的构建 ID,不是语义化版本。它对应的是 Command-line Tools 的某个具体修订版。不同构建号之间,sdkmanager的默认行为、支持的 Java 版本、以及--sdk_root参数的处理方式可能有细微差别。比如较老的构建号在 Java 17 下会报NoClassDefFoundError,而较新的构建号已经适配。如果你在 CI 里遇到莫名其妙的java.lang.NoSuchMethodError,先确认你用的构建号是否和当前 JDK 匹配。常见做法是:JDK 17 配较新的构建号,JDK 8 配老构建号,不要混搭。
2.4 最小可用目录布局
sdkmanager对目录结构有硬性要求。它期望在<sdk_root>/cmdline-tools/latest/bin/sdkmanager这个路径下找到自己。如果你直接把解压出来的cmdline-tools放在<sdk_root>/cmdline-tools,它会报Could not determine SDK root。正确的做法是手动建一层latest目录,把解压出来的内容挪进去。这个坑几乎每个人都会踩一次,下面用命令演示。
# 假设下载到 /tmp,准备安装到 /opt/android-sdk mkdir -p /opt/android-sdk/cmdline-tools cd /tmp unzip commandlinetools-linux-9477386-latest.zip -d /tmp/cmdline-tools-temp # 关键一步:把解压出的 cmdline-tools 内容挪到 latest 下 mv /tmp/cmdline-tools-temp/cmdline-tools /opt/android-sdk/cmdline-tools/latest # 验证目录结构 ls /opt/android-sdk/cmdline-tools/latest/bin/sdkmanager逻辑说明:sdkmanager脚本内部会通过自身路径反推sdk_root,它假设自己的父目录名是latest,再往上一级才是cmdline-tools,再往上一级才是 SDK 根。如果你不建latest这一层,它反推出来的根目录就是错的。参数上,-d指定解压目标目录,mv那一步不能省。执行完ls能看到sdkmanager文件存在,才算布局正确。
3. 在无界面 Linux 上跑通 SDK 安装与许可证接受
3.1 配置环境变量与 PATH
装好目录结构后,需要把sdkmanager和后续的platform-tools加进 PATH。我一般会在~/.bashrc或 CI 的 env 里写三行:ANDROID_SDK_ROOT、ANDROID_HOME、以及把cmdline-tools/latest/bin和platform-tools追加到 PATH。ANDROID_SDK_ROOT和ANDROID_HOME现在建议都设成同一个值,因为不同工具读的变量名不一样,只设一个容易在某些脚本里翻车。
export ANDROID_SDK_ROOT=/opt/android-sdk export ANDROID_HOME=/opt/android-sdk export PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin export PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools逻辑说明:ANDROID_SDK_ROOT是较新工具链推荐使用的变量,ANDROID_HOME是历史遗留但很多 Gradle 插件仍在读。两个都设,避免兼容性问题。PATH 里先加cmdline-tools再加platform-tools,因为platform-tools里才有adb,而cmdline-tools里才有sdkmanager,顺序不影响功能,但分开写更清晰。
3.2 用 sdkmanager 安装指定组件
sdkmanager的基本用法是sdkmanager "包名"。包名有固定格式,比如platforms;android-34、build-tools;34.0.0、platform-tools。注意分号在 shell 里需要引号包起来,否则会被当成命令分隔符。安装前可以先--list看有哪些可用包,但--list会联网拉取仓库元数据,首次执行可能较慢。
# 查看已安装和可用包 sdkmanager --list # 安装平台、构建工具和 platform-tools sdkmanager "platforms;android-34" "build-tools;34.0.0" "platform-tools" # 指定 sdk_root(当环境变量没设时) sdkmanager --sdk_root=/opt/android-sdk "platforms;android-34"逻辑说明:--list会输出两大块,Installed packages和Available Packages。安装时如果包名拼错,它会提示Warning: Failed to find package。--sdk_root参数在环境变量已设的情况下可以省略,但在 Docker 构建的某些阶段环境变量还没生效,显式传更稳妥。安装完成后,platforms/android-34和build-tools/34.0.0目录会出现。
3.3 非交互式接受许可证
CI 环境没有 TTY,sdkmanager安装时如果遇到未接受的许可证会卡住等待输入。解决办法是用yes管道把y灌进去。这是最常用也最稳的方式,比--licenses交互式接受更适合脚本。
yes | sdkmanager --licenses # 或者安装时直接带 yes yes | sdkmanager "platforms;android-34" "build-tools;34.0.0"逻辑说明:yes命令会无限输出y,管道给sdkmanager后,所有许可证提示都会被自动确认。--licenses子命令会遍历所有需要接受的许可证并逐个提示。注意,如果sdkmanager因为网络问题提前退出,yes会收到SIGPIPE但不会报错,所以执行完最好再跑一次sdkmanager --list确认目标包已安装。
3.4 验证安装结果
安装完不能只看命令没报错,要实际验证目录和命令。adb version能输出版本号,说明platform-tools就位;ls $ANDROID_SDK_ROOT/platforms能看到android-34,说明平台就位。这一步在 Docker 构建里建议写成RUN指令,构建失败能立刻暴露。
adb version ls $ANDROID_SDK_ROOT/platforms ls $ANDROID_SDK_ROOT/build-tools逻辑说明:adb version读的是platform-tools里的二进制,如果 PATH 没配好会报command not found。ls两个目录是确认sdkmanager真的把文件写到了预期位置。有些 CI 缓存会把旧版本目录带进来,导致ls看到多个版本,这时要确认 Gradle 里指定的compileSdk和buildToolsVersion与实际安装的一致。
4. 避坑与排查:那些让流水线红掉的细节
4.1 报错 Could not determine SDK root
现象:执行sdkmanager时直接报Could not determine SDK root,命令退出。原因:解压后没有把cmdline-tools内容放到latest子目录下,sdkmanager反推根目录失败。解决:按 2.4 节的目录布局重新整理,确保路径是<sdk_root>/cmdline-tools/latest/bin/sdkmanager。如果已经装错,删掉cmdline-tools重新解压挪动即可。
4.2 Java 版本不匹配导致 NoClassDefFoundError
现象:sdkmanager启动时报NoClassDefFoundError或UnsupportedClassVersionError。原因:构建号 9477386 对应的工具版本对 JDK 有要求,用 JDK 8 跑较新工具或反过来都可能出问题。解决:先java -version确认当前 JDK,然后要么换 JDK,要么换一个匹配的构建号。CI 里建议把 JDK 版本和命令行工具构建号一起锁在 Dockerfile 里,不要依赖宿主机默认 Java。
4.3 许可证接受后仍然报 license not accepted
现象:明明跑过yes | sdkmanager --licenses,Gradle 构建时仍报You have not accepted the license agreements。原因:许可证文件写在$ANDROID_SDK_ROOT/licenses/下,如果构建时用的ANDROID_SDK_ROOT和接受许可证时的根目录不是同一个,Gradle 读不到。解决:确认接受许可证和构建使用同一个 SDK 根路径,检查licenses目录下是否有android-sdk-license文件。Docker 多阶段构建时尤其容易把许可证留在上一个阶段。
4.4 网络超时导致包下载一半失败
现象:sdkmanager下载到一半卡住或报Connection timed out。原因:默认仓库地址在部分网络环境下不稳定,或者 CI 节点没有外网访问权限。解决:配置 HTTP 代理(如果环境允许),或者提前把需要的包缓存到本地目录,用--sdk_root指向缓存。另一个办法是在 Dockerfile 里把sdkmanager安装步骤放在网络稳定的构建阶段,并把结果提交进镜像层。
4.5 目录权限导致写入失败
现象:sdkmanager报Permission denied写$ANDROID_SDK_ROOT。原因:Docker 里用非 root 用户运行,但 SDK 目录是 root 创建的。解决:在 Dockerfile 里chown -R给运行用户,或者把 SDK 装到用户有写权限的路径下。CI 里如果缓存目录由不同用户创建,也会出现类似问题,清理缓存或统一用户即可。
5. 把它塞进 Docker 与 CI:一个可复用的最小构建镜像
5.1 Dockerfile 分层策略
把命令行工具、SDK 组件、许可证接受分成三层,利用 Docker 缓存。命令行工具包很少变,放最底层;SDK 组件按需安装,放中间层;许可证接受放上层。这样改应用代码时不会触发 SDK 重装。
FROM ubuntu:22.04 # 安装基础依赖 RUN apt-get update && apt-get install -y openjdk-17-jdk unzip wget && rm -rf /var/lib/apt/lists/* # 下载并解压命令行工具 ENV ANDROID_SDK_ROOT=/opt/android-sdk RUN mkdir -p $ANDROID_SDK_ROOT/cmdline-tools && \ wget -q https://example.com/commandlinetools-linux-9477386-latest.zip -O /tmp/tools.zip && \ unzip -q /tmp/tools.zip -d /tmp/tools && \ mv /tmp/tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest && \ rm -rf /tmp/tools /tmp/tools.zip ENV PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin # 安装 SDK 组件并接受许可证 RUN yes | sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" && \ yes | sdkmanager --licenses ENV PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools逻辑说明:wget那行的 URL 需要替换成实际可访问的下载地址,这里用占位符表示。mv到latest是必须的。yes | sdkmanager两行分别安装组件和接受许可证,顺序不能反,因为安装时可能触发新的许可证提示。ENV PATH分两次设置,确保adb和sdkmanager都在 PATH 里。
5.2 CI 缓存与增量更新
在 GitLab CI 或 GitHub Actions 里,可以把$ANDROID_SDK_ROOT目录缓存起来,key 用构建号加组件列表的哈希。这样后续流水线不用重复下载。但要注意,缓存命中时sdkmanager可能因为目录已存在而跳过安装,导致许可证文件缺失。稳妥做法是缓存后仍然跑一次yes | sdkmanager --licenses,成本很低。
# 以 GitLab CI 为例的缓存片段 cache: key: android-sdk-9477386 paths: - /opt/android-sdk/逻辑说明:key里带上构建号,避免不同工具版本共用缓存。paths指向 SDK 根目录。缓存恢复后,构建脚本里仍然要执行许可证接受和组件校验,不能假设缓存一定完整。
5.3 验证镜像是否真的可用
构建完镜像后,不要只看docker build成功。跑一个临时容器,执行sdkmanager --list和adb version,确认输出正常。如果sdkmanager --list报错,说明目录布局或 Java 环境有问题;如果adb version找不到命令,说明 PATH 没生效。
docker run --rm my-android-builder sdkmanager --list docker run --rm my-android-builder adb version逻辑说明:--rm让容器退出后自动删除,避免残留。两条命令分别验证命令行工具和平台工具。如果第一条报Could not determine SDK root,回去检查latest目录;如果第二条报command not found,检查ENV PATH是否包含platform-tools。
5.4 一个容易忽略的细节:时区和 locale
sdkmanager在某些 locale 下会输出乱码或解析异常,尤其是LANG没设成 UTF-8 时。Docker 基础镜像默认可能是POSIX,建议在 Dockerfile 里设ENV LANG=C.UTF-8。时区不影响 SDK 安装,但会影响构建日志时间戳,CI 里统一设成 UTC 更利于排查。
ENV LANG=C.UTF-8 ENV TZ=UTC逻辑说明:LANG影响 Java 的默认字符集,C.UTF-8在大多数 Linux 发行版上可用。TZ设成 UTC 避免日志时间跳变。这两行放在 Dockerfile 靠前位置,利用缓存。
5.5 进阶技巧:用 sdkmanager 的 --channel 控制更新通道
sdkmanager默认走稳定通道,但可以通过--channel=3切到 canary 通道拿预览版工具。生产环境不建议用 canary,但如果你需要某个只在预览通道修复的 bug,可以临时切。切完记得切回来,否则后续安装的组件可能不稳定。
sdkmanager --channel=3 --list sdkmanager --channel=0 "platforms;android-34"逻辑说明:--channel=0是稳定通道,--channel=3是 canary。--list配合 channel 可以看到不同通道的包版本差异。这个参数在排查“为什么官方文档说的包我这儿没有”时很有用,因为有些包只在特定通道发布。
我自己在流水线里踩过最疼的一次,是缓存了 SDK 目录但没缓存许可证文件,结果构建到一半报 license 未接受,排查了半小时才定位到缓存 key 没包含许可证状态。从那以后,我习惯在 Dockerfile 里把许可证接受和组件安装放在同一层,并且每次构建都重新跑一遍yes | sdkmanager --licenses,多花几秒换来确定性。希望帮到你。
本文还有配套的精品资源,点击获取