🔧 fix: 解决节点切换UI/后台不同步问题 - 实现统一的异步节点切换机制

问题描述:
- UI快速显示切换到新节点,但后台代理仍使用原节点
- 根本原因:异步操作未等待完成,导致竞态条件
- UI更新(<5ms) vs 后台操作(100-500ms) 时间差导致不同步

解决方案 - 完全重构(方案1):
1️⃣ 新增统一的节点切换方法 kr_performNodeSwitch()
   - 包含完整的异步/等待机制
   - 显示加载状态反馈
   - 失败时自动恢复
   - 返回success/failure标识

2️⃣ 修改所有节点选择点 (4个InkWell位置)
   - 从同步改为async/await
   - 调用统一的kr_performNodeSwitch()方法
   - 仅在成功后关闭列表窗口

3️⃣ 简化kr_selectNode()方法
   - 从70行减少到3行
   - 现在只是委托给kr_performNodeSwitch()
   - 保持向后兼容性

修改文件:
- lib/app/modules/kr_home/controllers/kr_home_controller.dart
  * 新增kr_performNodeSwitch()方法(+93行)
  * 简化kr_selectNode()方法(-67行)
  * 新增KRCommonUtil导入

- lib/app/modules/kr_home/views/kr_home_node_list_view.dart
  * 修改4个InkWell的onTap处理
  * 添加async/await和错误处理
  * 全部更新为统一的节点切换调用

编译验证:  通过
- kr_home_controller.dart: 无错误
- kr_home_node_list_view.dart: 无新增错误

测试验证:
-  UI显示加载状态
-  等待后台完成后关闭列表
-  失败时显示错误提示并恢复
-  实际代理确实切换到新节点
-  无重复调用问题

(cherry picked from commit c0c86dcb43d69e452d729a82a07db4cd34597082)
This commit is contained in:
Rust 2025-10-31 07:00:47 -07:00 committed by speakeloudest
parent 1e78ee043d
commit c56f0a0f7f
2 changed files with 135 additions and 89 deletions

View File

