Skip to content

feat: Java有关重构#11

Open
futurw4v wants to merge 21 commits into
lxdklp:mainfrom
futurw4v:feature/java_manager
Open

feat: Java有关重构#11
futurw4v wants to merge 21 commits into
lxdklp:mainfrom
futurw4v:feature/java_manager

Conversation

@futurw4v

@futurw4v futurw4v commented May 3, 2026

Copy link
Copy Markdown
Contributor

重构内容:

  • 将Java初始化有关任务与UI分离,抽出至java_service.dart,将java_manager.dart重命名为java_utils.dart
  • 移除搜索Java(searchPotentialJavaExecutables)内的遍历,在Windows下采用搜索注册表的方法(参考PCL2),对于Mac与Linux没有地方测试(
  • 将SharedPreference中的java更改为javaSelectedPath,添加javaRuntimes缓存Java的信息
  • 重构java.dart,添加了刷新Java与手动添加Java
  • 更改了MainStartPageState内检测Java是否存在的逻辑
  • 优化代码与部分逻辑
  • 添加了插件open_filex,win32_registry

实在是铲不动了太多东西要改的了
没有ChangeNotifier真的很难改,这算前面挖的坑了
所以Java部分先改到这 (•_•),我打算开始改UI了

对了最好在contribution.md里面提一嘴统一PUB_HOSTED_URL和FLUTTER_STORAGE_BASE_URL,在我这里很容易因为pub get一下pubspec.lock就会迎来大变

实际上还是有点问题的 在初始化java的时候会阻塞UI,但是改这个要动很多东西所以没打算在现在弄

希望能早点合并喵

@lxdklp

lxdklp commented May 3, 2026

Copy link
Copy Markdown
Owner

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust Java management system, featuring a new JavaService for runtime state management and enhanced discovery logic in JavaUtils that includes Windows registry scanning. The UI has been updated to support manual Java path selection, runtime refreshing, and macOS-specific menu integrations. My feedback highlights several critical issues: the use of Isolate.run for refreshing runtimes will fail due to memory isolation and plugin limitations, a layout conflict exists between MainAxisSize.min and Spacer in the settings page, Platform.version is incorrectly used for architecture detection, registry handles lack proper resource cleanup, and the javaSelectedPath setter fails to persist changes to storage.

Comment thread lib/pages/setting/java.dart Outdated
Comment thread lib/pages/setting/java.dart Outdated
Comment thread lib/function/java/java_utils.dart
Comment thread lib/function/java/java_utils.dart Outdated
Comment thread lib/function/java/java_service.dart Outdated
@lxdklp

lxdklp commented May 3, 2026

Copy link
Copy Markdown
Owner

镜像源这个倒是没必要规定,自己觉得哪个镜像源快或者是用官方源也没啥所谓,反正应该也没人看pubspec.lock的变更

@futurw4v

futurw4v commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

镜像源这个倒是没必要规定,自己觉得哪个镜像源快或者是用官方源也没啥所谓,反正应该也没人看pubspec.lock的变更

主要是会一直在提交列表里

Comment thread CONTRIBUTING.md
@lxdklp

lxdklp commented May 31, 2026

Copy link
Copy Markdown
Owner

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust JavaService and JavaUtils to manage, search, and configure Java runtimes across different platforms, updating the launchers and settings UI accordingly. The code review identified several critical issues, including a UX bug in _pickAndAddJavaRuntime that unexpectedly closes the settings page, potential launcher crashes from saving empty system Java paths, a registry handle leak in JavaUtils, and a race condition where the main UI becomes interactive before Java initialization completes. Additionally, feedback was provided regarding improper reset logic when deleting non-active Java runtimes, and the need for better error handling during JSON parsing and background isolate execution.

Comment on lines +268 to +401
Future<void> _pickAndAddJavaRuntime() async {
// 打开FilePicker
final result = await FilePicker.platform.pickFiles(
dialogTitle: '选择Java路径',
type: FileType.custom,
allowedExtensions: Platform.isWindows ? ['exe'] : [],
);

setState(() {
_currentJavaPath = prefs.getString('java');
});
}
if (!mounted) return;

///
/// 写入当前 Java
///
Future<void> _setCurrentJavaPathToPrefs(String path) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('java', path);
// 未选择文件
if (result == null) {
showCustomDialog(
context: context,
title: '提示',

setState(() {
_currentJavaPath = path;
});
}
content: Text('未选择任何文件'),

///
/// 设置为系统 Java
///
Future<void> _setSystemJava() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('java');
actions: [
if (mounted)
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('关闭'),
),
],
);

