zl程序教程

您现在的位置是:首页 >  APP

当前栏目

Flutter问题记录 - Unable to find bundled Java version

2023-03-31 11:02:06 时间


前言

有个紧急问题需要修复,本以为很快就能解决继续休假,没想到项目打开运行后Android端跑不起来了,iOS端正常运行,这就有点莫名其妙,明明放假前还是没问题的,难道我拉取的最新代码有问题?不会吧,谁放假还敲代码啊?🤔️看了下最新的提交记录,还是放假前我提交的,那就肯定不是项目的问题。

开发环境

  • Android Studio: 2022.1.1
  • Flutter: 3.3.10

问题描述

Android端构建运行失败,报错信息如下:

Execution failed for task ':app:processDebugMainManifest'.
> Unable to make field private final java.lang.String java.io.File.path accessible: module java.base does not "opens java.io" to unnamed module

问题分析

先网上搜一搜,大部分都是说JDK版本有问题,提到JDK版本我想到放假最后一天升级了Android Studio版本,从2021.3.1升级到2022.1.1,这应该算大版本更新,难道是这个升级导致JDK版本出问题了。尝试设置项目的JDK版本,折腾一番无果。

既然是Flutter项目,那可以尝试用Flutter命令检查Android Studio正不正常:

flutter doctor

执行输出:

[] Flutter (Channel stable, 3.3.10, on macOS 13.0.1 22A400 darwin-x64, locale
    zh-Hans-CN)
[!] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/macos#android-setup for
      more details.
[] Xcode - develop for iOS and macOS (Xcode 14.2)
[] Chrome - develop for the web
[!] Android Studio (version 2022.1)
    ✗ Unable to find bundled Java version.
[] IntelliJ IDEA Community Edition (version 2022.3.1)
[] VS Code (version 1.74.2)
[] Connected device (3 available)
[] HTTP Host Availability

原来真有问题,还不止一个,那就逐个解决。

  1. Android license status unknown

这个问题的解决办法Flutter已经给出了,执行下方命令即可:

flutter doctor --android-licenses

执行输出:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/android/prefs/AndroidLocationsProvider has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
	at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
	at java.lang.Class.getMethod0(Class.java:3018)
	at java.lang.Class.getMethod(Class.java:1784)
	at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:650)
	at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:632)

好家伙,这命令执行竟然报错了,我记得老早以前执行过没报错,网上一搜又是JDK版本问题。奇了怪了,虽然我电脑上有装多个版本的JDK,但是Android Studio一直是用的自带的JRE。

  1. Unable to find bundled Java version

这个报错是说找不到捆绑的Java版本🤔️,难道新版的Android Studio移除了自带的JRE?这么一想好像已经找到问题的根源所在了。接下来就是验证这个想法。

首先把Flutter框架项目代码拉下来,搜索关键词Unable to find bundled Java version

screenshot1

关键代码(位于Flutter框架项目下的packages/flutter_tools/lib/src/android/android_studio.dart文件):

final String javaPath = globals.platform.isMacOS ?
    version != null && version.major < 2020 ?
    globals.fs.path.join(directory, 'jre', 'jdk', 'Contents', 'Home') :
    globals.fs.path.join(directory, 'jre', 'Contents', 'Home') :
    globals.fs.path.join(directory, 'jre');
final String javaExecutable = globals.fs.path.join(javaPath, 'bin', 'java');
if (!globals.processManager.canRun(javaExecutable)) {
  _validationMessages.add('Unable to find bundled Java version.');
} else {
// 省略
}

这段代码的作用就是拼接Java可执行程序的路径,并验证是否能执行。现在我们把涉及到路径的几个变量打印出来,打印很简单,这是Dart写的,直接用print方法就可以了。

print('directory: $directory');
print('javaPath: $javaPath');
print('javaExecutable: $javaExecutable');

现在的问题是怎么执行到修改后的文件,当我们执行flutter doctor命令时,会执行到packages/flutter_tools/bin/flutter_tools.dart文件(后面会分析),然后再进一步解析命令执行。所以我们直接通过Dart命令执行flutter_tools.dart文件是不是就可以了。

dart flutter_tools.dart doctor

切换到Flutter框架项目的packages/flutter_tools/bin目录下执行输出:

flutter_tools.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/executable.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/runner.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/src/artifacts.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/src/base/context.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/src/base/io.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/src/base/io.dart:28:8: Error: Expected an identifier, but got ';'.
Try inserting an identifier before ';'.
library;
       ^
../lib/src/base/logger.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/src/base/platform.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^
../lib/src/base/template.dart:1:1: Error: The specified language version is too high. The highest supported language version is 2.18.
// Copyright 2014 The Flutter Authors. All rights reserved.
^

