修复安卓bug
Some checks failed
Build Windows / build (push) Has been cancelled

This commit is contained in:
Rust 2025-10-03 15:20:49 +08:00
parent 866938abab
commit d87c58ac26
3 changed files with 213 additions and 3 deletions

View File

@ -0,0 +1,200 @@
# Android VPN 权限弹窗关闭后 UI 状态不同步问题修复
## 问题描述
在 Android 平台上,当用户第一次打开 App 后点击连接按钮时,系统会弹出 VPN 权限请求弹窗。如果用户点击取消或关闭这个弹窗,会出现以下问题:
- **现象**: UI 上的 Switch 开关没有正确回到关闭状态,仍然显示为打开
- **实际情况**: 节点已经尝试连接(但因权限被拒绝而失败)
- **用户体验**: UI 状态与实际连接状态不一致,造成困惑
## 问题根源分析
### 1. Android 原生层面
`android/app/src/main/kotlin/com/hiddify/hiddify/bg/VPNService.kt` 文件中:
```kotlin
override fun openTun(options: TunOptions): Int {
if (prepare(this) != null) error("android: missing vpn permission") // 第76行
// ...
}
```
当用户拒绝 VPN 权限时,`prepare(this)` 返回非空值,代码会抛出 "android: missing vpn permission" 错误。
### 2. Flutter 服务层面
`lib/app/services/singbox_imp/kr_sing_box_imp.dart` 文件中的 `kr_start()` 方法:
**修复前的问题**:
- 当启动失败时,虽然设置了 `kr_status.value = SingboxStopped()`
- 但没有强制触发状态观察者的更新
- 导致 UI 层的 `Obx` 观察者可能无法及时响应状态变化
### 3. Controller 层面
`lib/app/modules/kr_home/controllers/kr_home_controller.dart``kr_toggleSwitch()` 方法中:
**修复前的问题**:
- 异常捕获后只记录了日志
- 没有主动触发 UI 状态同步
- 导致 Switch 开关状态与实际连接状态不一致
## 修复方案
### 修改 1: 增强 SingBox 服务层的状态刷新
**文件**: `lib/app/services/singbox_imp/kr_sing_box_imp.dart:427-465`
**修改内容**:
```dart
Future<void> kr_start() async {
kr_status.value = SingboxStarting();
try {
// ... 启动逻辑 ...
await kr_singBox.start(_cutPath, kr_configName, false).map(
(r) {
KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox');
},
).mapLeft((err) {
KRLogUtil.kr_e('❌ SingBox 启动失败: $err', tag: 'SingBox');
// 确保状态重置为Stopped触发UI更新
kr_status.value = SingboxStopped();
// 强制刷新状态以触发观察者 ← 新增
kr_status.refresh();
throw err;
}).run();
} catch (e, stackTrace) {
KRLogUtil.kr_e('💥 SingBox 启动异常: $e', tag: 'SingBox');
KRLogUtil.kr_e('📚 错误堆栈: $stackTrace', tag: 'SingBox');
// 确保状态重置为Stopped触发UI更新
kr_status.value = SingboxStopped();
// 强制刷新状态以触发观察者 ← 新增
kr_status.refresh();
rethrow;
}
}
```
**关键改动**:
- 在两处异常处理中都添加了 `kr_status.refresh()` 调用
- 确保即使状态值相同(都是 Stopped也能强制触发观察者更新
### 修改 2: 增强 Controller 的异常处理
**文件**: `lib/app/modules/kr_home/controllers/kr_home_controller.dart:529-558`
**修改内容**:
```dart
void kr_toggleSwitch(bool value) async {
// 如果正在切换中,直接返回
if (kr_isSwitching) {
KRLogUtil.kr_i('正在切换中,忽略本次操作', tag: 'HomeController');
return;
}
try {
kr_isSwitching = true;
if (value) {
await KRSingBoxImp.instance.kr_start();
// 添加延迟验证,确保状态正确更新
Future.delayed(const Duration(seconds: 2), () {
kr_forceSyncConnectionStatus();
});
} else {
await KRSingBoxImp.instance.kr_stop();
}
} catch (e) {
KRLogUtil.kr_e('切换失败: $e', tag: 'HomeController');
// 当启动失败时如VPN权限被拒绝强制同步状态 ← 新增
Future.delayed(const Duration(milliseconds: 100), () {
kr_forceSyncConnectionStatus();
});
} finally {
// 确保在任何情况下都会重置标志
kr_isSwitching = false;
}
}
```
**关键改动**:
- 在 `catch` 块中添加了状态强制同步逻辑
- 延迟 100ms 调用 `kr_forceSyncConnectionStatus()` 确保状态同步到 UI
- 这样当 VPN 权限被拒绝导致启动失败时UI 会正确回退到断开状态
## 技术细节
### 状态流转过程
1. **用户点击 Switch 开启**
- UI 调用 `kr_toggleSwitch(true)`
- Controller 调用 `KRSingBoxImp.instance.kr_start()`
- SingBox 服务设置状态为 `SingboxStarting()`
2. **VPN 权限被拒绝场景**
- Android 原生层 `VPNService.openTun()` 抛出权限错误
- 错误传播到 SingBox 服务层
- `kr_start()` 的异常处理捕获错误
- 设置状态为 `SingboxStopped()` 并调用 `refresh()`
- Controller 的异常处理触发 `kr_forceSyncConnectionStatus()`
3. **UI 状态同步**
- `kr_status.refresh()` 强制触发 GetX 的 `Obx` 观察者
- `kr_forceSyncConnectionStatus()` 确保所有 UI 状态变量同步
- Switch 开关状态通过 `kr_isConnected.value` 回退到 `false`
### 为什么需要 refresh() ?
GetX 的响应式系统通过值比较来决定是否通知观察者:
- 如果 `kr_status.value``Starting` 变为 `Stopped`,会自动通知
- 但某些边界情况下,如果状态转换没有被正确捕获,调用 `refresh()` 可以强制通知所有观察者,不管值是否改变
### 为什么需要延迟调用?
- `Future.delayed(const Duration(milliseconds: 100))` 确保异步操作链完成
- 避免在状态还在转换过程中就尝试同步,导致读取到中间状态
## 测试验证
### 测试场景 1: 首次启动拒绝权限
1. 安装 App 后首次启动
2. 点击连接按钮
3. 系统弹出 VPN 权限请求
4. 点击"取消"或直接关闭弹窗
5. **预期结果**: Switch 自动回到关闭状态
### 测试场景 2: 二次启动(已授权)
1. 用户之前已授权 VPN 权限
2. 点击连接按钮
3. **预期结果**: 正常连接Switch 保持开启状态
### 测试场景 3: 撤销权限后重连
1. 在系统设置中撤销 VPN 权限
2. 返回 App 点击连接
3. 系统再次弹出权限请求
4. 拒绝权限
5. **预期结果**: Switch 自动回到关闭状态
## 影响范围
- **影响平台**: 仅 Android 平台
- **影响功能**: VPN 连接开关
- **向后兼容**: 完全兼容,不影响已有功能
- **性能影响**: 无明显性能影响,仅在异常情况下多执行一次状态同步
## 相关文件
1. `lib/app/services/singbox_imp/kr_sing_box_imp.dart` - SingBox 服务层
2. `lib/app/modules/kr_home/controllers/kr_home_controller.dart` - 主页控制器
3. `lib/app/modules/kr_home/views/kr_home_connection_info_view.dart` - Switch UI 组件
4. `android/app/src/main/kotlin/com/hiddify/hiddify/bg/VPNService.kt` - Android VPN 服务
## 修复日期
2025-10-03
## 修复作者
collins
OmnTech 提供技术支持

View File

@ -547,6 +547,10 @@ class KRHomeController extends GetxController {
}
} catch (e) {
KRLogUtil.kr_e('切换失败: $e', tag: 'HomeController');
// VPN权限被拒绝
Future.delayed(const Duration(milliseconds: 100), () {
kr_forceSyncConnectionStatus();
});
} finally {
//
kr_isSwitching = false;

View File

@ -447,13 +447,19 @@ class KRSingBoxImp {
},
).mapLeft((err) {
KRLogUtil.kr_e('❌ SingBox 启动失败: $err', tag: 'SingBox');
// StoppedUI更新
kr_status.value = SingboxStopped();
//
kr_status.refresh();
throw err;
}).run();
} catch (e, stackTrace) {
KRLogUtil.kr_e('💥 SingBox 启动异常: $e', tag: 'SingBox');
KRLogUtil.kr_e('📚 错误堆栈: $stackTrace', tag: 'SingBox');
// StoppedUI更新
kr_status.value = SingboxStopped();
//
kr_status.refresh();
rethrow;
}
}