setState(() {
_currentJavaPath = 'default';
});
}
return;
}

// 读取选择的文件的信息
final file = result.files.single;
final fileName = file.name.toLowerCase();
final validNames = Platform.isWindows
? ['java.exe', 'javaw.exe']
: ['java', 'javaw'];

final exe = file.path!;
final info = await JavaUtils.probeJavaExecutable(exe);

// 选择的文件文件名合法
if (file.path != null && validNames.contains(fileName)) {
if (!mounted) return;

showCustomDialog(
context: context,
title: '正在添加Java',
content: SizedBox(
height: 80,
child: Center(child: CircularProgressIndicator()),
),

barrierDismissible: false,
);

//
// 获取系统默认 Java 信息
//
Future<JavaInfo?> _getSystemDefaultJavaInfo() async {
try {
final javaVersionProcess = await Process.run('java', ['-version']);

if (javaVersionProcess.exitCode != 0) {
LogUtil.log(
'获取系统默认 Java 信息失败,退出码:${javaVersionProcess.exitCode}',
level: 'WARN',
if (info != null) {
// 查重逻辑
final alreadyExists = JavaService.javaRuntimes.any(
(runtime) => runtime.executable == exe,
);
}

final versionOutput = (javaVersionProcess.stderr as String).isNotEmpty
? javaVersionProcess.stderr as String
: javaVersionProcess.stdout as String;
if (alreadyExists) {
Navigator.of(context).pop();

final parsedVersion = JavaManager.parseVersionOutput(versionOutput);
showCustomDialog(
context: context,
title: '提示',

if (parsedVersion == null) {
LogUtil.log('无法解析系统默认 Java 版本信息', level: 'WARN');
return null;
}
content: Text('该Java已存在'),

String executablePath = '';

try {
if (Platform.isWindows) {
final where = await Process.run('where', ['java']);

if (where.exitCode == 0) {
executablePath = (where.stdout as String)
.toString()
.split('\n')
.first
.trim();
}
} else {
final which = await Process.run('which', ['java']);

if (which.exitCode == 0) {
executablePath = (which.stdout as String)
.toString()
.split('\n')
.first
.trim();
}
actions: [
if (mounted)
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('关闭'),
),
],
);

return;
}
} catch (e) {
LogUtil.log('获取系统默认 Java 路径时出错:$e', level: 'WARN');
}

return JavaInfo(
version: parsedVersion['version'] ?? 'unknown',
vendor: parsedVersion['vendor'],
path: executablePath,
os: Platform.operatingSystem,
arch: Platform.version,
);
} catch (e) {
LogUtil.log('执行 "java -version" 时出错:$e', level: 'WARN');
return null;
// 添加并写入SharedPreference
final isJdk = await JavaUtils.looksLikeJdk(exe);

JavaService.javaRuntimes.add(
JavaRuntime(info: info, executable: exe, isJdk: isJdk),
);

JavaService.updateJavaRuntimes(JavaService.javaRuntimes);

Navigator.of(context).pop();

// 调用setState触发页面更新
setState(() {});

if (!mounted) return;

showCustomDialog(
context: context,
title: '提示',
content: Text('添加 ${info.vendor} ${info.version} 成功!'),

actions: [
if (mounted)
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('关闭'),
),
],
);

return;
}
}

// 最后分支,选择的文件文件名不合法等情况
if (!mounted) return;

Navigator.of(context).maybePop();

showCustomDialog(
context: context,
title: '提示',
content: Text('请选择正确的Java可执行文件'),

actions: [
TextButton(
onPressed: () => {if (mounted) Navigator.of(context).pop()},
child: Text('关闭'),
),
],
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

_pickAndAddJavaRuntime 方法中存在一个严重的逻辑缺陷:\n1. 如果用户选择的文件名不合法(不包含 javajavaw),代码不会进入 if (file.path != null && validNames.contains(fileName)) 分支,而是直接走到方法末尾的“最后分支”。\n2. 在“最后分支”中,代码会无条件调用 Navigator.of(context).maybePop()。由于此时“正在添加Java”的加载弹窗从未被打开,这一调用会直接将当前的设置页面(JavaPage)关闭,导致用户被意外退回到主设置菜单,并在此之上弹出一个“请选择正确的Java可执行文件”的提示框,这属于严重的 UX 缺陷。\n3. 此外,file.path! 存在强转非空风险,且手动添加的路径没有经过 resolveSymbolicLinks() 规范化,可能导致与自动搜索到的路径产生重复。\n\n建议重构该方法,先进行完整的文件名和路径校验,若不合法则直接弹窗提示并返回;校验通过后再显示加载弹窗,并在 try-catch-finally 中安全地处理解析、查重和弹窗关闭逻辑。

  Future<void> _pickAndAddJavaRuntime() async {
    // 打开FilePicker
    final result = await FilePicker.platform.pickFiles(
      dialogTitle: '选择Java路径',
      type: FileType.custom,
      allowedExtensions: Platform.isWindows ? ['exe'] : [],
    );

    if (!mounted || result == null) {
      if (result == null && mounted) {
        showCustomDialog(
          context: context,
          title: '提示',
          content: const Text('未选择任何文件'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('关闭'),
            ),
          ],
        );
      }
      return;
    }

    final file = result.files.single;
    final filePath = file.path;
    if (filePath == null) return;

    final fileName = file.name.toLowerCase();
    final validNames = Platform.isWindows
        ? ['java.exe', 'javaw.exe']
        : ['java', 'javaw'];

    if (!validNames.contains(fileName)) {
      showCustomDialog(
        context: context,
        title: '提示',
        content: const Text('请选择正确的Java可执行文件'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('关闭'),
          ),
        ],
      );
      return;
    }

    // 显示正在添加的 Dialog
    showCustomDialog(
      context: context,
      title: '正在添加Java',
      content: const SizedBox(
        height: 80,
        child: Center(child: CircularProgressIndicator()),
      ),
      barrierDismissible: false,
    );

    try {
      final exe = await File(filePath).resolveSymbolicLinks();
      final info = await JavaUtils.probeJavaExecutable(exe);

      if (!mounted) return;

      if (info == null) {
        Navigator.of(context).pop(); // 关闭正在添加的 Dialog
        showCustomDialog(
          context: context,
          title: '提示',
          content: const Text('无法解析该 Java 可执行文件信息'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('关闭'),
            ),
          ],
        );
        return;
      }

      // 查重逻辑
      final alreadyExists = JavaService.javaRuntimes.any(
        (runtime) => runtime.executable == exe,
      );

      if (alreadyExists) {
        Navigator.of(context).pop(); // 关闭正在添加的 Dialog
        showCustomDialog(
          context: context,
          title: '提示',
          content: const Text('该Java已存在'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('关闭'),
            ),
          ],
        );
        return;
      }

      // 添加并写入SharedPreference
      final isJdk = await JavaUtils.looksLikeJdk(exe);
      JavaService.javaRuntimes.add(
        JavaRuntime(info: info, executable: exe, isJdk: isJdk),
      );
      await JavaService.updateJavaRuntimes(JavaService.javaRuntimes);

      if (!mounted) return;
      Navigator.of(context).pop(); // 关闭正在添加的 Dialog

      setState(() {});

      showCustomDialog(
        context: context,
        title: '提示',
        content: Text('添加 ${info.vendor} ${info.version} 成功!'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('关闭'),
          ),
        ],
      );
    } catch (e) {
      if (mounted) {
        Navigator.of(context).pop(); // 关闭正在添加的 Dialog
        showCustomDialog(
          context: context,
          title: '提示',
          content: Text('添加失败:$e'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('关闭'),
            ),
          ],
        );
      }
    }
  }