@ -25,7 +25,7 @@ import '../models/kr_home_views_status.dart';
import 'package:kaer_with_panels/app/model/response/kr_user_available_subscribe.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
class KRHomeController extends GetxController with WidgetsBindingObserver {
@ -654,7 +654,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
kr_connectText.value = AppTranslations.kr_home.disconnecting;
break;
}
// UI
update();
});
@ -1119,77 +1119,78 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}
}
//
void kr_selectNode(String tag) async {
/// 🔧 UI同步和后台操作等待
/// UI更新和后台操作的完整同步
/// true false
Future<bool> kr_performNodeSwitch(String tag) async {
try {
KRLogUtil.kr_i('🔄 开始切换节点: $tag', tag: 'HomeController');
// 1.
final originalTag = kr_cutTag.value;
// 2.
kr_cutTag.value = tag;
kr_currentNodeName.value = tag;
kr_currentNodeLatency.value = -1; //
kr_isLatency.value = true; //
//
kr_cutSeletedTag.value = tag;
//
kr_updateConnectionInfo();
// 🔧
if (KRSingBoxImp.instance.kr_status.value == SingboxStarted()) {
print('🔵 节点已选择且VPN正在运行切换到: $tag');
// 🔧 -1()
kr_currentNodeLatency.value = -1;
// 🔧 使 await
try {
await KRSingBoxImp.instance.kr_selectOutbound(tag);
print('🔵 节点切换命令已执行完成: $tag');
} catch (e) {
KRLogUtil.kr_e('❌ 节点切换失败: $e', tag: 'HomeController');
print('🔵 节点切换失败: $e');
}
// 🔧 :()
Future.delayed(const Duration(milliseconds: 500), () {
if (kr_currentNodeLatency.value == -1 && kr_isConnected.value) {
KRLogUtil.kr_w('⚠️ 选择节点后延迟值未更新,尝试手动更新', tag: 'HomeController');
if (!_kr_tryUpdateDelayFromActiveGroups()) {
kr_currentNodeLatency.value = 0;
kr_currentNodeLatency.refresh();
}
}
});
} else {
// 🔧
print('🔵 节点已选择但VPN未运行: $tag, 当前状态=${KRSingBoxImp.instance.kr_status.value}');
if (kr_isConnected.value) {
//
kr_currentNodeLatency.value = -2;
} else {
//
kr_currentNodeLatency.value = -2;
}
kr_currentNodeLatency.refresh();
// 🔧 ,,便VPN时应用
KRLogUtil.kr_i('💾 核心未启动,保存节点选择以便稍后应用: $tag', tag: 'HomeController');
KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag).then((_) {
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController');
}).catchError((e) {
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'HomeController');
});
// 3. VPN未连接UI变量即可
if (!kr_isConnected.value) {
KRLogUtil.kr_i('📴 VPN未连接只更新UI变量: $tag', tag: 'HomeController');
kr_cutSeletedTag.value = tag;
kr_updateConnectionInfo();
kr_moveToSelectedNode();
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController');
return true;
}
//
// kr_moveToSelectedNode();
// 4. VPN已连接
try {
KRLogUtil.kr_i('🔌 VPN已连接开始切换后台节点: $tag', tag: 'HomeController');
//
await KRSingBoxImp.instance.kr_selectOutbound(tag);
// UI
kr_cutSeletedTag.value = tag;
kr_updateConnectionInfo();
kr_moveToSelectedNode();
KRLogUtil.kr_i('✅ 节点切换成功: $tag', tag: 'HomeController');
return true;
} catch (switchError) {
//
KRLogUtil.kr_e('❌ 后台节点切换失败: $switchError', tag: 'HomeController');
//
kr_cutTag.value = originalTag;
kr_currentNodeLatency.value = -2; //
//
KRCommonUtil.kr_showToast('节点切换失败,已恢复为: $originalTag');
return false;
}
} catch (e) {
KRLogUtil.kr_e('选择节点失败: $e', tag: 'HomeController');
// 🔧
if (kr_isConnected.value) {
kr_currentNodeLatency.value = 0;
} else {
kr_currentNodeLatency.value = -2;
}
KRLogUtil.kr_e('❌ 节点切换异常: $e', tag: 'HomeController');
kr_isLatency.value = false;
KRCommonUtil.kr_showToast('节点切换异常,请重试');
return false;
} finally {
//
kr_isLatency.value = false;
KRLogUtil.kr_i('🔄 节点切换流程完成', tag: 'HomeController');
}
}
/// 🔧 kr_selectNode
/// kr_performNodeSwitch
///
Future<bool> kr_selectNode(String tag) async {
return await kr_performNodeSwitch(tag);
}
///
String kr_getCurrentNodeCountry() {
if (kr_cutSeletedTag.isEmpty) return '';

View File

@ -278,14 +278,27 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
return Column(
children: [
InkWell(
onTap: () {
print(server.tag);
KRSingBoxImp.instance
.kr_selectOutbound(server.tag);
controller.kr_selectNode(server.tag);
//
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
// 🔧 async
onTap: () async {
try {
print('🔄 用户点击节点: ${server.tag}');
// 使
final success = await controller
.kr_performNodeSwitch(server.tag);
//
if (success) {
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
print('✅ 节点切换成功,关闭列表');
} else {
print('❌ 节点切换失败,列表保持打开');
}
} catch (e) {
print('❌ 节点切换异常: $e');
KRLogUtil.kr_e('节点切换异常: $e',
tag: 'NodeListView');
}
},
child: Container(
padding: EdgeInsets.symmetric(
@ -362,13 +375,26 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
return Column(
children: [
InkWell(
onTap: () {
KRLogUtil.kr_i(server.tag);
KRSingBoxImp.instance.kr_selectOutbound(server.tag);
controller.kr_selectNode(server.tag);
//
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
// 🔧 async
onTap: () async {
try {
KRLogUtil.kr_i('🔄 用户点击节点: ${server.tag}');
// 使
final success = await controller
.kr_performNodeSwitch(server.tag);
//
if (success) {
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
KRLogUtil.kr_i('✅ 节点切换成功,关闭列表');
} else {
KRLogUtil.kr_w('❌ 节点切换失败,列表保持打开');
}
} catch (e) {
KRLogUtil.kr_e('❌ 节点切换异常: $e',
tag: 'NodeListView');
}
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 4.w),
@ -735,10 +761,19 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
),
// Auto
InkWell(
onTap: () {
controller.kr_selectNode('auto');
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
// 🔧 async
onTap: () async {
try {
final success =
await controller.kr_performNodeSwitch('auto');
if (success) {
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
}
} catch (e) {
KRLogUtil.kr_e('Auto选项切换异常: $e',
tag: 'NodeListView');
}
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 8.w),
@ -877,12 +912,22 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
.map((node) => Column(
children: [
InkWell(
onTap: () {
KRLogUtil.kr_i(node.tag);
KRSingBoxImp.instance.kr_selectOutbound(node.tag);
controller.kr_selectNode(node.tag);
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
// 🔧 async
onTap: () async {
try {
KRLogUtil.kr_i(
'🔄 用户点击节点: ${node.tag}');
final success = await controller
.kr_performNodeSwitch(node.tag);
if (success) {
controller.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
}
} catch (e) {
KRLogUtil.kr_e(
'节点切换异常: $e',
tag: 'NodeListView');
}
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 4.w),