简介:这是一份面向Windows平台的Android命令行工具包,专为不需要安装完整Android Studio的开发者设计,可解决SDK组件下载、应用编译、设备连接与调试等常用开发问题。压缩包共108个文件,其中95个为jar格式的工具组件,10个为bat命令入口,另有1个properties配置、1个readme说明和1个txt文档,整体体积136.41MB。工具集内置sdkmanager,能够按需下载系统镜像、开发库、API文档与模拟器等组件,避免一次性拉取整个IDE;同时包含avdmanager、apkanalyzer、lint等命令,用于管理虚拟设备、解析APK安装包和检查代码质量。借助这些工具,即使在没有图形界面的服务器或CI环境中,也可以通过命令行完成从项目编译、单元测试到打包部署的完整流程。该资源已有544人学习下载,适合习惯命令行操作、需要维护多个项目或希望实现自动化构建的开发者,也可作为Android开发的轻量级入门选型。
1. 别急着装 Android Studio:这个 zip 就能把 SDK 管起来
在 Windows 上做 Android 工程的自动化打包时,我第一件事不是装 Android Studio,而是先找一个 zip:Android 命令行工具(commandlinetools-win-13114758-latest.zip)。这个名字看着像某个不起眼的下载产物,实际它是 Android SDK 的官方命令行工具集,里面打包了 sdkmanager、avdmanager、apkanalyzer 等一整套东西。只要把它装好,你就能在没有 IDE 的情况下完成 SDK 组件安装、模拟器创建、构建环境配置,甚至跑起完整的 CI 流水线。适合三类人:想在服务器或虚拟机里做安卓构建的工程师、写脚本批量管理多台机器 SDK 的人,以及单纯受不了 Android Studio 那股笨重劲儿的老手。它解决的问题很朴素:安卓开发工具链在命令行下能不能独立活。
2. 解压与目录结构:cmdline-tools 为什么必须套一层 latest
2.1 先看懂压缩包里装的是什么
拿到 commandlinetools-win-13114758-latest.zip 之后,别急着双击解压。这个 zip 解压出来是一个名为 cmdline-tools 的文件夹,里面是 bin、lib 和几个子目录。bin 下有 sdkmanager.bat、avdmanager.bat、apkanalyzer.bat 这些批处理入口;lib 里是工具运行依赖的 Java 类库。也就是说,这套工具本质是一个 Java 程序,Windows 上靠 .bat 包装成命令行工具。文件名里的 13114758 是这次构建的构建号,装好之后没有任何命令会专门引用它,真正重要的是后面要讲的目录摆放规则。
很多人把 zip 解压后直接扔到某个目录,然后进 cmdline-tools\bin 跑 sdkmanager --version,发现能输出版本号,就以为装好了。等到用 sdkmanager 去安装 platforms;android-34 之类的包时,要么静默失败,要么把包装到了莫名其妙的位置。原因是 sdkmanager 并不是单纯的一个命令,它会基于自身所在路径反推 SDK 根目录,再往根目录下的 platforms、build-tools、system-images 等子目录写入内容。如果目录层级不符合它预期的结构,它就找不准家在哪。
2.2 标准目录结构:一层 latest 是关键
常见做法是先把 zip 解压,再把里面的 cmdline-tools 整个移动到一个干净的 SDK 根目录下的 cmdline-tools\latest 子目录里。假设我把 SDK 根目录定在 D:\Android\Sdk,那么最终结构应该是:
D:\Android\Sdk └── cmdline-tools └── latest ├── bin │ ├── sdkmanager.bat │ └── avdmanager.bat └── lib
注意这个 latest 是怎么来的:它不是 zip 里自带的,而是你在 cmdline-tools 下面新建一个名为 latest 的子目录,把解压出来的文件全部放进去。之后的 platform-tools、platforms、build-tools 这些目录,会由 sdkmanager 自动生成在 D:\Android\Sdk 下面,形成完整的 SDK 布局。zip 里解压出来的那个 cmdline-tools 文件夹,本身千万不要改名为其他名字,sdkmanager 对目录名有既定预期。
为了快速操作,我给你一段我在 PowerShell 里常用的初始化命令:
# 定义你的 SDK 根目录,选一个干净的盘符路径,别用 C:\Windows 这类系统目录 $sdkRoot = "D:\Android\Sdk" # 解压 zip 到临时目录。这里假设 zip 已下载到 D:\Downloads Expand-Archive -Path "D:\Downloads\commandlinetools-win-13114758-latest.zip" -DestinationPath "$env:TEMP\cmdline-tools-extract" # 建立标准目录结构:cmdline-tools/latest New-Item -ItemType Directory -Force -Path "$sdkRoot\cmdline-tools\latest" # 把解压出来的 cmdline-tools 里的内容复制到 latest 目录 Copy-Item -Path "$env:TEMP\cmdline-tools-extract\cmdline-tools\*" -Destination "$sdkRoot\cmdline-tools\latest" -Recurse -Force这段逻辑的核心是两条:先创建 cmdline-tools\latest 骨架,再把 zip 解压出的内容整体塞进 latest。很多教程只给一句“解压后改名”,没说清楚为什么改成 latest,导致我见过有人把整个 D:\Android\Sdk\cmdline-tools 目录结构搞成 D:\Android\Sdk\cmdline-tools\cmdline-tools\bin 这种嵌套。原因就是解压后没有把内层目录展开,直接把外层整个目录当成工具目录了。判断方法很简单:看 sdkmanager.bat 的完整路径,最后两级必须分别是 cmdline-tools 和 latest。
2.3 配置环境变量与最小验证
目录放好之后,要让工具在任何终端里都能直接调用。需要配置两个东西:一个是 SDK 根目录的环境变量,另一个是工具 bin 目录的 PATH。sdkmanager 在寻找 SDK 位置时会参考 ANDROID_HOME 或 ANDROID_SDK_ROOT,两个变量指向同一个路径不会冲突,但别一个配一个不配。
# 以当前用户级别设置环境变量,避免权限问题 [Environment]::SetEnvironmentVariable("ANDROID_HOME", "D:\Android\Sdk", "User") [Environment]::SetEnvironmentVariable("ANDROID_SDK_ROOT", "D:\Android\Sdk", "User") # 把 cmdline-tools/latest/bin 加进 PATH $userPath = [Environment]::GetEnvironmentVariable("Path", "User") [Environment]::SetEnvironmentVariable("Path", "$userPath;D:\Android\Sdk\cmdline-tools\latest", "User")设置完环境变量,注意新开的终端才会继承这些值。验证是否装对,最直接的方式是关闭旧终端,重新开一个 PowerShell 窗口:
# 新终端里执行版本检查 sdkmanager --version如果输出类似一长串版本号,说明工具本身已经能跑。如果提示找不到命令,多半是 PATH 没刷新,或者你还在用之前没关掉的旧终端。如果提示找不到 Java 运行时,那是 JAVA_HOME 没配或者 JDK 版本不对,sdkmanager 这个 Java 程序先得有 JVM 才能干活。我建议用主流的 LTS 版本 JDK,太老或太新的 JDK 都有可能让它在启动阶段报奇怪异常。
3. sdkmanager:包管理命令与 3 个高频参数
3.1 先看有哪些包可以装:sdkmanager --list
装好之后,第一件事是拉取远端仓库里可用的组件清单。命令是 sdkmanager --list,它会从 Google 的仓库拉取数据,输出当前已安装的组件、可用的组件和可用的更新三部分。输出行非常多,直接看会花眼。我在 Windows 上通常会过滤一下,只看自己想要的那一类包:
# 列出所有平台的 SDK 版本。PowerShell 下用 Select-String 过滤 sdkmanager --list | Select-String "platforms;android-" # 只看构建工具版本 sdkmanager --list | Select-String "build-tools;" # 只看模拟器相关镜像(system-images) sdkmanager --list | Select-String "system-images;"如果列表拉不下来,先检查网络能通到仓库,再考虑是不是被代理环境挡住了。sdkmanager 默认会读系统代理设置吗?不一定,它更信任自己命令行里给的代理参数。公司环境普遍用内网代理时,要用显式的代理参数才能拉回列表,这个细节最容易让人误以为命令坏了。
3.2 安装与卸载:包名格式决定成败
sdkmanager 的安装命令核心就一句话:把包名作为参数传给 install。包名格式是分号分隔的层级路径,比如 platforms;android-34 表示 34 这个 API 级别的平台组件,build-tools;34.0.0 表示构建工具的具体版本,emulator、platform-tools 这种不带分号的属于顶层包。
# 安装一组常用组件。每个包名都必须用双引号包住,原因看下方说明 sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" "emulator" # 如果 SDK 不在默认位置,显式指定根目录。这个参数我几乎每次都会带 sdkmanager --sdk_root=D:\Android\Sdk "platform-tools"包名必须加引号,是因为分号在 PowerShell 里是语句分隔符,不加引号会被拆成多条语句,命令直接报错。在 cmd 里不加引号倒不会拆语句,但分号也可能引发其他解析问题,统一加引号最保险。--sdk_root 这个参数值得养成习惯:当你机器上有多个 SDK 目录时,sdkmanager 可能找到的不是你想要的那个,显式指定根目录能避免很多灵异问题。安装完成后,可以去 D:\Android\Sdk 下看是不是多了 platforms、build-tools 这些目录。
卸载同理,把 install 换成 uninstall,包名要跟安装时完全一致。我只在清理旧版本构建工具时用这个功能,比如一台机器上堆积了五六个 build-tools 版本,磁盘吃紧时会统一清一轮。
3.3 接受许可证:Windows 上没有 yes 的坑
装完组件第一次使用时,很多命令会要求接受许可证。常见做法是看到一堆 license 提示后,手动敲 y 回车。但在一台无人值守的机器上,手动一条条敲会崩溃。网上常见方案是执行 yes | sdkmanager --licenses,问题是 Windows 的 cmd 和 PowerShell 里根本没有 yes 这个命令。只有在 Git Bash 等环境里才有 yes 可用,直接照抄这条命令,在 PowerShell 里会得到“无法识别 yes”的错误。
我常用的做法是循环多次,自动把 y 喂给 sdkmanager:
# 自动接受所有许可证。原理是重复把 y 管道给 licenses 命令 # 之所以循环,是因为某些版本一次只能接受一部分 license 文件 for ($i = 0; $i -lt 20; $i++) { "y" | sdkmanager --licenses }这段逻辑里循环 20 次不是玄学:license 文件可能有好几个,比如 androidx 的 license 和其他组件的 license 分开存放,第一次接受完一部分,第二次再跑还会问你剩下的,循环能确保全部吃进去。如果你只想接受特定组件的 license,更精准的方式是先安装组件再接受,未安装的组件根本不会出现在 licenses 列表里。所以别在还没装任何包的时候去执行 licenses 命令,你会疑惑为什么它瞬间结束、什么都没干。
3.4 代理与更新通道:两个容易被忽略的参数
经过代理上网的环境,还要学会给 sdkmanager 显式传代理。命令行的形式和 Maven 那套很像,但参数名不一样,记错了就白搭。正确参数是这三件套:
# http 代理示例。host 和 port 按实际内网代理配置填写 sdkmanager --proxy=http --proxy_host=10.0.0.1 --proxy_port=8080 --list另一个参数是 --channel,它控制拉取哪个发布通道的组件。默认值是 0,表示稳定版通道。有的场景需要提前用某些 preview 功能,可以临时加上 --channel=1 或者 --channel=3 来拉试版组件。我的建议是日常构建固定用默认通道,只有确认要尝鲜某个新 API 时才临时指定通道,避免因为组件版本跳变导致构建环境不可复现。
4. avdmanager + emulator:无界面建一台模拟器
4.1 先装系统镜像:system-images 包的层级逻辑
要让模拟器能启动,光有 emulator 这个顶层组件不够,还必须装对应 API 级别的系统镜像。系统镜像的包名比前几个都长,比如 system-images;android-34;google_apis;x86_64。这个包名的四个分段各有含义:system-images 是类型,android-34 是 API 级别,google_apis 表示带 Google API 的镜像,x86_64 表示镜像架构。
# 装 34 级别的带 Google API 的 x86_64 镜像 # 适合跑在 Windows 的 Intel/AMD 桌面 CPU 上 sdkmanager "system-images;android-34;google_apis;x86_64"架构字段要留意:如果宿主机是 Apple Silicon,需要的可能不是 x86_64,但 Windows 场景基本都围绕 x86_64 展开。还有一个坑是 android 这个分段不带引号时会被 Shell 当作变量或其他含义处理,所以包名一律加双引号。镜像体积不小,下载时如果网络不稳定,sdkmanager 卡在某个百分比不动,我一般先看网络,再看仓库连通性,最后考虑是不是代理参数漏了。
4.2 创建 AVD:avdmanager 的最小参数组合
镜像装好之后,用 avdmanager 创建虚拟设备。这个命令在 cmdline-tools 的 bin 目录下,和 sdkmanager 是同一个家。创建 AVD 的核心参数是 -n 指定名字,-k 指定系统镜像的完整包名,-d 指定设备定义。这里 -d 可选,但你不指定的话,创建出来的设备屏幕分辨率、内存这些配置会比较随意,后面启动时容易出幺蛾子。
# 创建一个名为 ci_test 的 AVD,基于之前装好的镜像 # --device 参数可以用 pixel 这类通用设备定义,具体可用值可通过 list device 查看 echo no | avdmanager create avd -n ci_test -k "system-images;android-34;google_apis;x86_64" -d pixel命令里的 echo no 是为了回答 avdmanager 创建过程中会问的一个问题:是否创建自定义硬件配置文件。如果你不回答,它在某些版本会卡在读输入这里。直接回 no,让它用默认硬件配置创建即可,因为后面启动模拟器时还可以用命令行参数微调硬件表现。创建完成后,AVD 会落到用户目录下的 .android\avd 里,文件名和 -n 指定的名字一致。如果你打算把 AVD 放到指定位置,可以设置 ANDROID_AVD_HOME 环境变量,这个在 CI 场景下非常有用,能避免默认路径被系统清理或权限问题挡住。
4.3 从命令行启动模拟器:无头模式必配参数
AVD 建好不一定立刻能启动。最常见的问题出现在图形环境缺失的机器上,比如一台只有命令行界面的 Windows Server。模拟器默认会窗口启动,这时候必须改用 headless 模式。我平时启动模拟器的完整命令长这样:
# 无窗口、无音频、关启动动画、强制软件渲染 # 这一套参数在 CI 服务器上能跑得比较稳 emulator -avd ci_test -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect逐个解释这几个参数的用途。-no-window 让模拟器不创建图形窗口,适合飞在后台跑。-no-audio 关掉音频子系统,避免一些服务器上没有声卡导致初始化失败。-no-boot-anim 能缩短启动动画占用的等待时间。-gpu swiftshader_indirect 强制用软件渲染,在没有独立显卡的机器上不至于因为 GPU 驱动问题直接崩。最后一个参数比较关键:不指定的话,模拟器会尝试用宿主机的 GPU 加速,服务器上经常因为找不到可用 GPU 而终止。
启动起来之后还不能马上跑用例,要等系统真正完成 boot。只看终端输出不可靠,得轮询系统属性:
# 先等设备被 adb 识别到 adb wait-for-device # 轮询 sys.boot_completed,直到输出 1 为止 # 这里用循环等待,最长等 180 秒 for ($i = 0; $i -lt 180; $i++) { $bootCompleted = (adb shell getprop sys.boot_completed).Trim() if ($bootCompleted -eq "1") { Write-Host "Boot finished" break } Start-Sleep -Seconds 2 }这个等待逻辑要配合 adb wait-for-device 使用。wait-for-device 只能保证设备已经连上 adb,不能保证系统启动完毕。boot_completed 这个系统属性变成 1 才代表 Launcher 已经起来,可以开始装应用、跑测试。如果你在这个阶段用 adb install,大概率会报设备未就绪的错误。轮询间隔我一般设 2 秒,最长 3 分钟,超过就报超时退出,避免 CI 任务无限挂住。
5. 避坑排查:Windows 上装这套工具最常见的 5 个坑
5.1 命令能跑,但包装到了奇怪的位置
现象:sdkmanager --version 正常输出,执行 sdkmanager "platforms;android-34" 也不报错,但在 D:\Android\Sdk 下死活找不到 platforms 目录,反而在 zip 解压后的某个临时路径下生成了新目录。原因:目录结构不符合 cmdline-tools/latest 的预期,sdkmanager 把临时解压目录当成了 SDK 根。解决:严格按上一章的标准结构重新摆放目录,然后用 --sdk_root 显式指到 D:\Android\Sdk 再装一次。这是我见过最多人踩的坑,基本都是一上来就跳过目录结构直接开跑。
5.2 分号引发的命令截断
现象:在 PowerShell 里执行 sdkmanager platforms;android-34,报“表达式或语句中包含意外的标记”之类错误。原因:PowerShell 用分号分隔多条语句,包名里的分号被解释成了语句边界。解决:包名必须用双引号完整包住。另外一个相关现象是包名里的引号在 cmd 与 PowerShell 之间复制粘贴时会变成弯引号,导致参数解析断裂。解决:统一在 PowerShell 里手动输入直引号。
5.3 licenses 接受不彻底
现象:执行完 sdkmanager --licenses,回头构建时依然报“LICENSE NOT ACCEPTED”,或者 Jenkins 日志里提示某些组件的 license 文件缺失。原因:一是 Windows 上没有 yes 命令,网上的管道写法不适用;二是 license 文件不止一个,一条命令只接受了部分。解决:在 PowerShell 里用循环把 y 管道给命令,循环次数宁多勿少,接受完之后再去对应目录检查 license 文件是否存在。顺带一提,构建工具读取 license 的路径在 SDK 根目录下,如果你手工创建了一个假的 license 文件,格式不对一样会被拒绝。
5.4 模拟器进程在,但设备一直 offline
现象:emulator 进程在任务管理器里能看到,adb devices 却显示 offline,或者反复出现 device unauthorized。原因:这台机器的虚拟化支持没开,或者 Windows Hypervisor Platform 没启用。模拟器在没有硬件加速时启动很慢,甚至卡在早期初始化阶段。解决:先去 BIOS 确认 CPU 虚拟化已开启,再在 Windows 功能里勾选 Hyper-V 相关组件。CI 环境常见的是你自己能跑、装了 CI 服务的宿主机跑不了,多半是服务运行在没有虚拟化的容器里。
5.5 改了环境变量但服务进程找不到命令
现象:手动终端里 sdkmanager 正常,但 CI Agent 启动的构建任务报“sdkmanager 不是内部或外部命令”。原因:CI Agent 作为服务安装时继承了 System 级别的环境变量,你在 User 级别新加的 PATH 它根本看不到;或者 Agent 启动后环境变化没有重新加载。解决:在 CI 任务里显式设置工具路径,不要依赖宿主机的环境变量。做法是在任务脚本开头重新定义 PATH 和 ANDROID_HOME,保证每次构建从干净且固定的环境开始。
6. 进阶:把整套安装固化成一条命令
这一章聊怎么把这套手工过程变成幂等脚本。所谓幂等,就是同一台机器上跑十遍脚本,结果应该和跑一遍一样,不会因为重复创建目录或重复安装组件而报错。做法是写一个 PowerShell 脚本,内部先判断目录结构是否已存在,不存在才解压;再判断组件是否已安装,未安装才执行 sdkmanager。
# install-android-sdk.ps1 param( [string]$SdkRoot = "D:\Android\Sdk", [string]$ZipPath = "D:\Downloads\commandlinetools-win-13114758-latest.zip" ) $toolsDir = Join-Path $SdkRoot "cmdline-tools\latest" if (-not (Test-Path "$toolsDir\bin\sdkmanager.bat")) { Write-Host "Installing cmdline-tools..." $extractDir = Join-Path $env:TEMP "cmdline-tools-extract" Expand-Archive -Path $ZipPath -DestinationPath $extractDir -Force New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null Copy-Item -Path "$extractDir\cmdline-tools\*" -Destination $toolsDir -Recurse -Force } # 核心组件清单:这些包名写在一个文本文件里,方便跨机器维护 $packages = Get-Content "sdk-packages.txt" foreach ($pkg in $packages) { Write-Host "Installing $pkg" sdkmanager --sdk_root=$SdkRoot $pkg } for ($i = 0; $i -lt 20; $i++) { "y" | sdkmanager --sdk_root=$SdkRoot --licenses }配套的 sdk-packages.txt 里,我会按固定版本写上要安装的组件。这里的关键是版本号要锁定,比如 platforms;android-34 和 build-tools;34.0.0,而不是用 anything 之类通配。锁定版本的好处是半年后重新跑脚本,装的还是同一套东西,不会有“上次构建没问题,今天突然报依赖缺失”的困扰。这个教训是我自己翻车翻出来的:有一年我不指定 build-tools 版本,让 sdkmanager 装默认最新,结果某次远程仓库更新后,老项目突然打包失败,排查半天才意识到是新版构建工具改了旧版配置的默认行为。
另一个值得养成习惯的技巧是定期把 sdk-packages.txt 纳入版本控制,跟着项目仓库走。新机器加入集群时,一条命令拉代码、一条命令跑脚本,SDK 环境就能复现。相比每次都手动敲安装命令,这套方式更接近“基础设施即代码”的思路,也让接手的人少被玄学折磨。希望帮到你。
本文还有配套的精品资源,点击获取