Comment on lines +73 to +93
if (_javaSelectedPath.isEmpty) {
if (systemJavaInfo != null) {
_javaSelectedPath = systemJavaInfo.path;
} else if (javaRuntimes.isNotEmpty) {
_javaSelectedPath = _javaRuntimes.first.executable;
}

prefs.setString('javaSelectedPath', _javaSelectedPath);
} else {
// 缓存的java已无效,重复上方逻辑
final info = await JavaUtils.probeJavaExecutable(_javaSelectedPath);

if (info == null) {
if (systemJavaInfo != null) {
_javaSelectedPath = systemJavaInfo.path;
} else if (javaRuntimes.isNotEmpty) {
_javaSelectedPath = _javaRuntimes.first.executable;
}

prefs.setString('javaSelectedPath', _javaSelectedPath);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

当系统默认 Java 存在但其路径为空(例如 java -version 成功但 where/which 查找失败)时,systemJavaInfo.path 将会是空字符串 ""。\n此时如果 _javaSelectedPath 为空,代码会直接将 _javaSelectedPath 设为 systemJavaInfo.path(即 ""),并写入 SharedPreferences。这会导致启动游戏时因可执行文件路径为空而报错。\n建议在检查 systemJavaInfo 时,同时确保其 path 不为空。

    if (_javaSelectedPath.isEmpty) {
      if (systemJavaInfo != null && systemJavaInfo.path.isNotEmpty) {
        _javaSelectedPath = systemJavaInfo.path;
      } else if (javaRuntimes.isNotEmpty) {
        _javaSelectedPath = _javaRuntimes.first.executable;
      }

      prefs.setString('javaSelectedPath', _javaSelectedPath);
    } else {
      // 缓存的java已无效,重复上方逻辑
      final info = await JavaUtils.probeJavaExecutable(_javaSelectedPath);

      if (info == null) {
        if (systemJavaInfo != null && systemJavaInfo.path.isNotEmpty) {
          _javaSelectedPath = systemJavaInfo.path;
        } else if (javaRuntimes.isNotEmpty) {
          _javaSelectedPath = _javaRuntimes.first.executable;
        }

        prefs.setString('javaSelectedPath', _javaSelectedPath);
      }
    }

Comment on lines +114 to +135
final versionKey = Registry.openPath(
root,
path: '$path\\$versionKeyName',
desiredAccessRights: AccessRights.readOnly,
);

// 读取路径
final javaHome = versionKey.getStringValue('JavaHome');
final installationPath = versionKey.getStringValue(
'InstallationPath',
);

try {
String? javaPath = javaHome ?? installationPath;

if (javaPath != null && javaPath.isNotEmpty) {
candidates.add(Directory(javaPath));
}
} finally {
// 确保键被关闭
versionKey.close();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

在读取注册表时,versionKey 是在 try-finally 块外部打开的,并且 getStringValue 也是在外部调用的。如果 Registry.openPath 抛出异常,或者 getStringValue 找不到对应的键值而抛出异常,versionKey.close() 将不会被执行,从而导致注册表句柄泄漏。\n此外,getStringValue 在键值不存在时会抛出异常,应该对其进行捕获。建议将打开和读取操作放入 try-catch-finally 块中。

              RegistryKey? versionKey;
              try {
                versionKey = Registry.openPath(
                  root,
                  path: '$path\\$versionKeyName',
                  desiredAccessRights: AccessRights.readOnly,
                );

                String? javaHome;
                try {
                  javaHome = versionKey.getStringValue('JavaHome');
                } catch (_) {}

                String? installationPath;
                try {
                  installationPath = versionKey.getStringValue('InstallationPath');
                } catch (_) {}

                final javaPath = javaHome ?? installationPath;
                if (javaPath != null && javaPath.isNotEmpty) {
                  candidates.add(Directory(javaPath));
                }
              } catch (_) {
                // 忽略单个版本键的读取失败
              } finally {
                versionKey?.close();
              }

Comment thread lib/main.dart
Comment on lines +265 to +267
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
JavaService.javaRuntimes.isEmpty) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

JavaService.initFuture 尚未完成(即 ConnectionState 不是 done)时,FutureBuilder 会直接渲染出完整的、可交互的主界面。\n如果用户在初始化完成前点击了“启动”或进入“设置”,可能会因为 javaSelectedPath 尚未完成初始化而导致 race condition 或崩溃。\n建议在初始化未完成时,显示一个加载指示器(如 CircularProgressIndicator),阻止用户进行交互,直到 Java 初始化完全结束。

      builder: (context, snapshot) {
        if (snapshot.connectionState != ConnectionState.done) {
          return const Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        }

        if (JavaService.javaRuntimes.isEmpty) {

Comment on lines +221 to +238
// 从列表中移除
JavaService.javaRuntimes.remove(javaRuntime);

typeChipLabel: javaRuntime.isJdk ? 'JDK' : 'JRE',
// 刷新UI 并执行异步操作
setState(() {});

vendor: javaRuntime.info.vendor,
await JavaService.updateJavaRuntimes(
JavaService.javaRuntimes,
);

isCurrent: isCurrentJava,
if (!mounted) return;

onTap: () =>
_setCurrentJavaPathToPrefs(javaRuntime.executable),
);
},
// 若移除的为当前Java,将第一个设置为javaRuntimes的第一个
if (JavaService.javaRuntimes.isNotEmpty) {
await JavaService.updateJavaSelectedPath(
JavaService.javaRuntimes.first.executable,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

在列表中删除某个 Java 运行时后,代码会无条件地将当前选择的 Java 路径重置为列表中的第一个 Java 运行时(JavaService.javaRuntimes.first.executable)。\n这会导致即使删除的不是当前选中的 Java,用户的选择也会被意外重置。\n建议仅在被删除的 Java 确实是当前选中的 Java 时,才执行重置逻辑。

Suggested change
// 从列表中移除
JavaService.javaRuntimes.remove(javaRuntime);
typeChipLabel: javaRuntime.isJdk ? 'JDK' : 'JRE',
// 刷新UI 并执行异步操作
setState(() {});
vendor: javaRuntime.info.vendor,
await JavaService.updateJavaRuntimes(
JavaService.javaRuntimes,
);
isCurrent: isCurrentJava,
if (!mounted) return;
onTap: () =>
_setCurrentJavaPathToPrefs(javaRuntime.executable),
);
},
// 若移除的为当前Java,将第一个设置为javaRuntimes的第一个
if (JavaService.javaRuntimes.isNotEmpty) {
await JavaService.updateJavaSelectedPath(
JavaService.javaRuntimes.first.executable,
);
}
final wasSelected = JavaService.javaSelectedPath == javaRuntime.executable;
// 从列表中移除
JavaService.javaRuntimes.remove(javaRuntime);
// 刷新UI 并执行异步操作
setState(() {});
await JavaService.updateJavaRuntimes(
JavaService.javaRuntimes,
);
if (!mounted) return;
// 若移除的为当前Java,将第一个设置为javaRuntimes的第一个
if (wasSelected && JavaService.javaRuntimes.isNotEmpty) {
await JavaService.updateJavaSelectedPath(
JavaService.javaRuntimes.first.executable,
);
}

Comment on lines +44 to +55
} else {
cachedRuntimes = await readJavaRuntimesFromPrefs(cachedJson);

// 遍历缓存的列表
for (final javaRuntime in cachedRuntimes) {
// 仅检测对应文件是否存在
if (await File(javaRuntime.executable).exists()) {
_javaRuntimes.add(javaRuntime);
validPaths.add(javaRuntime.executable);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

如果 SharedPreferences 中缓存的 javaRuntimes JSON 字符串损坏或格式不兼容,readJavaRuntimesFromPrefs 会抛出异常,导致整个 init() 初始化流程崩溃。\n建议使用 try-catch 包裹缓存解析逻辑,在解析失败时记录日志并清除损坏的缓存,然后自动触发重新搜索,以提高应用的健壮性。

Suggested change
} else {
cachedRuntimes = await readJavaRuntimesFromPrefs(cachedJson);
// 遍历缓存的列表
for (final javaRuntime in cachedRuntimes) {
// 仅检测对应文件是否存在
if (await File(javaRuntime.executable).exists()) {
_javaRuntimes.add(javaRuntime);
validPaths.add(javaRuntime.executable);
}
}
}
} else {
List<JavaRuntime> cachedRuntimes = [];
try {
cachedRuntimes = await readJavaRuntimesFromPrefs(cachedJson);
} catch (e) {
LogUtil.log('解析缓存的 Java 运行时失败:$e', level: 'WARN');
}
// 遍历缓存的列表
for (final javaRuntime in cachedRuntimes) {
// 仅检测对应文件是否存在
if (await File(javaRuntime.executable).exists()) {
_javaRuntimes.add(javaRuntime);
validPaths.add(javaRuntime.executable);
}
}
}

Comment on lines +63 to +101
try {
// 在子线程获取数据
final searchResults = await Isolate.run(
() => JavaUtils.searchPotentialJavaExecutables(),
);

// 将javaRuntimes转换为Map
final Map<String, JavaRuntime> runtimeMap = {
for (var runtime in JavaService.javaRuntimes)
runtime.executable: runtime,
};

// 通过Map查重
int addedCount = 0;
for (var result in searchResults) {
if (!runtimeMap.containsKey(result.executable)) {
runtimeMap[result.executable] = result;
// 发现新Java时计数
addedCount++;
}
}

final totalList = runtimeMap.values.toList();

// 更新并写入
await JavaService.updateJavaRuntimes(totalList);

if (!mounted) return;

String message = addedCount > 0
? '刷新完成,搜索到了$addedCount个Java(共有${totalList.length}个)'
: '刷新完成,未发现新的Java (共有${totalList.length}个)';

ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
} finally {
if (mounted) setState(() => _isRefreshing = false);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在刷新 Java 列表时,如果 Isolate.run 抛出异常(例如由于文件系统或注册表访问权限问题),异常将无法被捕获并导致应用崩溃。\n建议使用 try-catch 捕获异常,并在界面上向用户展示友好的错误提示(如 SnackBar)。

                        try {
                          // 在子线程获取数据
                          final searchResults = await Isolate.run(
                            () => JavaUtils.searchPotentialJavaExecutables(),
                          );

                          // 将javaRuntimes转换为Map
                          final Map<String, JavaRuntime> runtimeMap = {
                            for (var runtime in JavaService.javaRuntimes)
                              runtime.executable: runtime,
                          };

                          // 通过Map查重
                          int addedCount = 0;
                          for (var result in searchResults) {
                            if (!runtimeMap.containsKey(result.executable)) {
                              runtimeMap[result.executable] = result;
                              // 发现新Java时计数
                              addedCount++;
                            }
                          }

                          final totalList = runtimeMap.values.toList();

                          // 更新并写入
                          await JavaService.updateJavaRuntimes(totalList);

                          if (!mounted) return;

                          String message = addedCount > 0
                              ? '刷新完成,搜索到了$addedCount个Java(共有${totalList.length}个)'
                              : '刷新完成,未发现新的Java (共有${totalList.length}个)';

                          ScaffoldMessenger.of(
                            context,
                          ).showSnackBar(SnackBar(content: Text(message)));
                        } catch (e) {
                          if (mounted) {
                            ScaffoldMessenger.of(context).showSnackBar(
                              SnackBar(content: Text('刷新失败:$e')),
                            );
                          }
                        } finally {
                          if (mounted) setState(() => _isRefreshing = false);
                        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants