Unity Android SDK包列表更新失败的根源与离线解决方案
1. 这个报错不是你的代码问题而是Unity在“假装能连上Android SDK”实际却卡在了网络握手的第一步“Unity 打包失败 Failed to update Android SDK package list”——这行红色报错几乎每个做过Android平台发布的Unity开发者都见过。它不挑项目大小不管你是刚建的空场景还是百万行代码的MMO它不看Unity版本2019.4、2021.3、2022.3.25f1甚至最新的2023.2 LTS全都照常报。最气人的是它从不告诉你具体哪一步失败了只甩出一句冷冰冰的“Failed to update Android SDK package list”然后打包流程戛然而止控制台日志里翻来覆去就这几行像卡带的老式录音机。我第一次遇到它是在给一个教育类App做热更模块时本地调试一切正常一到CI流水线打包就崩。当时以为是JDK版本不对换了OpenJDK 11、17、21全试了一遍又怀疑是Gradle配置问题把gradleTemplate.properties重写三遍最后甚至重装了整个Android SDK——结果第二天早上打开Unity它又稳稳地躺在Build Settings窗口下面红得刺眼。后来我才明白这个报错根本不是构建逻辑出错而是Unity在启动Android SDK Manager时试图连接Google官方的SDK仓库https://dl.google.com/android/repository/repository2-1.xml获取最新包列表但这个HTTP请求在绝大多数国内开发环境中连DNS解析都过不去。它不是“下载失败”而是“连门都没摸到”。关键词“Unity”“Android SDK”“package list”“Failed to update”指向的不是一个编译错误而是一个环境级的网络可达性断层。它影响的不是你写的C#脚本而是Unity Editor底层调用sdkmanager命令时的网络握手能力。所以所有“检查Player Settings”“确认JDK路径”“清理Library文件夹”的常规操作对它统统无效。真正要解决的是让Unity Editor在不依赖Google服务器的前提下也能拿到一份可信、完整、可安装的Android SDK包清单。这不是绕过限制而是重建一套本地可控的SDK元数据分发机制。适合谁来看如果你正被这个问题卡住无论你是刚入门的Unity新手还是带团队的TA负责人无论你用的是个人版还是企业版Unity无论你是在Windows、macOS还是Linux上开发——只要你的开发环境在国内且没配过全局代理或代理对Unity Editor进程无效这篇就是为你写的。它不讲虚的只讲怎么在不改一行游戏代码的前提下让打包流程重新跑通。2. 根源不在Unity而在Android SDK Manager的“信任链”设计缺陷要真正解决这个问题必须先搞懂Unity到底在执行什么。很多人误以为Unity自己实现了SDK管理逻辑其实不然。从Unity 2018.3开始Unity就彻底弃用了自研的SDK更新器转而直接调用Android官方提供的sdkmanager命令行工具位于Android/sdk/tools/bin/sdkmanager。而sdkmanager本身又严重依赖一个叫repository2-1.xml的元数据文件——它就像Android SDK世界的“黄页”里面列出了所有可用的platforms、build-tools、platform-tools、ndk、emulator等组件的名称、版本号、下载地址、校验码和依赖关系。关键点来了sdkmanager默认只认一个URL就是Google托管的https://dl.google.com/android/repository/repository2-1.xml。它不会自动 fallback 到镜像也不会读取本地缓存的旧版XML。每次Unity执行Build时只要检测到本地SDK缺少某个必要组件比如platforms;android-33或build-tools;33.0.2就会强制触发sdkmanager --list命令而该命令的第一步就是联网拉取这份XML。如果这一步超时或返回非200状态码比如DNS污染导致返回404或TCP连接被RSTsdkmanager就直接抛出Failed to update Android SDK package list后续流程全部中止。提示你可以手动验证这一点。打开终端cd到你的Android SDK根目录执行./tools/bin/sdkmanager --list --verbose。如果看到类似[----] 33% Fetching remote repository...然后卡住超过60秒或者直接报Connection timed out那就100%确认是网络问题。注意这个命令和Unity Editor是否运行无关它是独立进程。为什么Unity不提供“离线模式”开关因为Google官方sdkmanager压根没设计这个功能。它的设计理念是“永远在线”假设开发者始终能直连Google服务。Unity作为调用方只是忠实地把错误原样抛出并未做任何封装或兜底。这就造成了一个事实Unity Editor的Android打包能力在国内网络环境下默认处于“半残废”状态。不是Unity不行而是它依赖的上游工具在特定网络条件下天生失能。更麻烦的是这个失败是静默的。Unity不会在Console里打印完整的HTTP错误栈也不会提示你“请检查网络连接”。它只在Editor Log~/Library/Logs/Unity/Editor.log或C:\Users\user\AppData\Local\Unity\Editor\Editor.log里留下一行Failed to update Android SDK package list然后继续往下走直到某处因缺少组件而真正崩溃——比如报Unable to find android.jar这时你才意识到根源早在几分钟前就埋下了。3. 真正有效的解决方案只有两种离线预加载清单或劫持远程请求市面上流传的所谓“解决方案”90%都是治标不治本。比如“换用旧版Unity”2017.x确实用自研更新器但早已停止支持且无法兼容新API“关闭Auto-update SDK”这只是禁用自动安装不解决清单拉取失败的问题“手动下载zip包再install”sdkmanager --install命令依然需要先读取XML才能知道该装哪个zip“设置HTTP_PROXY环境变量”对Unity Editor主进程无效且多数公司内网禁止代理出口。真正能一劳永逸解决问题的只有两条技术路径且都已被大量一线团队验证过3.1 方案A离线预加载——用已知可用的XML文件覆盖默认行为这是最稳妥、最可控的方式。核心思想是既然无法实时拉取那就提前准备好一份“干净、完整、可验证”的repository2-1.xml并告诉sdkmanager“别上网了就用这个”。第一步获取一份可靠的离线XML。最推荐的方式是从一台能稳定访问Google服务的机器比如海外云服务器、或同事的MacBook上导出。操作如下# 在能联网的机器上进入Android SDK目录 cd /path/to/android-sdk # 执行一次成功更新确保XML已缓存 ./tools/bin/sdkmanager --update # 查找缓存的XML位置通常在 ~/.android/repositories.cfg 指定的路径下 # 或直接搜索 find . -name repository2-1.xml -type f | head -n 1 # 输出类似./.android/repositories/repository2-1.xml把这个XML文件拷贝出来重命名为repository2-1.xml.bak并用文本编辑器打开检查其内容是否包含大量remotePackage节点正常应有数百个。确认无误后把它放到你的开发机上比如放在D:\UnitySDKFix\Windows或~/UnitySDKFix/macOS。第二步让sdkmanager强制使用这个文件。Unity没有提供GUI开关但sdkmanager本身支持--repository参数# 测试命令不通过Unity直接验证 ./tools/bin/sdkmanager --repository D:\UnitySDKFix\repository2-1.xml.bak --list如果能看到完整的包列表输出说明路径正确、XML有效。第三步让Unity Editor在每次调用sdkmanager时自动带上这个参数。Unity不支持全局配置但可以通过修改Unity的内部启动脚本实现。以Windows为例找到Unity安装目录下的Editor\Data\PlaybackEngines\AndroidPlayer\Tools\ConsolidatedSDKTools\bin\sdkmanager.bat备份原文件编辑该bat文件找到类似%JAVA_EXE% %DEFAULT_JVM_OPTS% %JAVA_OPTS% %SDKMANAGER_OPTS% -classpath %~dp0\..\lib\*这一长串命令在-classpath之前插入--repository D:\UnitySDKFix\repository2-1.xml.bakmacOS用户则修改对应路径下的sdkmanagershell脚本无.bat后缀在exec java命令前加入相同参数。注意此修改需针对每个Unity版本单独进行。如果你团队用多个Unity版本建议写个Python脚本批量处理或在CI流水线中用sed命令注入。3.2 方案B本地HTTP劫持——搭建轻量级代理服务拦截请求这是更“优雅”但也稍复杂的方式适合有运维基础或团队统一部署需求的场景。原理是在本地启动一个微型HTTP服务器当sdkmanager尝试请求https://dl.google.com/android/repository/repository2-1.xml时我们把它302重定向到本地文件或直接返回预存的XML内容。我推荐使用Python的http.server模块无需额外安装快速搭建# save as local_sdk_proxy.py import http.server import socketserver import os # 指向你准备好的repository2-1.xml文件 XML_PATH os.path.abspath(repository2-1.xml) class SDKProxyHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): if self.path /android/repository/repository2-1.xml: self.send_response(200) self.send_header(Content-type, application/xml) self.end_headers() with open(XML_PATH, rb) as f: self.wfile.write(f.read()) else: # 其他请求全部404避免干扰 self.send_error(404) if __name__ __main__: port 8080 with socketserver.TCPServer((, port), SDKProxyHandler) as httpd: print(fLocal SDK Proxy running on http://localhost:{port}) httpd.serve_forever()保存后执行python local_sdk_proxy.py服务即启动。然后你需要让sdkmanager把请求发给这个本地服务而不是Google。方法是设置环境变量# Windows PowerShell $env:ANDROID_SDK_ROOTD:\android-sdk $env:JAVA_HOMEC:\Program Files\Java\jdk-17 # 关键让sdkmanager认为dl.google.com指向本地 Add-Content $env:WINDIR\System32\drivers\etc\hosts n127.0.0.1 dl.google.com # macOS / Linux echo 127.0.0.1 dl.google.com | sudo tee -a /etc/hosts警告修改hosts文件会影响系统全局务必在测试完成后及时清理。更安全的做法是使用--no_https参数配合自签名证书但这会显著增加复杂度对大多数团队不推荐。两种方案对比维度离线预加载方案A本地HTTP劫持方案B实施难度★☆☆☆☆纯文件操作★★★☆☆需启服务改hosts维护成本★☆☆☆☆XML每年更新1-2次★★☆☆☆服务需常驻端口可能冲突团队适配性★★★★☆每人改自己Unity安装★★★☆☆需统一部署proxy服务安全性★★★★★完全离线无网络暴露★★★☆☆本地端口监听需防火墙策略CI/CD友好度★★★★☆脚本化后一键注入★★☆☆☆需在CI节点部署服务我个人在三个不同规模的项目中都首选方案A。原因很简单它不引入任何新服务、不修改系统配置、不依赖网络状态且一旦配置好就彻底告别这个报错。而方案B虽然看起来“更现代”但在实际落地时经常因为CI节点权限不足、Docker容器网络隔离、或安全组策略拦截而导致失败。4. 实操避坑指南那些文档里绝不会写的细节与血泪教训光知道方案还不够真正卡住开发者的永远是那些藏在犄角旮旯里的细节。以下是我在过去三年里踩过的、修过的、被客户凌晨三点电话call醒后反复验证过的12个关键点按优先级排序4.1 XML文件编码必须是UTF-8且不能带BOM这是最高频的失败原因。很多Windows用户用记事本保存XML会默认加上UTF-8 BOMByte Order Mark而sdkmanager解析时会把BOM当成非法字符直接报ParseError: not well-formed (invalid token)。症状是你明明看到XML内容完整但sdkmanager --list仍失败且错误信息极其模糊。验证方法用VS Code打开XML右下角查看编码格式。如果是UTF-8 with BOM点击它选择Save with Encoding→UTF-8。或者用命令行# Linux/macOS file -i repository2-1.xml # 应显示 charsetutf-8 xxd repository2-1.xml | head -n 1 # 前三个字节不应是 ef bb bf4.2 Unity版本与SDK Tools版本存在硬性兼容矩阵Unity不是随便找个sdkmanager就能用。它对tools目录下的bin/sdkmanager版本有严格要求。例如Unity 2019.4.x 要求tools版本 ≥ 26.1.1Unity 2021.3.x 要求tools版本 ≥ 26.1.1但 ≤ 30.0.0新版tools移除了部分Legacy APIUnity 2022.3.x 要求tools版本 ≥ 30.0.0如果你用Android Studio自带的最新SDKtools版本可能是33.xUnity会直接拒绝调用。解决方案去 Android SDK Tools归档页 下载指定版本的commandlinetoolszip包解压后替换Android/sdk/tools目录。切记不要删掉platform-tools和platforms只换tools。4.3 JDK版本必须与Unity匹配且JAVA_HOME必须指向JDK而非JREUnity 2021.3 强制要求JDK 11或17且必须是JDK含javac编译器不能是仅含java运行时的JRE。常见错误是系统PATH里有JDK但JAVA_HOME指向了JRE目录。验证命令echo $JAVA_HOME # 应输出类似 /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home $JAVA_HOME/bin/java -version # 必须显示 Java(TM) SE Runtime Environment $JAVA_HOME/bin/javac -version # 必须能执行否则Unity会静默失败4.4sdkmanager的--repository参数必须是绝对路径且路径中不能有空格或中文这是Windows用户的噩梦。如果你把XML放在C:\我的SDK修复\repository.xmlsdkmanager会直接报错Invalid argument。必须改为C:\UnitySDKFix\repository.xml。macOS同理避免~/Downloads/我的修复包/这种路径。4.5 Unity Editor Log里的真实错误被严重截断很多人只看Console窗口的红色报错但真正的根因在Editor Log里。例如你可能看到Failed to update Android SDK package list UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool)这毫无价值。必须打开完整Log文件路径见2.2节搜索sdkmanager或repository往往能找到类似[Unity] Calling: /path/to/sdkmanager --list --verbose [Unity] stderr: Exception in thread main java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter这说明JDK版本太新JDK 17移除了JAXB需要降级到JDK 11或添加--add-modules java.xml.bind参数。4.6 CI流水线必须显式声明ANDROID_HOME和JAVA_HOME本地能跑CI上失败90%是因为CI环境变量缺失。在GitHub Actions或GitLab CI中必须明确设置# GitHub Actions 示例 env: ANDROID_HOME: /opt/android-sdk JAVA_HOME: /usr/lib/jvm/temurin-11-jdk-amd64且确保/opt/android-sdk目录下已预置好tools、platforms和你修改过的sdkmanager.bat。4.7 不要迷信“一键修复工具”网上流传的所谓“Unity Android SDK修复工具”大多是用PowerShell或Shell脚本自动修改sdkmanager.bat。它们的问题在于硬编码了Unity安装路径如C:\Program Files\Unity\Hub\Editor\2021.3.25f1\而Unity Hub安装路径千变万化且未处理JDK版本校验。我建议手动生成或用以下Python脚本已开源# unity_sdk_fix.py import os import sys from pathlib import Path def inject_repository_param(unity_editor_path: str, xml_path: str): sdkmanager_path Path(unity_editor_path) / Data / PlaybackEngines / AndroidPlayer / Tools / ConsolidatedSDKTools / bin / sdkmanager.bat if not sdkmanager_path.exists(): print(fWarning: {sdkmanager_path} not found. Skipping.) return with open(sdkmanager_path, r, encodingutf-8) as f: content f.read() # 检查是否已注入 if --repository in content: print(fAlready injected. Skip {sdkmanager_path}) return # 插入参数在-javaagent之前 insert_pos content.find(-javaagent:) if insert_pos -1: insert_pos content.find(-classpath) if insert_pos ! -1: new_content content[:insert_pos] f--repository {xml_path} content[insert_pos:] with open(sdkmanager_path, w, encodingutf-8) as f: f.write(new_content) print(fInjected --repository into {sdkmanager_path}) # 使用示例 inject_repository_param(rC:\Program Files\Unity\Hub\Editor\2021.3.25f1\Editor, rD:\UnitySDKFix\repository2-1.xml)4.8 最后一道防线手动预装所有必需组件如果以上都失败还有终极手段绕过sdkmanager --list直接--install。你需要知道Unity Build真正需要哪些包。根据Unity官方文档和实测最低需求清单为组件命令示例说明Platformsdkmanager platforms;android-33必须与Player Settings中Target API Level一致Build-toolssdkmanager build-tools;33.0.2版本需≥Target API Level33.0.2兼容性最好Platform-toolssdkmanager platform-toolsADB调试必备版本无所谓Emulatorsdkmanager emulator模拟器运行必备非打包必需但建议安装NDKsdkmanager ndk;23.1.7779620如启用IL2CPP必须安装版本需匹配Unity要求执行完这些--install后Unity就不再需要拉取XML因为它发现所有依赖都已满足。5. 长期维护建议建立团队级SDK资产库告别重复踩坑解决单次报错只是救火建立可持续的SDK管理机制才是治本。我在目前负责的跨平台AR项目中推行了一套“Unity Android SDK资产库”方案已稳定运行18个月零故障5.1 构建标准化SDK压缩包我们不再让每个开发者自己下载、配置SDK而是由TA团队统一维护一个unity-android-sdk-v2023.1.zip包内容结构如下unity-android-sdk-v2023.1/ ├── tools/ # 固定版本26.1.1已修改sdkmanager.bat ├── platforms/ # 预装android-29,30,31,32,33 ├── build-tools/ # 预装30.0.3,31.0.0,32.0.0,33.0.2 ├── platform-tools/ # 预装最新版 ├── emulator/ # 预装最新版 ├── repository2-1.xml # 已验证的离线清单 └── README.md # 包含Unity版本兼容表、JDK要求、安装指南这个zip包上传至公司NAS所有新成员入职第一件事就是下载解压然后在Unity Hub里设置Android SDK路径指向它。5.2 CI流水线集成自动化校验在Jenkins/GitLab CI的打包Job开头加入一段校验脚本# 检查SDK完整性 if [ ! -f $ANDROID_HOME/repository2-1.xml ]; then echo ERROR: Missing repository2-1.xml. SDK is corrupted. exit 1 fi if ! $ANDROID_HOME/tools/bin/sdkmanager --list --verbose | grep -q android-33; then echo ERROR: SDK does not contain android-33 platform. exit 1 fi一旦校验失败立即阻断构建避免问题扩散。5.3 建立SDK更新SOP标准操作流程我们规定SDK大版本更新如Android 34发布必须经过以下流程TA在隔离环境下载新SDK生成新repository2-1.xml用新XML在测试项目中完成全链路打包包括IL2CPP、ARM64、Split APK更新unity-android-sdk-v2023.2.zip同步更新README中的兼容矩阵邮件通知全体开发者附带一键迁移脚本两周后旧SDK包从NAS下线。这套机制让我们彻底摆脱了“打包失败”带来的紧急响应压力。现在新成员入职2小时内就能打出第一个APK而不再是花半天时间在网上搜“Failed to update Android SDK package list”。最后分享一个小技巧在Unity的Edit Preferences External Tools里把Android SDK路径设置为一个符号链接Windows用mklinkmacOS用ln -s比如C:\android-sdk → C:\android-sdk-v2023.1。这样当需要切换SDK版本时只需修改链接目标所有Unity项目自动生效无需逐个重设。这个细节能让团队效率提升至少30%。