Flutter Android license 报错排查:sdkmanager 与 Java 环境修复指南
2026/9/13 9:53:24 网站建设 项目流程

说实话,我第一次在 Windows 上配 Flutter 环境时,被这个报错卡了整整一个下午。flutter doctor前几项全绿,唯独 Android licenses 后面挂着[!] Android license status unknown,于是我按提示执行flutter doctor --android-licenses,等来的不是交互式的授权确认,而是一行冷冰冰的:

Exception in thread "main" java.lang.RuntimeException: Android sdkmanager tool was found but failed to run.

相信搜索到这篇文章的你,大概率和我当时一样:不知道到底是谁没装、谁配错了、谁版本不对。别急,这篇不是数据库式的“复制粘贴答案”,而是带你把整个链路拆开,理解这个报错为什么会出现、怎么定位真正原因,以及最终怎么把它彻底解决。

1. 这条报错到底意味着什么:拆开 flutter doctor 的调用链路

1.1 flutter doctor 与 Android 许可证检查的关系

先理清一个基本问题:flutter doctor怎么知道 Android 许可协议尚未接受?

Flutter 自身不做 Android 打包,它需要借助 Android SDK 里的命令行工具来完成真实任务。当flutter doctor发现你的 Flutter 项目需要验证 Android SDK licenses 时,它会做这么几件事:

  1. 使用你预先配置的ANDROID_HOMEANDROID_SDK_ROOT环境变量,找到 Android SDK 安装根目录;
  2. 在这个根目录下的cmdline-tools文件夹里寻找sdkmanager可执行文件;
  3. 调用sdkmanager --licenses,把用户需要接受的所有许可协议打印出来;
  4. 读取交互式输入,把“接受”结果写回本地的~/.android/licenses目录。

也就是说,flutter doctor --android-licenses只是一个壳,真正的干活工具是sdkmanager。如果sdkmanager本身启动失败,Flutter 拿不到任何有用反馈,仅能从进程退出状态推断:这个工具“找到了,但没跑起来”。

1.2 “was found but failed to run” 的准确含义

这个报错信息在 Flutter 源码里实际上是一个兜底异常。它的字面意思非常明确:

  • was found:Flutter 已经在 Android SDK 目录里找到了sdkmanager的可执行文件,所以“没有安装 Android SDK”不是问题的原因;
  • failed to run:找到了文件,但执行它时进程崩溃、超时或者立刻以非零状态退出。

听起来很简单,但问题恰恰出在“为什么没跑起来”。我在实际排查中见过至少四类触发条件,它们的现象都是这一条 RuntimeException,底层原因却完全不同:

触发条件底层真实现象
缺少 cmdline-tools 组件找不到sdkmanager.bat或对应 shell 脚本
Java 版本太旧UnsupportedClassVersionErrorNoSuchMethodError
JAVA_HOME 未设置或指向错误启动 JVM 失败,提示找不到 Java
目录权限或路径含特殊字符无法创建临时文件或运行权限不足

所以,正确排除思路不是“见到这个报错就去重装 Android Studio”,而是先搞清楚当前机器上究竟是哪一类问题。

1.3 为什么真实异常会被吞掉

你可能已经注意到,报错只有一行,完全看不到 Java 内部异常的堆栈信息。这是因为 Flutter 在调用外部命令时,并不会无条件把子进程的标准输出和标准错误原样透传给你。它默认只显示自己封装好的消息,真正的sdkmanager输出被丢到了日志里。

这有点像甲方给你打电话说“方案不行”,但不告诉你哪一页不行,也不把对方修改意见转给你。如果只看最终提示,你只能知道方案挂了,具体挂在哪一步只能自己去查。

我自己排查时找到被吞掉的详细输出,方法是直接手动执行sdkmanager,也就是下一节要讲的关键操作。

2. 挖出真报错:手动执行 sdkmanager

既然 Flutter 不把细节告诉我们,那就绕过 Flutter,直接敲命令。手动运行sdkmanager有两个好处:一是能看到真正的 Java 异常堆栈;二是能确认是不是环境配置问题。这也是我推荐每个被这问题困扰的开发者先做的一步。

2.1 先确认你的 Android SDK 到底放在哪

执行下面的命令,先看 Flutter 认为你的 SDK 在哪个目录:

flutter doctor -v

输出里有一行类似:

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at C:\Android\sdk • Platform android-34, build-tools 34.0.0 • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java • Java version OpenJDK Runtime Environment (build 17.0.x)

其中Android SDK at后面的路径就是关键。如果这里没打印路径,说明ANDROID_HOME环境变量没设置好,Flutter 根本找不到 SDK,那又是另一个故事了。

也可以手动检查环境变量:

  • Windows CMD:
echo %ANDROID_HOME% echo %ANDROID_SDK_ROOT%
  • PowerShell:
echo $env:ANDROID_HOME echo $env:ANDROID_SDK_ROOT
  • macOS / Linux:
echo $ANDROID_HOME echo $ANDROID_SDK_ROOT

两个变量都为空也没关系,Flutter 还能通过 Android Studio 配置来发现 SDK 位置,不过既然报错已经走到sdkmanager was found,至少说明 SDK 本身是能被找到的。

2.2 打开 cmdline-tools 目录看结构

拿到 SDK 根目录后,打开它,重点看cmdline-tools文件夹。像我见过比较典型的目录结构如下:

C:\Android\sdk ├── build-tools ├── cmdline-tools │ ├── 11.0 │ ├── latest │ └── tools ├── emulator ├── platform-tools └── platforms

这里有几个关键点:

  1. latest目录是新版 Android Studio 安装命令行工具的默认位置,里面结构为latest/bin/sdkmanager.bat
  2. tools目录是旧版 Android SDK Tools 的遗留位置,老版本 Flutter 很偏爱它;
  3. 带版本号的目录(如11.0)通常是你手动解压命令行工具时留下的。

Flutter 在找 sdkmanager 时,优先级在不同版本里略有差异,但原则上是“先找cmdline-tools/tools/bin/sdkmanager,再找cmdline-tools/latest/bin/sdkmanager”。如果你的目录结构是cmdline-tools/11.0而没有latesttools,Flutter 就会认为“没有找到可用的 sdkmanager”,报错文案会变成 not found 而不是 failed to run。

这里我是建议你顺手把cmdline-tools目录完整列出来,留个截图或者记录下来,后面修复会用到。

2.3 检查 Java:别只信 java -version

sdkmanager本质上是一个 Java 程序,所以 Java 环境对不对至关重要。注意,光在终端敲java -version能看到版本还不够,因为那只能说明 PATH 里的 java 能用,不代表 Flutter 和 sdkmanager 用的也是同一个。

最可靠的检查方式:

Windows CMD:

echo %JAVA_HOME% "%JAVA_HOME%\bin\java.exe" -version

macOS / Linux:

echo $JAVA_HOME $JAVA_HOME/bin/java -version

如果 JAVA_HOME 为空,或者指向的目录里根本没有bin/java,那sdkmanager启动 JVM 时就会失败。尤其是很多人电脑上装了一堆 JDK 版本:Oracle JDK 8、OpenJDK 11、Android Studio 自带的 JBR 17,环境变量一乱,问题马上就来了。

2.4 手动运行 sdkmanager,看真实堆栈

这个步骤是整套排查的灵魂。进入对应的目录,直接执行:

  • Windows(以 latest 为例):
cd /d %ANDROID_HOME%\cmdline-tools\latest\bin sdkmanager.bat --version
  • macOS / Linux:
cd "$ANDROID_HOME/cmdline-tools/latest/bin" ./sdkmanager --version

如果一切正常,会输出类似12.0这样的版本号。如果失败,你会看到比 Flutter 提示详细得多的堆栈。我遇到最多的输出是这种:

Exception in thread "main" java.lang.UnsupportedClassVersionError: com/android/sdklib/tool/sdkmanager/SdkManagerCli has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0

如果你看到的错误和我这个类似,说明 Java 版本不对,sdkmanager要求 Java 17,但你当前运行的 JVM 只有 Java 11 甚至 8。

另一种常见情况是:

The operation couldn’t be completed. Unable to locate a Java Runtime.

这说明系统连合适的 Java 都找不到,需要先安装 JDK,或者设置 JAVA_HOME。

还有一类错误发生在运行sdkmanager --licenses而不是sdkmanager --version时,可能是网络代理或 SSL 证书问题。后面我会单独讲。

3. 修复实战:按根因解决

手动执行后,报错原因基本就浮出水面了。下面按概率从高到低列出修复方案。

3.1 根因A:缺少或错位 cmdline-tools

如果你的手动执行结果是“找不到文件”或者“No such file or directory”,基本就是 cmdline-tools 组件缺失或目录结构不对。

最省事的办法是用 Android Studio 安装:

  1. 打开 Android Studio,进入Settings(或 Preferences)→ Languages & Frameworks → Android SDK → SDK Tools
  2. 在列表里找到Android SDK Command-line Tools (latest),勾选;
  3. 点击 Apply 安装,完成后检查cmdline-tools/latest/bin/sdkmanager是否存在。

如果你不想打开 Android Studio,也可以去官方命令行工具下载页,下载对应平台的 compressed zip,解压后得到一个cmdline-tools文件夹。注意,官方 zip 解压后目录结构是cmdline-tools/cmdline-tools/bin,很多人直接塞进 SDK 根目录,导致最终路径变成SDK/cmdline-tools/bin/sdkmanager,这也不对。

正确做法是,进入cmdline-tools文件夹,把里面的内容放到一个名为latest的子目录中。也就是:

SDK/cmdline-tools/latest/bin/sdkmanager.bat

如果之前已经有cmdline-tools/11.0这类版本目录,也可以直接把它改名为latest,效果一样。我自己的习惯是保留版本号目录,再做一个latest目录指向当前正在使用的版本,方便以后升级时对比。

3.2 根因B:Java 版本太旧,无法驱动现代 sdkmanager

新版 Android sdkmanager(大致从 8.0 开始)要求 Java 17,这一点被坑的人最多。为什么?因为不少人电脑上装着 JDK 8 或 JDK 11,而且它们也能正常编译运行一般的 Flutter Android 工程,于是压根没想到问题出在 Java 版本上。

严格来说,sdkmanager 报的是 class file version 61.0(Java 17)与 class file version 55.0(Java 11)不匹配。你要做的不是换一个老版本 sdkmanager,而是给 sdkmanager 一个 Java 17 的运行环境。

三种做法:

做法一:设置 JAVA_HOME 指向 Android Studio 自带的 JBR

Android Studio 从某个版本开始自带 JetBrains Runtime,本质是定制版 OpenJDK,版本通常就是 17 或 21,而且路径固定:

  • Windows 默认路径:C:\Program Files\Android\Android Studio\jbr
  • macOS 默认路径:/Applications/Android Studio.app/Contents/jbr/Contents/Home

在系统环境变量里把JAVA_HOME指到对应路径即可。Windows 可以这么设置:

setx JAVA_HOME "C:\Program Files\Android\Android Studio\jbr"

注意,如果路径里有空格,不要自己加引号,setx的语法里,后面的值整体算一个参数,不加额外引号。设置完记得重开终端,让环境变量生效。

macOS 临时生效的话:

export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"

如果想永久生效,就写到~/.zshrc~/.bash_profile。之后重新跑一遍手动验证命令,应该就能看到版本号了。

做法二:安装独立 JDK 17

如果你不想依赖 Android Studio,也可以直接装一个通用的 OpenJDK 17,比如 Temurin 17 或 Oracle JDK 17。安装时勾选“设置 JAVA_HOME”,省得自己再配。

装完之后同样先验证:

java -version

看到openjdk version "17.0.x"再继续。

做法三:接受同时存在多个 JDK 的事实,但要把优先级管好

很多开发者的机器上不可能只有一套 JDK,我也不建议为了一个 sdkmanager 卸载其他版本。更好的做法是把 Java 17 设为系统默认的 JAVA_HOME,同时用工具链给不同项目指定不同 JDK。比如 Android Studio 的 Gradle JDK 设置、FVM 的环境变量等。这样 sdkmanager 能跑,Gradle 构建也能按项目需要切换。

