相信不少人在脱离Android Studio、转向纯命令行方式管理SDK时,都遇到过这个让人一头雾水的场景:明明JDK配好了,环境变量也设了,Command line tools也解压到指定目录了,结果打开终端执行sdkmanager --list,包列表没出来,先弹一行“Warning: Could not create settings”。第一反应是哪里配置错了,反复检查几遍又没发现明显问题,反而因为这行警告开始怀疑人生。
这行警告本身并不代表命令一定失败,但它往往意味着sdkmanager没能按预期初始化自己的配置区域。如果不搞清楚背后原因,后续安装platform-tools、build-tools时可能出现各种莫名其妙的行为,比如配置写不进去、许可协议无法持久化、每次运行都像第一次启动。这篇文章就专门说透这个“Could not create settings”的来龙去脉,以及我在Windows、Linux、macOS三种环境下的实际解决经验,顺便把常见的连带问题一并整理出来。
1. 问题现象与成因分析
1.1 报错现场还原
先还原一下现场。假设你在Windows的PowerShell里执行:
sdkmanager --list输出第一行就是:
Warning: Could not create settings然后才慢吞吞出现一堆available packages。如果你是第一次看到这个Warning,大概率会忽略它,继续往下看包列表;但在某些情况下,这行Warning后面跟着的可能是Error,直接把整个命令卡死,比如:
Error: Could not determine SDK root Error: java.lang.NullPointerException这两种结果我都遇到过。区别在于,前者是“能忍的警告”,后者是“完全没法用”,但根源往往是一样的——sdkmanager这个Java程序的Preferences存储路径出了问题。
1.2 为什么只是警告却未必能忽略
要理解这个问题,得先弄明白sdkmanager保存配置的机制。sdkmanager是基于Java开发的命令行工具,它使用Java自带的Preferences API来存储一些用户级配置,比如已接受的许可协议、缓存目录位置等。Java的Preferences在Windows上默认走注册表,在Linux和macOS上默认走用户目录下的~/.java/.userPrefs文件。
但问题来了,很多SDK工具在启动脚本里会通过-Djava.util.prefs.userRoot参数强制指定一个特定的Preferences根目录。sdkmanager也不例外,它希望把配置写到<用户目录>/.android下面,而不是系统默认的注册表或.java目录。这样做的本意是让每个用户的SDK配置独立、可迁移,听起来很合理。
问题就出在这个“强制指定”的路径上。如果路径本身不存在、父目录没有写权限、磁盘空间不足、甚至文件名被安全软件拦截,Java在初始化Preferences时就会打印“Could not create settings”的警告,并自动回退到内存中的临时配置。
回退到内存配置的后果是什么?最直观的体验就是:你运行sdkmanager --licenses,辛辛苦苦输入一堆y接受了协议,下次再执行命令,它还会像第一次一样弹出许可协议要求你重新接受。因为这些“接受记录”只写进了内存,没有落到磁盘。另一个后果是,如果你用命令行工具安装组件,日志和配置断断续续,看起来像每次运行都在“失忆”。
1.3 触发这个Warning的三大类原因
我在不同机器上反复触发过这个问题,总结下来基本逃不出三类原因。
第一类是Java环境问题。sdkmanager在比较老的版本上对Java版本非常挑剔,如果JAVA_HOME指向的JDK版本过高(比如Java 17、Java 21),或者指向的路径根本不存在,启动脚本在拼接java命令时就会出问题,导致Preferences初始化失败。这也是最常见的一种情况。
第二类是配置目录的物理条件不满足。最常见的是~/.android目录不存在且无法自动创建,这在某些公司电脑、CI服务器或权限受限的账户下特别常见。比如Windows域账户对用户目录有严格的ACL限制,Linux容器内HOME目录被挂载成只读,都会导致创建失败。
第三类是环境变量中的路径包含了无法解析的内容。比如JAVA_HOME包含了空格、系统用户名是中文或特殊字符、ANDROID_SDK_ROOT路径拼写错误等,这些都在实际工作中亲眼见过。
2. 动手前必看:环境与版本排查清单
2.1 第一步先确认Java环境正常
不要一上来就改脚本,先确认Java环境是不是真的没问题。打开终端执行:
java -version echo $JAVA_HOME在Windows的cmd里则是:
java -version echo %JAVA_HOME%我见过不少人栽在JAVA_HOME上。比如JAVA_HOME指向了C:\Program Files\Java\jre1.8,而sdkmanager需要的是JDK(不仅仅有JRE),这时即使java -version能正常输出,sdkmanager启动后也会因为缺少编译器相关组件而出问题。还有更隐蔽的情况:JAVA_HOME路径末尾多了个反斜杠或空格,或者路径里带了Program Files (x86)这种带括号的目录,都可能让sdkmanager.bat脚本中的引号处理失效。
稳妥的做法是让JAVA_HOME指向一个真实的JDK安装根目录,比如:
- Windows:
C:\Program Files\Java\jdk-11.0.21 - Linux:
/usr/lib/jvm/java-11-openjdk-amd64 - macOS:
/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
然后执行java -version确认能看到版本号,用where java或which java确认解析到的是JAVA_HOME下的命令,而不是系统自带的其他Java版本。
2.2 环境变量命名必须统一规范
sdkmanager识别SDK根目录有两种环境变量:ANDROID_HOME和ANDROID_SDK_ROOT。新旧项目对这两者的支持度不太一样,Android Studio新版本主推ANDROID_SDK_ROOT,但很多老工具链还在读ANDROID_HOME。
实践中我建议两个都设置,而且指向同一个目录。我踩过的坑是:只设了ANDROID_HOME,结果某个命令行工具只识别ANDROID_SDK_ROOT,行为就开始迷离。
在Windows下设置方法:
setx ANDROID_HOME "D:\Android\Sdk" setx ANDROID_SDK_ROOT "D:\Android\Sdk"Linux和macOS则在~/.bashrc或~/.zshrc中添加:
export ANDROID_HOME=$HOME/Android/Sdk export ANDROID_SDK_ROOT=$HOME/Android/Sdk设置完之后,新开一个终端窗口让配置生效,不要在当前窗口里硬试,设置环境变量后当前窗口经常不自动刷新。
2.3 版本兼容矩阵:并非越新越好
关于Java版本与Command line tools的搭配,官方文档只是笼统地写了“需要Java 8或更高版本”,但实际体验下来,Java 11是最省心的选择。Java 8能用但偏老,Java 17以上在某些sdkmanager版本上会触发模块访问限制,什么java.base/java.util.prefs访问冲突之类的问题。Java 21我试过一次,部分老版本命令行工具直接起不来。
一个可复现的经验规则:
| 命令行工具版本 | 推荐JDK | 不推荐JDK | 备注 |
|---|---|---|---|
| 老版本(2021年及之前) | JDK 8 | JDK 17+ | 对高版本JDK兼容性差 |
| 中等版本(2022-2023) | JDK 11 | JDK 8 | JDK 8也能跑,但控制台有乱码风险 |
| 最新版本 | JDK 11或17 | 无特别限制 | 但避开JDK 21+,亲测仍偶发问题 |
确保JAVA_HOME指向的JDK在推荐范围内,这一步做好了,后面很多奇奇怪怪的问题都会自动消失。
3. 四种解法逐一实操:从简单到彻底
3.1 方法一:手动创建缺失的配置目录
这是最直白的一招,原理很简单:既然Java报“Could not create settings”,那我就亲手把目录建好,给它充分的权限,让它没有借口。
在Windows上打开cmd,依次执行:
mkdir %USERPROFILE%\.android mkdir %USERPROFILE%\.java mkdir %USERPROFILE%\.java\.userPrefs在Linux/macOS上执行:
mkdir -p ~/.android mkdir -p ~/.java/.userPrefs这里有个细节:.android目录才是sdkmanager真正想用的目录,.java/.userPrefs是Java Preferences默认的落地点。两个都建出来最稳妥。
建完之后,给目录设置正确的权限。Linux/macOS下:
chmod -R 755 ~/.android chmod -R 755 ~/.javaWindows下不需要专门设权限,正常用户目录都应该有写权限;如果你用的是受限域账户,检查一下用户目录的“安全”选项卡,确保当前用户有“完全控制”权限。
建完目录再跑一次sdkmanager --version,如果Warning消失,说明问题就是这个。这个方法之所以放在第一位,是因为它零风险、见效快,而且即使不直接解决问题,也能帮你排除一个嫌疑点。
3.2 方法二:修改sdkmanager脚本中的prefs路径
如果手动创建目录后Warning还在,说明sdkmanager脚本里写的Preferences路径根本不是你建的那些目录,或者脚本指定的路径有问题。这时候就得直接看脚本内容了。
Windows版的脚本在cmdline-tools\bin\sdkmanager.bat,Linux/macOS版的是在同一个目录下的sdkmanager(无扩展名)。
用文本编辑器打开sdkmanager.bat,搜索关键参数java.util.prefs.userRoot。正常情况下你能看到类似这样的一段:
-Djava.util.prefs.userRoot=%USERPROFILE%\.android问题通常出在这个参数上。比如:
%USERPROFILE%变量在某些精简版Windows系统上没有正确展开(概率很低,但遇到过)。- 路径后面带了尾随空格或多余符号。
- 脚本里写死了某个不存在的用户名路径。
这时候的改法是:把userRoot指向一个绝对路径,而不是依赖环境变量。比如改成:
-Djava.util.prefs.userRoot=C:\Users\你的用户名\.android\prefs注意要把“你的用户名”替换成真实系统账户名。我一般会顺手把同一行里的-Djava.util.prefs.systemRoot也检查一遍,如果有,同样改成绝对路径。
Linux/macOS版脚本里则是:
-Djava.util.prefs.userRoot=$HOME/.android同样可以改成:
-Djava.util.prefs.userRoot=/home/你的用户名/.android/prefs改之前复制一份原文件做备份,这个操作涉及脚本修改,小心驶得万年船。改完保存,重新运行sdkmanager --list。
3.3 方法三:切换到匹配的JDK版本
如果脚本没问题、目录也创建了,但Warning依然顽固,那十有八九是JDK版本兼容性的问题。这种情况在最新版JDK上跑旧版Command line tools时尤为明显。
有一个典型的现场:某开发者的电脑装了最新的JDK 21,运行sdkmanager --list时,报警告的同时还带了一堆java.lang.reflect.InaccessibleObjectException,这就是JDK模块系统拦截了sdkmanager内部的反射调用。这时候无论你怎么创建目录、改脚本路径,都绕不过JDK的模块访问限制。
解决方案很朴素:另外装一个JDK 11,把JAVA_HOME切过去。
在Windows上,我习惯用一个绿色版JDK(解压免安装),放在一个不容易被误删的目录,比如D:\DevTools\jdk-11。然后把JAVA_HOME指向这个目录,系统PATH里最靠前的位置也加一行%JAVA_HOME%\bin。
在Linux上:
sudo apt install openjdk-11-jdk安装后用update-alternatives --config java切换默认版本。
macOS上如果没有的话,用包管理器安装JDK 11,然后:
export JAVA_HOME=$(/usr/libexec/java_home -v 11)切换完成后,执行:
java -version sdkmanager --version如果Warning消失,那就确认是JDK版本问题没跑了。
值得一提的是,为什么我强烈推荐JDK 11而不是最新的?因为Android的构建生态对JDK 11的支持最成熟,Gradle插件、AGP、sdkmanager这些工具链都以JDK 11为基准测试过。你不需要最新,你需要最稳的。
3.4 方法四:Windows权限修复与管理身份
这个方法专门针对Windows下的权限问题。有些公司电脑,用户目录被IT部门的策略锁得严严实实,看起来你是管理员,但实际上对C:\Users\你的用户名某些子目录并没有写权限。
这种情况下,先确认当前用户的权限状态。在cmd里执行:
whoami /user icacls "%USERPROFILE%"icacls会列出用户目录的ACL信息,看看当前用户有没有(F)完全控制权限。如果显示的是(R)只读或(W)部分写入,就说明权限不够。
修复方式:对.android目录单独授权。
icacls "%USERPROFILE%\.android" /grant "%USERNAME%:(OI)(CI)F" /T /C这里解释一下参数含义:(OI)(CI)表示继承到子文件和子目录,F是完全控制,/T递归处理现有子项,/C遇到错误继续执行。执行完再跑一次sdkmanager。
如果连.android目录都不存在,或者icacls报错说找不到文件,那就先创建目录再授权:
mkdir "%USERPROFILE%\.android" icacls "%USERPROFILE%\.android" /grant "%USERNAME%:(OI)(CI)F" /T /C另外,如果你的用户目录在一台磁盘空间几乎满的机器上,也会出现无法创建配置文件的情况。检查一下C:盘的空间,至少留出几个GB的余量。这个原因听起来低级,但我真遇到过两回,系统盘满了之后什么妖魔鬼怪都会冒出来。
3.5 验证是否恢复:三种检查手段
无论用的是上面哪种方法,最后都要做个验证,不能看着Warning消失就算完事,还得确认配置真的能持久化。
第一层检验:输出是否干净。
sdkmanager --version如果只有版本号,没有Warning,第一层通过。
第二层检验:许可协议持久化。
sdkmanager --licenses用y接受所有许可,然后再次执行:
sdkmanager --licenses如果这次不再弹出让你接受许可的提示,说明配置真正写入磁盘了。如果第二次运行仍然询问许可协议,说明Preferences还是在走内存通道,问题没有根治。
第三层检验:安装一个真实组件。
sdkmanager "platform-tools"安装成功后再重跑一次命令,确认不会重复下载或提示错误。这一步能把隐藏问题彻底暴露出来,因为平台工具的安装涉及多个目录写入,一次性验证最彻底。
4. 常见问题与排查技巧实录
4.1 目录建了,Warning还在?
这种情况我见过不少次。手动创建了.android目录,甚至chmod 777都给上了,Warning还是原封不动地出现。这时候别再跟目录本身较劲,换个思路:
用--verbose参数跑一次,把详细日志打出来:
sdkmanager --verbose --list日志里通常会直接显示它尝试初始化的完整路径。比如某次我在一个中文用户名的系统上排查,日志里显示路径是C:\Users\张三\.android,但脚本里实际展开的是C:\Users\???\.android,中文编码在批处理脚本里乱了,导致路径完全对不上。这个问题不是靠建目录能解决的,只能用方法二里说的改绝对路径方案。
还有一种可能:你以为生效的是你正在执行的sdkmanager,但实际调用的可能是另一个路径下的sdkmanager。在执行which sdkmanager(Windows用where sdkmanager),看看解析出来的是不是你解压的那个目录。PATH环境变量里如果同时存在多个版本的cmdline-tools,极容易踩中这个坑。
4.2 报错提示的路径与当前用户不一致?
有些机器上,%USERPROFILE%变量被某些软件改写了,或者当前用户是Administrator但系统里还有别的账户,导致脚本生成的路径指向了一个根本不存在的用户目录。
这时候直接在cmd里看变量展开结果:
echo %USERPROFILE%如果输出内容和你预期的不一致,事情就清楚了。解决办法同样是绕开变量,在脚本里写死绝对路径。这个方法虽然不够优雅,但胜在稳,毕竟sdkmanager.bat只需要在一台机器上配一次,之后很少会动。
4.3 命令闪退、无任何输出怎么办?
有时候问题不是Warning,而是闪退。双击sdkmanager.bat或者窗口一闪而过,什么信息都看不到。
这种情况先在cmd里手动执行:
call sdkmanager.bat --version用call是因为批处理脚本结束后如果不加call,控制权可能被转移,窗口直接关闭。加了call之后,就算脚本报错,cmd窗口也会保持打开,让你看到错误信息。
如果依然没有任何输出,检查一下是不是Java没装。直接执行:
java -version如果提示java不是内部或外部命令,说明JAVA_HOME和PATH都没配对,那问题就根本不是sdkmanager本身的毛病了。
在Linux/macOS上类似,用bash -x sdkmanager --version可以开启脚本调试模式,逐行显示执行过程和变量值,排查非常高效。
4.4 在CI/服务器无交互环境下怎么处理?
在CI流水线里跑sdkmanager时出现“Could not create settings”,处理思路和本机不太一样。CI环境大多是临时容器,用户目录每次都是全新状态,而且很多镜像的HOME目录指向的路径并不存在。
典型现象是:构建脚本里执行sdkmanager --licenses时,无法创建~/.android,导致许可协议接受后无法持久化,后面Gradle构建又检测到许可未接受,直接失败循环。
我的做法是在CI脚本的安装阶段提前创建目录:
mkdir -p "$HOME/.android" mkdir -p "$HOME/.java/.userPrefs" echo "y" | sdkmanager --licenses这里有个细节,echo "y"只发送一个y,如果许可协议很多,就需要发送多个。更稳妥的写法是用yes |管道:
mkdir -p "$HOME/.android" mkdir -p "$HOME/.java/.userPrefs" yes | sdkmanager --licenses如果CI容器用的是非root用户且HOME目录是只读挂载,那就得在启动容器时把HOME目录挂载成可写,或者在构建脚本开头用export HOME=/tmp/sdk-home重新指一个可写目录。不要觉得改HOME是什么见不得人的操作,在SDK安装的自动化脚本里,这是很常见的解法。
4.5 常见问题速查表
| 现象 | 直接原因 | 优先解法 |
|---|---|---|
| Warning: Could not create settings | Preferences路径不可写 | 手动创建.android目录 |
| Warning后所有许可协议都要重新接受 | prefs回退到内存配置 | 修改脚本里userRoot为绝对路径 |
| 附带InaccessibleObjectException | JDK版本过高 | 切换到JDK 11 |
| Windows下提示权限拒绝 | 用户目录ACL受限 | icacls授权完全控制 |
| CI中每次安装都要重新接受许可 | HOME目录未持久化 | 提前mkdir并将HOME指向可写目录 |
| 命令直接闪退 | Java未安装或PATH错误 | 用call sdkmanager.bat --version定位 |
5. 个人实操心得与备选方案
折腾这个Warning的过程中,我最大的体会是:别一上来就以为是自己某个步骤做错了,这行警告百分之七八十是环境问题而非配置问题,最常见的坑集中在JDK版本和用户目录权限上。按“先检查Java -> 再查环境变量 -> 最后动脚本”的顺序排查,基本能在十分钟内定位到根因。
还有一点想提醒大家,如果你不想跟命令行工具死磕,Android Studio自带的SDK Manager界面完全能承担同样的工作,下载、更新、删除组件都够用。命令行工具的价值在于脚本化和CI集成,如果只是本机开发,不必非得用sdkmanager折腾自己。
最后分享一个小技巧:排查这个Warning时,把输出异常和磁盘目录结构一起抓拍下来,很多时候问题自己就暴露了。比如我在Linux容器里遇到过容器镜像根本没有创建/root/.android目录,结果第一次运行sdkmanager时系统尝试创建却被镜像的只读根文件系统挡住,连报错日志都很诡异。这时候用一个临时可写目录做预配置,再挂载到正式路径,问题立刻解决。
如果你也遇到过类似的情况,用上面的方法解决后再配合sdkmanager --licenses做两次验证,基本可以确定问题是否彻底根除。实在不行就换个思路,检查一下磁盘剩余空间和目录所有权,这两处隐藏坑往往比表面的错误信息更难发现。