执行报错了,原因是Dart SDK版本低了,电脑上当前的Dart SDK版本是2.18,拉下来的Flutter框架项目代码要求更高的版本。这时候不用去下载最新的Dart SDK版本,Flutter框架项目是自带Dart SDK的,位于项目bin/cache/dart-sdk路径下。

2023/01/26更新:以上说明有点问题,实际的Flutter框架项目默认是不带Dart SDK的,如果你没找到,请看补充说明1

指定Dart可执行程序路径:

flutter框架项目路径/bin/cache/dart-sdk/bin/dart flutter_tools.dart doctor

或者用这个,这个最终也是用到了bin/cache/dart-sdk/bin/dart

flutter框架项目路径/bin/dart flutter_tools.dart doctor

执行输出(省略部分):

directory: /Applications/Android Studio.app/Contents
javaPath: /Applications/Android Studio.app/Contents/jre/Contents/Home
javaExecutable: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java

接下来检查/Applications/Android Studio.app/Contents路径下文件,可以选择[访达] -> [顶部菜单栏的前往] -> [前往文件夹] -> [输入路径跳转]或[应用程序] -> [Android Studio.app] -> [右键显示包内容] -> [Contents]。

screenshot2

实锤了,新版本的Android Studio真的移除了JRE,jre目录找不到,怪不得报错了,不过多了一个jbr目录,找了个以前的Android Studio版本对比(旧版下载,不是很全):

screenshot3

搜了一下jbr(JetBrains Runtime),原来IDEA老早就开始用了,是基于OpenJDK修改的东西。不知道为什么Android Studio从2022.1.1版本才开始支持,去下载了两个预览版也是没有jre目录了,说明后续应该都没了。

综上,问题的根源在于Android Studo移除了jre目录。那如果我直接修改代码,将路径拼接过程中的jre改为jbr是不是就没问题了,实测可行,执行flutter框架项目路径/bin/cache/dart-sdk/bin/dart flutter_tools.dart doctor命令一切正常。

解决方案

如果你略过了前面的问题分析,请注意,以下所说的jbr所在目录路径为:

  • macOS系统:/Applications/Android Studio.app/Contents
  • Windows系统:Android Studio的安装目录

2023/01/30更新:实测以下三种解决方法在Flutter 3.7.0版本依旧适用。对于前两种方法,如果你尝试后未能解决,请先检查上方所说的目录中是否有jre目录或者软链接。

这里提供三种解决方法(任选一种):

  1. 简单粗暴的方法
  • 如果是macOS系统,在jbr同目录下创建一个jre目录,然后将jbr目录内的全部文件复制一份到jre目录下即可。
  • 如果是Windows系统,jre目录是存在的,不过里面几乎没东西,可以直接将jbr目录内的全部文件复制一份到jre目录下即可。
  1. 创建软链接的方法
  • 如果是macOS系统,切换到jbr所在目录执行命令:
ln -s jbr jre

注意,如果是通过cd命令切换到jbr所在目录,需要对路径中的空格进行转义或用单/双引号包裹路径,例如cd /Applications/Android Studio.app/Contents

  • 如果是Windows系统,使用管理员身份打开终端(可以按Win+X键,然后选择Windows 终端(管理员)),切换到jbr所在目录执行命令:
mklink /D jre jbr

这里有几点要注意:

  • 一定要管理员身份运行,不然权限不足会报错提示你没有足够的权限执行此操作
  • 如果打开终端默认用的是PowerShell(输入命令那一行最前面有PS字符),需要输入cmd切换到命令提示符,这是因为PowerShell不支持mklink命令
  • 执行命令前需要删除已经存在的jre目录,不然会报错提示当文件已存在时,无法创建该文件
  • 右键创建快捷方式实测是行不通的,这种方式创建的快捷方式其实是创建了一个新的文件,只不过这个文件属性里面包含了目标路径,查看这个文件大小,才1000字节。通过mklink命令方式创建的,文件大小和占用空间等属性和目标文件属性一致。

2023/02/05更新:通过以上两种方法解决后,Android Studio后续升级可能会遇到如下所示问题:

screenshot4

如果你当前的Flutter版本已经修复了该问题,点击Proceed删除添加的jre目录或者软链接即可。如果还未修复又想继续升级(点击Proceed),请按之前的方法将被删除的jre目录或者软链接添加回去。

  1. 修改Flutter框架代码的方法(不推荐,有点麻烦,感兴趣的可以看看)

前面问题分析中尝试修改代码是可行的,但是那是在Flutter框架项目下改的,现在我们要做的是对当前电脑上的Flutter SDK进行修改。首先找到Flutter SDK目录:

screenshot5

如果你见过Flutter框架项目的目录结构,你会发现一模一样,那是不是说,直接修改目录下的packages/flutter_tools/lib/src/android/android_studio.dart文件就好了,将jre改为jbr,实测没变化。这难道还需要编译?是的,确实还要做一些处理。

flutter doctor命令的执行开始分析,首先找到flutter可执行文件(位于Flutter SDK的bin目录下),这个本质是一个shell脚本,可以用文本工具打开,有点长就贴一个脚本的关键部分:

source "$BIN_DIR/internal/shared.sh"

shared::execute "$@"

在脚本的最后执行了shared::execute::是命名约定,用于分隔库)函数,并将命令执行时的参数传递下去。找到shared.sh文件(位于Flutter SDK的bin/internal目录下)中的shared::execute函数:

# This function is intended to be executed by entrypoints (e.g. `//bin/flutter`
# and `//bin/dart`). PROG_NAME and BIN_DIR should already be set by those
# entrypoints.
function shared::execute() {
  export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)"

  # If present, run the bootstrap script first
  BOOTSTRAP_PATH="$FLUTTER_ROOT/bin/internal/bootstrap.sh"
  if [ -f "$BOOTSTRAP_PATH" ]; then
    source "$BOOTSTRAP_PATH"
  fi

  FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools"
  SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
  STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp"
  SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart"
  DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk"

  DART="$DART_SDK_PATH/bin/dart"

  # If running over git-bash, overrides the default UNIX executables with win32
  # executables
  case "$(uname -s)" in
    MINGW*)
      DART="$DART.exe"
      ;;
  esac

  # Test if running as superuser – but don't warn if running within Docker or CI.
  if [[ "$EUID" == "0" && ! -f /.dockerenv && "$CI" != "true" && "$BOT" != "true" && "$CONTINUOUS_INTEGRATION" != "true" ]]; then
    >&2 echo "   Woah! You appear to be trying to run flutter as root."
    >&2 echo "   We strongly recommend running the flutter tool without superuser privileges."
    >&2 echo "  /"
    >&2 echo "📎"
  fi

  # Test if Git is available on the Host
  if ! hash git 2>/dev/null; then
    >&2 echo "Error: Unable to find git in your PATH."
    exit 1
  fi
  # Test if the flutter directory is a git clone (otherwise git rev-parse HEAD
  # would fail)
  if [[ ! -e "$FLUTTER_ROOT/.git" ]]; then
    >&2 echo "Error: The Flutter directory is not a clone of the GitHub project."
    >&2 echo "       The flutter tool requires Git in order to operate properly;"
    >&2 echo "       to install Flutter, see the instructions at:"
    >&2 echo "       https://flutter.dev/get-started"
    exit 1
  fi

  upgrade_flutter 7< "$PROG_NAME"

  BIN_NAME="$(basename "$PROG_NAME")"
  case "$BIN_NAME" in
    flutter*)
      # FLUTTER_TOOL_ARGS aren't quoted below, because it is meant to be
      # considered as separate space-separated args.
      exec "$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.dart_tool/package_config.json" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
      ;;
    dart*)
      exec "$DART" "$@"
      ;;
    *)
      >&2 echo "Error! Executable name $BIN_NAME not recognized!"
      exit 1
      ;;
  esac
}

函数有点长,直接看最后面就好。最后有个分支语句,根据BIN_NAME的值走不同分支,把相关的变量打印出来看看:

echo "PROG_NAME: $PROG_NAME"
echo "BIN_NAME: $BIN_NAME"

执行flutter doctor输出(省略部分):

PROG_NAME: /usr/local/Caskroom/flutter/3.3.10/flutter/bin/flutter
BIN_NAME: flutter

根据输出结果可以判断,走的是第一个分支,执行exec "$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.dart_tool/package_config.json" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"。之所以会有这个分支判断,是因为Dart命令最终也会执行到这个函数,执行Dart相关命令输出(省略部分):

PROG_NAME: /usr/local/Caskroom/flutter/3.3.10/flutter/bin/dart
BIN_NAME: dart

分析到这,好像也没用到SCRIPT_PATH变量(flutter_tools.dart的路径,请看上述脚本函数代码中的定义),前面问题分析中提到执行flutter doctor命令时,会执行到packages/flutter_tools/bin/flutter_tools.dart文件,这难道是假的?不能说是假的,只能说有点偏差。如果是在这直接执行packages/flutter_tools/bin/flutter_tools.dart文件的,那前面一开始修改代码就可以生效了,原因在于这边是用Dart命令执行SNAPSHOT_PATH所指向的快照文件(flutter_tools.snapshot)去了。

flutter_tools.snapshot是通过Dart命令生成的快照文件,那我们是不是也可以生成一个快照文件替换掉自带的?切换到Flutter SDK目录的packages/flutter_tools/bin路径下执行命令:

dart --snapshot=flutter_tools.snapshot flutter_tools.dart

2023/01/26更新:如果生成快照文件报错,请看补充说明2

然后将生成的flutter_tools.snapshot文件复制到bin/cache目录下,注意,最好先备份原先的flutter_tools.snapshot文件。对比文件大小,发现相差很小,那应该稳了,执行flutter doctor输出:

Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel stable, 3.3.10, on macOS 13.0.1 22A400 darwin-x64, locale
    zh-Hans-CN)
[] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
[] Xcode - develop for iOS and macOS (Xcode 14.2)
[] Chrome - develop for the web
[] Android Studio (version 2022.1)
[] IntelliJ IDEA Community Edition (version 2022.3.1)
[] VS Code (version 1.74.2)
[] Connected device (3 available)
[] HTTP Host Availability

• No issues found!

一切正常!🎉

2023/01/26更新:注意,修改Flutter SDK文件后,后续使用Flutter命令升级时可能会提示你是否要保留这些改动,一般来说,使用flutter upgrade --force 命令强制升级即可。

快写完了,才发现差点忘了一开始是因为什么原因写这篇文章的,按以上解决方案做,如果执行flutter doctor命令没问题,实测Android端构建运行也恢复正常了,所以这其实是同一个问题导致的,如果感兴趣的话,可以看看补充分析Flutter问题记录 - Unable to find bundled Java version(续)

补充说明1

如果在Flutter框架项目中没找到Dart SDK,需要先执行命令:

flutter框架项目路径/bin/flutter

执行后,Flutter会自动下载Dart SDK,下载完成后会构建flutter tool,也就是生成flutter_tools.snapshot快照文件。
由于我执行过Flutter命令,所以一开始没注意到项目中默认是不带Dart SDK的。

也许执行命令后你还会遇到这样的问题:

Error: The Flutter directory is not a clone of the GitHub project.
       The flutter tool requires Git in order to operate properly;
       to install Flutter, see the instructions at:
       https://flutter.dev/get-started

解决方法:使用Git工具拉取Flutter框架项目代码,不要直接下载。

补充说明2

如果生成flutter_tools.snapshot快照文件失败,报错提示:

../lib/src/test/flutter_tester_device.dart:180:11: Warning: Operand of null-aware operation '!' has type 'Uri' which excludes null.
 - 'Uri' is from 'dart:core'.
          forwardingUri!,
          ^
../lib/runner.dart:9:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/args-2.3.1/lib/command_runner.dart': No such file or directory
import 'package:args/command_runner.dart';
       ^
../lib/src/runner/flutter_command.dart:6:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/args-2.3.1/lib/command_runner.dart': No such file or directory
import 'package:args/command_runner.dart';
       ^
../lib/runner.dart:10:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/intl-0.17.0/lib/intl.dart': No such file or directory
import 'package:intl/intl.dart' as intl;
       ^
../lib/runner.dart:11:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/intl-0.17.0/lib/intl_standalone.dart': No such file or directory
import 'package:intl/intl_standalone.dart' as intl_standalone;
       ^
../lib/src/runner/flutter_command_runner.dart:6:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/args-2.3.1/lib/command_runner.dart': No such file or directory
import 'package:args/command_runner.dart';
       ^
../lib/src/artifacts.dart:5:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/file-6.1.4/lib/memory.dart': No such file or directory
import 'package:file/memory.dart';
       ^
../lib/src/cache.dart:8:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/file-6.1.4/lib/memory.dart': No such file or directory
import 'package:file/memory.dart';
       ^
../lib/src/artifacts.dart:6:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/process-4.2.4/lib/process.dart': No such file or directory
import 'package:process/process.dart';
       ^
../lib/src/cache.dart:10:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/process-4.2.4/lib/process.dart': No such file or directory
import 'package:process/process.dart';
       ^
../lib/src/commands/analyze.dart:6:8: Error: Error when reading '../../../.pub-cache/hosted/pub.flutter-io.cn/process-4.2.4/lib/process.dart': No such file or directory
import 'package:process/process.dart';
       ^

执行命令获取flutter_tools所需要的依赖包即可解决:

flutter update-packages

总结

写完这篇文章,想着不知道有没有人提issue,要是没人提,我去提一个。没想到,老早就有人提了issue,应该是用Android Studio预览版的人提的,最近也还有人不断在重复提。

最后

如果这篇文章对你有所帮助,请不要吝啬你的点赞👍加星🌟,谢谢~