作为长期方案,我建议:JAVA_HOME 永远指向一个 17 或 21 的 JDK。Flutter 新版和 Android Gradle Plugin 对 Java 17 的兼容性已经很稳定了,不用再守着 JDK 8 不放。

3.3 根因C:目录权限、路径含空格或代理下载失败

如果你的 sdkmanager 能手动运行--version,但运行--licenses时依然崩溃,问题往往不在 Java,而在更隐蔽的系统层。

比较典型的几类:

目录权限不足

Windows 上如果 Android SDK 装在C:\Program Files (x86)\Android\android-sdk,普通用户终端没有写权限,sdkmanager 想往 SDK 目录写临时文件时会失败。解法是:

  1. 右键 SDK 根目录 → 属性 → 安全 → 编辑权限,给当前用户发“完全控制”权限;
  2. 或者干脆把 SDK 挪到C:\Android\sdk这类不涉及系统保护的目录。

macOS/Linux 上同理,如果在/usr/local/opt下,当前用户可能没写权限,用sudo chown -R $(whoami) /path/to/sdk把所有权交给当前用户更省心。

路径中有空格或中文

Android SDK 目录最好不要有空格,也尽量不要有中文。如果你装在C:\Users\张三\AppData\Local\Android\Sdk,某些工具解析路径时可能出问题。虽然现代版本大多已经能处理空格,但“能处理”和“处理得好”是两回事。既然我们用命令行工具,就把坑留少一点。

代理问题

sdkmanager --licenses需要从 Google 的仓库拉取许可信息。如果你的网络环境需要使用代理,而 sdkmanager 没有配置代理,就可能报出让人摸不着头脑的 SSL 异常。遇到这种情况,先检查.android目录下有没有sdkmanager 的代理配置,或者在 SDK 根目录下的cmdline-tools/latest/bin/sdkmanager脚本里看到代理相关参数。

其实更直接的判断方式是:手动执行sdkmanager --list,如果列表能正常拉下来,说明网络这块没问题;如果连列表都拉不下来,那就先解决网络和代理,再回来处理 licenses。

历史残留目录脏了

如果上述都正常但问题还在,可以清理一下本地授权缓存。Android licenses 存在用户主目录下的.android/licenses文件夹里,里面是 Android SDK License、Android Auto Distribution License 等几个没有扩展名的文件。你可以把.android目录临时改名备份,再重新运行flutter doctor --android-licenses。注意,这只是恢复初始化,不是把问题掩盖了,如果还有残余错误,备份恢复很关键。

4. 不想敲命令?用 Android Studio 图形界面兜底

如果手动执行 sdkmanager 这一步对你来说太劝退,或者整了半天 Java 还是没对齐,还有一个办法能绕开命令行:直接让 Android Studio 的图形界面来处理许可。

4.1 SDK Manager 图形界面如何接过许可证任务

打开 Android Studio,按下面路径操作:

  1. 顶部菜单File → Settings(macOS 是Android Studio → Preferences);
  2. 找到Languages & Frameworks → Android SDK
  3. 左侧能直接看到 SDK 路径和已安装的组件清单。

要触发许可证窗口,最直接的办法是切到SDK Platforms选项卡,勾选一个尚未安装的 API Level,点击 Apply。这时候 Android Studio 会弹出一个许可协议确认列表,列出所有需要接受的协议,并让你逐条勾选 Accept。

把每个协议都勾上 Accept,点 Finish,Android Studio 会把这些许可写进.android/licenses目录。之后再回到终端执行:

flutter doctor

如果 Flutter 能读出这些授权,Android license status unknown就会消失,整个流程就结束了。

4.2 我说说为什么“图形界面法”也有局限性

这个方法从体验上确实是“躺平式修复”,但它有两个前提:

  1. 你的 Android Studio 版本和 SDK 版本不能太老。太老的版本里,许可列表可能不包含新组件需要的协议,导致部分新协议仍未被接受;
  2. Flutter 要能正确读到 Android Studio 写入的授权文件。如果 Flutter 因为 PATH 或环境变量问题用了另一套 SDK,授权文件写到 A 套 SDK,Flutter 检查 B 套 SDK,自然也会失败。

所以,图形界面法更适合“已经能用 Android Studio 正常开发,只是 Flutter 这边报授权未知”的场景。如果连 Android Studio 的 SDK Manager 都打不开,或者你根本没装 Android Studio,那就还是得回到命令行方案。

另外还有一个小技巧:即使你用图形界面接受了许可,也可以顺手在 Android Studio 的Terminal面板里敲一次:

%ANDROID_HOME%\cmdline-tools\latest\bin\sdkmanager.bat --licenses

Android Studio 自带的终端会继承它的环境变量,比你在系统终端敲命令少了环境变量干扰,往往能直接跑通。这也是排查环境问题的一个低成本的“试金石”。

5. 从源头不再踩坑:一套稳定的环境配置流程

解决完这一次,我更想聊聊怎么让这种问题在以后少出现。毕竟 Flutter 开发不是只配一次环境就结束,换电脑、升级 Flutter、升级 Android Studio,每一步都可能让这类老问题卷土重来。

5.1 我的环境配置清单

我现在给新机器配置 Flutter Android 环境,固定按这套顺序来,几乎不会在 licenses 上踩坑:

  1. 先装 Java 17 JDK,配置好 JAVA_HOME,并把%JAVA_HOME%\bin加入 PATH;
  2. 安装 Android Studio,并在 SDK Manager 里至少安装:
    • Android SDK Platform-Tools
    • Android SDK Build-Tools
    • Android SDK Command-line Tools (latest)
    • 一个当前常用的 Android Platform(比如 Android 34)
  3. 设置 ANDROID_HOME 指向 SDK 根目录,并把%ANDROID_HOME%\platform-tools加入 PATH;
  4. 打开新终端,先执行sdkmanager --version确认 Java 与 sdkmanager 能协同工作;
  5. 再执行flutter doctor,按提示处理剩余问题。

这个方法的核心逻辑是:在跑 Flutter 之前,先保证 Android SDK 自身的工具链是完好的。好比你在用打包工具之前,得先确认原料供应商能正常发货。

5.2 一条命令验证环境是否完整

我习惯在配环境时写一个小的检查脚本,Windows 上存成一个.bat,macOS/Linux 上存成一个.sh,内容大致是:

Windows CMD:

@echo off echo ANDROID_HOME=%ANDROID_HOME% echo JAVA_HOME=%JAVA_HOME% if defined JAVA_HOME ( "%JAVA_HOME%\bin\java" -version ) if exist "%ANDROID_HOME%\cmdline-tools\latest\bin\sdkmanager.bat" ( echo sdkmanager is OK ) else ( echo sdkmanager NOT FOUND )

macOS / Linux:

echo "ANDROID_HOME=$ANDROID_HOME" echo "JAVA_HOME=$JAVA_HOME" "$JAVA_HOME/bin/java" -version if [ -f "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then echo "sdkmanager is OK" else echo "sdkmanager NOT FOUND" fi

运行一次,所有关键路径一目了然。哪个变量没设、哪个文件缺失,不需要再靠flutter doctor猜来猜去。

5.3 个人经验碎碎念

这次踩坑让我最深的体会是:Flutter 的错误提示可以很简陋,但不要借此偷懒。遇到Exception in thread "main"这种输出,第一步不是去搜索整行报错,而是先想清楚“这个命令在调用谁、它依赖什么”。一旦上手手动跑 sdkmanager,问题范围立刻缩到 Java 版本、目录结构、权限这三块。

另外还有一个细节值得留意:很多人在网上看到的解决方案是“安装 Android SDK Tools(旧版)”,也就是那个带 GUI 的SDK Manager.exe的旧工具。我建议别再用它了,它是被官方弃用的历史组件,和现代 Flutter 的兼容性时好时坏。用新版 Command-line Tools 才是正道。

最后说一句,如果你照着上面操作,把sdkmanager --version跑通了,那么flutter doctor --android-licenses大概率也已经正常了。如果还不行,那就回到第 2 节手动执行sdkmanager --licenses,看它具体卡在哪个环节——至少这一次,它会给你一个真实、可读的错误信息,而不是让你在Exception in thread "main"里干瞪眼。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询