feat: 初始化并发问题处理
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
Build Windows / build (push) Has been cancelled

This commit is contained in:
2025-11-13 05:15:18 -08:00
parent 748ec6bee9
commit a47174df56
5 changed files with 400 additions and 118 deletions
+240 -64
View File
@@ -107,6 +107,9 @@ class KRSingBoxImp {
/// 初始化标志,防止重复初始化
bool _kr_isInitialized = false;
/// 初始化进行中共享 Futuresingle-flight 防并发)
Future<void>? _kr_initFuture;
/// 当前混合代理端口是否就绪
bool get kr_isProxyReady => kr_status.value is SingboxStarted;
@@ -139,12 +142,23 @@ class KRSingBoxImp {
/// 初始化
Future<void> init() async {
// 防止重复初始化
// 防止重复初始化(已完成)
if (_kr_isInitialized) {
KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化', tag: 'SingBox');
return;
}
// 防止并发重复初始化(进行中复用同一个 Future)
if (_kr_initFuture != null) {
KRLogUtil.kr_i('SingBox 初始化进行中,等待完成(single-flight', tag: 'SingBox');
await _kr_initFuture;
return;
}
// 建立 single-flight 共享 Future
final completer = Completer<void>();
_kr_initFuture = completer.future;
try {
KRLogUtil.kr_i('开始初始化 SingBox');
// 在应用启动时初始化
@@ -167,16 +181,16 @@ class KRSingBoxImp {
KRLogUtil.kr_i('iOS 路径获取完成: $paths');
kr_configDics = (
baseDir: Directory(paths?["base"]! as String),
workingDir: Directory(paths?["working"]! as String),
tempDir: Directory(paths?["temp"]! as String),
baseDir: Directory(paths?["base"]! as String),
workingDir: Directory(paths?["working"]! as String),
tempDir: Directory(paths?["temp"]! as String),
);
} else {
final baseDir = await getApplicationSupportDirectory();
final workingDir =
Platform.isAndroid ? await getExternalStorageDirectory() : baseDir;
Platform.isAndroid ? await getExternalStorageDirectory() : baseDir;
final tempDir = await getTemporaryDirectory();
// Windows 路径规范化:确保使用正确的路径分隔符
Directory normalizePath(Directory dir) {
if (Platform.isWindows) {
@@ -188,11 +202,11 @@ class KRSingBoxImp {
}
return dir;
}
kr_configDics = (
baseDir: normalizePath(baseDir),
workingDir: normalizePath(workingDir!),
tempDir: normalizePath(tempDir),
baseDir: normalizePath(baseDir),
workingDir: normalizePath(workingDir!),
tempDir: normalizePath(tempDir),
);
KRLogUtil.kr_i('其他平台路径初始化完成');
}
@@ -201,7 +215,7 @@ class KRSingBoxImp {
KRLogUtil.kr_i('baseDir: ${kr_configDics.baseDir.path}', tag: 'SingBox');
KRLogUtil.kr_i('workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_i('tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox');
// 确保所有目录都存在
if (!kr_configDics.baseDir.existsSync()) {
await kr_configDics.baseDir.create(recursive: true);
@@ -215,11 +229,11 @@ class KRSingBoxImp {
await kr_configDics.tempDir.create(recursive: true);
KRLogUtil.kr_i('已创建 tempDir', tag: 'SingBox');
}
// 创建 libcore 数据库所需的 data 目录(在 workingDir 下)
// 注意:libcore 的 Setup 会调用 os.Chdir(workingPath),所以 data 目录必须在 workingDir 下
final dataDir = Directory(p.join(kr_configDics.workingDir.path, 'data'));
// 强制确保 data 目录存在(Windows 可能需要多次尝试)
int retryCount = 0;
const maxRetries = 5;
@@ -228,7 +242,7 @@ class KRSingBoxImp {
await dataDir.create(recursive: true);
// 等待文件系统同步(Windows 上可能需要一点时间)
await Future.delayed(const Duration(milliseconds: 100));
// 验证目录确实创建成功
if (dataDir.existsSync()) {
KRLogUtil.kr_i('✅ 已创建 data 目录: ${dataDir.path}', tag: 'SingBox');
@@ -248,13 +262,13 @@ class KRSingBoxImp {
await Future.delayed(Duration(milliseconds: delayMs));
}
}
if (!dataDir.existsSync()) {
final error = 'data 目录不存在: ${dataDir.path}';
KRLogUtil.kr_e('$error', tag: 'SingBox');
throw Exception(error);
}
// 验证目录权限(尝试创建一个测试文件)
try {
final testFile = File(p.join(dataDir.path, '.test_write'));
@@ -265,13 +279,13 @@ class KRSingBoxImp {
KRLogUtil.kr_e('⚠️ data 目录写入权限验证失败: $e', tag: 'SingBox');
// 不抛出异常,让 libcore 自己处理
}
// 在 Windows 上额外等待,确保文件系统操作完成
if (Platform.isWindows) {
await Future.delayed(const Duration(milliseconds: 300));
KRLogUtil.kr_i('⏳ Windows 文件系统同步等待完成', tag: 'SingBox');
}
// 最终验证:在 setup() 之前再次确认 workingDir 和 data 目录都存在且可访问
// libcore 的 Setup() 会调用 os.Chdir(workingPath),然后使用相对路径 "./data"
// 如果 os.Chdir() 失败(路径不存在或权限问题),后续的相对路径访问会失败
@@ -280,7 +294,7 @@ class KRSingBoxImp {
KRLogUtil.kr_e(error, tag: 'SingBox');
throw Exception(error);
}
// 验证 workingDir 可读可写
try {
final testWorkingFile = File(p.join(kr_configDics.workingDir.path, '.test_working_dir'));
@@ -292,7 +306,7 @@ class KRSingBoxImp {
KRLogUtil.kr_e(error, tag: 'SingBox');
throw Exception(error);
}
final finalDataDir = Directory(p.join(kr_configDics.workingDir.path, 'data'));
if (!finalDataDir.existsSync()) {
KRLogUtil.kr_e('❌ 最终验证失败:data 目录不存在', tag: 'SingBox');
@@ -308,7 +322,7 @@ class KRSingBoxImp {
}
throw Exception('data 目录在 setup() 前验证失败: ${finalDataDir.path}');
}
// 再次尝试写入测试,确保目录确实可用
try {
final verifyFile = File(p.join(finalDataDir.path, '.verify_setup'));
@@ -364,7 +378,7 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' - tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(' - data 目录: ${p.join(kr_configDics.workingDir.path, "data")}', tag: 'SingBox');
KRLogUtil.kr_i(' - data 目录存在: ${finalDataDir.existsSync()}', tag: 'SingBox');
// 在 Windows 上,列出 data 目录内容(如果有文件)
if (Platform.isWindows && finalDataDir.existsSync()) {
try {
@@ -378,10 +392,10 @@ class KRSingBoxImp {
KRLogUtil.kr_e(' - 无法列出 data 目录内容: $e', tag: 'SingBox');
}
}
KRLogUtil.kr_i(' - libcore 将通过 os.Chdir() 切换到: ${kr_configDics.workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(' - 然后使用相对路径 "./data" 访问数据库', tag: 'SingBox');
// Windows 特定:验证路径格式是否正确
if (Platform.isWindows) {
final workingPath = kr_configDics.workingDir.path;
@@ -400,11 +414,11 @@ class KRSingBoxImp {
KRLogUtil.kr_i('📡 开始调用 setup() 注册 FFI 端口', tag: 'SingBox');
final setupResult = await kr_singBox.setup(kr_configDics, false).run();
setupResult.match(
(error) {
(error) {
KRLogUtil.kr_e('❌ setup() 失败: $error', tag: 'SingBox');
throw Exception('FFI setup 失败: $error');
},
(_) {
(_) {
KRLogUtil.kr_i('✅ setup() 成功,FFI 端口已注册', tag: 'SingBox');
},
);
@@ -416,6 +430,10 @@ class KRSingBoxImp {
// 这样可以确保 UI 始终与 libcore 的实际状态同步
_kr_subscribeToStatus();
KRLogUtil.kr_i('✅ 状态订阅已设置', tag: 'SingBox');
// 完成 single-flight
completer.complete();
_kr_initFuture = null;
} catch (e, stackTrace) {
KRLogUtil.kr_e('❌ SingBox 初始化失败: $e');
KRLogUtil.kr_e('📚 错误堆栈: $stackTrace');
@@ -451,6 +469,11 @@ class KRSingBoxImp {
// 如果初始化失败,允许下次重试
_kr_isInitialized = false;
// 失败时通知等待者并清理 single-flight
if (!completer.isCompleted) {
completer.completeError(e, stackTrace);
}
_kr_initFuture = null;
rethrow;
}
}
@@ -464,13 +487,13 @@ class KRSingBoxImp {
"region": "other", // 参考 hiddify-app: 默认使用 "other" 跳过规则集下载
"block-ads": false, // 参考 hiddify-app: 默认关闭广告拦截
"use-xray-core-when-possible": false,
"execute-config-as-is": false,
"execute-config-as-is": true, // 🔧 修复:使用我们生成的完整配置,不让 libcore 重写
"log-level": "info", // 调试阶段使用 info,生产环境改为 warn
"resolve-destination": false,
"ipv6-mode": "ipv4_only", // 参考 hiddify-app: 仅使用 IPv4 (有效值: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
"remote-dns-address": "udp://1.1.1.1", // 参考 hiddify-app: 使用 Cloudflare DNS
"remote-dns-address": "https://dns.google/dns-query", // 使用 Google DoH,避免中转节点 DNS 死锁
"remote-dns-domain-strategy": "prefer_ipv4",
"direct-dns-address": "udp://1.1.1.1", // 参考 hiddify-app: 统一使用 1.1.1.1
"direct-dns-address": "local" ,
"direct-dns-domain-strategy": "prefer_ipv4",
"mixed-port": kr_port,
"tproxy-port": kr_port,
@@ -561,7 +584,7 @@ class KRSingBoxImp {
_kr_subscriptions.add(
kr_singBox.watchStatus().listen(
(status) {
(status) {
if (kDebugMode) {
print('🔵 收到 Native 状态更新: ${status.runtimeType}');
}
@@ -599,7 +622,7 @@ class KRSingBoxImp {
// 所以外层必须有 try-catch
final stream = kr_singBox.watchStats();
final subscription = stream.listen(
(stats) {
(stats) {
kr_stats.value = stats;
},
onError: (error) {
@@ -624,11 +647,11 @@ class KRSingBoxImp {
_kr_subscriptions.add(
kr_singBox.watchActiveGroups().listen(
(groups) {
(groups) {
print('[watchActiveGroups] 📡 收到活动组更新,数量: ${groups.length}');
KRLogUtil.kr_i('📡 收到活动组更新,数量: ${groups.length}', tag: 'SingBox');
kr_activeGroups.value = groups;
// 详细打印每个组的信息
for (int i = 0; i < groups.length; i++) {
final group = groups[i];
@@ -638,7 +661,7 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox');
}
}
KRLogUtil.kr_i('✅ 活动组处理完成', tag: 'SingBox');
},
onError: (error) {
@@ -651,7 +674,7 @@ class KRSingBoxImp {
_kr_subscriptions.add(
kr_singBox.watchGroups().listen(
(groups) {
(groups) {
print('[watchGroups] 📡 收到所有组更新,数量: ${groups.length}');
kr_allGroups.value = groups;
// 打印每个组的基本信息
@@ -679,7 +702,7 @@ class KRSingBoxImp {
// 查找 "select" 组
final selectGroup = kr_activeGroups.firstWhere(
(group) => group.tag == 'select',
(group) => group.tag == 'select',
orElse: () => throw Exception('未找到 "select" 选择器组'),
);
@@ -719,11 +742,11 @@ class KRSingBoxImp {
///
/// 确保 command.sock 准备好后再执行节点选择
Future<void> _kr_selectOutboundWithRetry(
String groupTag,
String outboundTag, {
int maxAttempts = 3,
int initialDelay = 100,
}) async {
String groupTag,
String outboundTag, {
int maxAttempts = 3,
int initialDelay = 100,
}) async {
int attempt = 0;
int delay = initialDelay;
@@ -1031,17 +1054,86 @@ class KRSingBoxImp {
KRLogUtil.kr_i('✅ 过滤后节点数量: ${kr_outbounds.length}/${outbounds.length}', tag: 'SingBox');
// 只保存 outboundsMobile.buildConfig() 会添加其他配置
final map = {
"outbounds": kr_outbounds
// 🔧 修复:生成完整的 SingBox 配置
// 之前只保存 {"outbounds": [...]}, 导致 libcore 无法正确处理
// 现在保存完整配置,包含所有必需字段
final Map<String, dynamic> fullConfig = {
"log": {
"level": "debug",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "dns-remote",
"address": "https://dns.google/dns-query",
"detour": "direct",
"address_resolver": "dns-direct"
},
{
"tag": "dns-direct",
"address": "local",
"detour": "direct"
}
],
"rules": [],
"final": "dns-remote",
"strategy": "prefer_ipv4"
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "utun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"strict_route": true,
"sniff": true,
"sniff_override_destination": false
}
],
"outbounds": [
// 🔧 修复:添加 selector 组,让用户可以手动选择节点
{
"type": "selector",
"tag": "proxy",
"outbounds": kr_outbounds.map((o) => o['tag'] as String).toList(),
"default": kr_outbounds.isNotEmpty ? kr_outbounds[0]['tag'] : "direct",
},
...kr_outbounds,
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{ "type": "dns", "outbound": "dns-out" }, // sing-box 内部解析交给 dns-out
{ "type": "field", "port": 53, "network": "udp", "outbound": "direct" }, // 系统 UDP 53 直连
{ "type": "field", "port": 53, "network": "tcp", "outbound": "direct" }, // 系统 TCP 53 直连
{ "type": "field", "port": 853, "network": "tcp", "outbound": "direct" }, // 系统 TCP 853Private DNS)直连
{ "type": "field", "domain": ["dns.google", "cloudflare-dns.com", "one.one.one.one"], "outbound": "direct" } // 常见 DoH 域名直连,避免误走代理
],
"final": "proxy", // 🔧 修复:使用 selector 组作为默认出站
"auto_detect_interface": true
}
};
final file = _file(kr_configName);
final temp = _tempFile(kr_configName);
final mapStr = jsonEncode(map);
final mapStr = jsonEncode(fullConfig);
KRLogUtil.kr_i('📄 配置文件内容长度: ${mapStr.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 完整配置文件内容: $mapStr', tag: 'SingBox');
KRLogUtil.kr_i('📄 完整配置文件长度: ${mapStr.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 Outbounds 数量: ${kr_outbounds.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 配置前800字符:\n${mapStr.substring(0, mapStr.length > 800 ? 800 : mapStr.length)}', tag: 'SingBox');
await file.writeAsString(mapStr);
await temp.writeAsString(mapStr);
@@ -1089,11 +1181,11 @@ class KRSingBoxImp {
final oOption = SingboxConfigOption.fromJson(_getConfigOption());
final changeResult = await kr_singBox.changeOptions(oOption).run();
changeResult.match(
(error) {
(error) {
KRLogUtil.kr_e('❌ changeOptions() 失败: $error', tag: 'SingBox');
throw Exception('初始化 HiddifyOptions 失败: $error');
},
(_) {
(_) {
KRLogUtil.kr_i('✅ HiddifyOptions 初始化成功', tag: 'SingBox');
},
);
@@ -1112,7 +1204,7 @@ class KRSingBoxImp {
_kr_subscribeToStatus();
await kr_singBox.start(_cutPath, kr_configName, false).map(
(r) {
(r) {
KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox');
},
).mapLeft((err) {
@@ -1316,9 +1408,9 @@ class KRSingBoxImp {
mode = KRCountryUtil.kr_getCurrentCountryCode();
KRLogUtil.kr_i('🎯 切换到规则代理模式: $mode', tag: 'SingBox');
break;
// case KRConnectionType.direct:
// mode = "direct";
// break;
// case KRConnectionType.direct:
// mode = "direct";
// break;
}
oOption["region"] = mode;
KRLogUtil.kr_i('📝 更新 region 配置: $mode', tag: 'SingBox');
@@ -1454,8 +1546,8 @@ class KRSingBoxImp {
// 使用 then/catchError 避免异常导致 UI 阻塞
kr_singBox.selectOutbound("select", tag).run().then((result) {
result.match(
(error) => KRLogUtil.kr_w('🔁 定时器重选节点失败: $error', tag: 'SingBox'),
(_) => KRLogUtil.kr_d('🔁 定时器重选节点成功', tag: 'SingBox'),
(error) => KRLogUtil.kr_w('🔁 定时器重选节点失败: $error', tag: 'SingBox'),
(_) => KRLogUtil.kr_d('🔁 定时器重选节点成功', tag: 'SingBox'),
);
}).catchError((error) {
KRLogUtil.kr_w('🔁 定时器重选节点异常: $error', tag: 'SingBox');
@@ -1473,13 +1565,45 @@ class KRSingBoxImp {
if (country == 'auto') {
KRLogUtil.kr_i('🌀 国家为 auto,执行自动节点选择逻辑', tag: 'SingBox');
await kr_selectOutbound('auto');
try {
// 保存国家为 auto,并重建/保存全部节点
await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: 'auto');
await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: 'auto');
await kr_selectOutbound('auto');
_nodeSelectionTimer?.cancel();
_nodeSelectionTimer = null;
KRLogUtil.kr_i('✅ 已切换为 auto 节点选择', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ auto 国家选择失败: $e', tag: 'SingBox');
rethrow;
}
return;
}
try {
await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: country);
final oOption = _getConfigOption();
oOption["region"] = country;
KRLogUtil.kr_i('📝 更新 region 配置为国家: $country', tag: 'SingBox');
final op = SingboxConfigOption.fromJson(oOption);
await kr_singBox.changeOptions(op)
..map((r) {
KRLogUtil.kr_i('✅ 国家 region 更新成功: $country', tag: 'SingBox');
}).mapLeft((err) {
KRLogUtil.kr_e('❌ 更新国家 region 失败: $err', tag: 'SingBox');
throw err;
}).run();
// 设置为 auto 以便在该国集合内自动选最快
await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: 'auto');
KRLogUtil.kr_i('ℹ️ 已应用国家到配置,等待控制器执行重启', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ 国家选择流程失败: $e', tag: 'SingBox');
rethrow;
}
if (kr_activeGroups.isEmpty) {
KRLogUtil.kr_w('⚠️ kr_activeGroups 当前为空,无法进行国家选择', tag: 'SingBox');
return;
KRLogUtil.kr_w('⚠️ kr_activeGroups 当前为空,国家选择后将由重启同步分组', tag: 'SingBox');
}
}
@@ -1500,7 +1624,7 @@ class KRSingBoxImp {
Future<void> kr_urlTest(String groupTag) async {
KRLogUtil.kr_i('🧪 开始 URL 测试: $groupTag', tag: 'SingBox');
KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox');
// 打印所有活动组信息
for (int i = 0; i < kr_activeGroups.length; i++) {
final group = kr_activeGroups[i];
@@ -1510,15 +1634,15 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox');
}
}
try {
KRLogUtil.kr_i('🚀 调用 SingBox URL 测试 API...', tag: 'SingBox');
final result = await kr_singBox.urlTest(groupTag).run();
KRLogUtil.kr_i('✅ URL 测试完成: $groupTag, 结果: $result', tag: 'SingBox');
// 等待一段时间让 SingBox 完成测试
await Future.delayed(const Duration(seconds: 2));
// 再次检查活动组状态
KRLogUtil.kr_i('🔄 测试后活动组状态检查:', tag: 'SingBox');
for (int i = 0; i < kr_activeGroups.length; i++) {
@@ -1534,4 +1658,56 @@ class KRSingBoxImp {
KRLogUtil.kr_e('📚 错误详情: ${e.toString()}', tag: 'SingBox');
}
}
}
Future<void> kr_debugTunConnectivity() async {
KRLogUtil.kr_i('🧪 [TUN Debug] 开始调试', tag: 'SingBoxTun');
final statusType = kr_status.value.runtimeType.toString();
KRLogUtil.kr_i('🔎 当前状态: $statusType', tag: 'SingBoxTun');
final selectedCountry = await KRSecureStorage().kr_readData(key: 'SELECTED_COUNTRY_TAG');
final selectedNode = await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('🎯 选择: country=$selectedCountry, node=$selectedNode', tag: 'SingBox');
final activeTags = kr_activeGroups.map((g) => g.tag).join(', ');
KRLogUtil.kr_i('🧩 分组: all=${kr_allGroups.length}, active=${kr_activeGroups.length} [$activeTags]', tag: 'SingBoxTun');
// 1) TCP 到 1.1.1.1:443(避免纯 IP HTTP 被对端重置)
try {
final sock = await Socket.connect('1.1.1.1', 443, timeout: const Duration(seconds: 3));
sock.destroy();
KRLogUtil.kr_i('🌐 TCP 443连接成功 (1.1.1.1:443)', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_e('🌐 TCP 443连接失败: $e', tag: 'SingBoxTun');
}
// 2) HTTPS 测试(使用 Google 204 连接性检测,更通用更稳健)
try {
final client = HttpClient()..connectionTimeout = const Duration(seconds: 3);
final req = await client.getUrl(Uri.parse('https://connectivitycheck.gstatic.com/generate_204'));
req.headers.add('User-Agent', 'kr-debug');
final resp = await req.close();
KRLogUtil.kr_i('🔐 HTTPS 204 状态: ${resp.statusCode}', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_e('🔐 HTTPS 204 错误: $e', tag: 'SingBoxTun');
}
// 3) 系统DNS解析(走系统解析器)
try {
final addrs = await InternetAddress.lookup('google.com');
KRLogUtil.kr_i('🧭 DNS解析成功: ${addrs.map((a) => a.address).join(", ")}', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_e('🧭 DNS解析错误: $e', tag: 'SingBoxTun');
}
// 4) TCP 53检测(公共DNS TCP53可达性)
try {
final sock = await Socket.connect('8.8.8.8', 53, timeout: const Duration(seconds: 3));
sock.destroy();
KRLogUtil.kr_i('🛰️ TCP 53连接成功 (8.8.8.8:53)', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_w('🛰️ TCP 53连接失败: $e', tag: 'SingBoxTun');
}
KRLogUtil.kr_i('✅ [TUN Debug] 结束调试', tag: 'SingBoxTun');
}
}