- 添加完整的 GitHub Actions 构建指南文档 - BUILD_GUIDE.md: Android 详细构建指南 - MULTIPLATFORM_GUIDE.md: 多平台构建指南 - HOW_TO_BUILD.md: 分步操作教程 - QUICKSTART.md: 3步快速开始指南 - INDEX.md: 文档总览索引 - README.md: 基础说明 - 创建 docs/ 目录存放项目文档 - 锁定 libcore 子模块到 f993a57 (v3.1.7) - 防止在线编译时使用最新版本 - 确保构建稳定性和一致性
442 lines
8.4 KiB
Markdown
442 lines
8.4 KiB
Markdown
# Clash Meta 故障排查指南
|
|
|
|
## 常见运行时问题
|
|
|
|
### 问题 1: VPN 连接失败,日志显示 "PermissionMonitor error 22 (EINVAL)"
|
|
|
|
**症状:**
|
|
```
|
|
E/VPNService: PermissionMonitor error 22 (EINVAL)
|
|
E/Clash: TUN 设备启动失败
|
|
```
|
|
|
|
**原因:**
|
|
- VPN Builder 配置的路由规则无效
|
|
- 缺少 bypass-LAN 路由配置
|
|
- 路由 CIDR 格式错误
|
|
|
|
**解决方案:**
|
|
|
|
1. 检查 `getAndroidVpnOptions()` 返回的路由列表:
|
|
```dart
|
|
final options = await KRClashImp().getAndroidVpnOptions();
|
|
print('路由数量: ${options?['routeAddress']?.length}');
|
|
print('路由列表: ${options?['routeAddress']}');
|
|
```
|
|
|
|
2. 确认配置包含完整的 bypass-LAN 路由:
|
|
```yaml
|
|
# clash_config.yaml
|
|
tun:
|
|
route-exclude-address:
|
|
- 10.0.0.0/8
|
|
- 100.64.0.0/10
|
|
- 127.0.0.0/8
|
|
- 169.254.0.0/16
|
|
- 172.16.0.0/12
|
|
- 192.0.0.0/24
|
|
- 192.0.2.0/24
|
|
- 192.88.99.0/24
|
|
- 192.168.0.0/16
|
|
- 198.18.0.0/15
|
|
- 198.51.100.0/24
|
|
- 203.0.113.0/24
|
|
- 224.0.0.0/3
|
|
- fc00::/7
|
|
- fe80::/10
|
|
- ff00::/8
|
|
```
|
|
|
|
3. 验证 VPN Builder 配置:
|
|
```kotlin
|
|
// VPNService.kt
|
|
builder
|
|
.addAddress("172.19.0.1/30")
|
|
.addRoute("0.0.0.0/1") // ✅ 拆分默认路由
|
|
.addRoute("128.0.0.0/1") // ✅ 避免 0.0.0.0/0
|
|
.addRoute("10.0.0.0/8") // ✅ bypass-LAN
|
|
// ...
|
|
```
|
|
|
|
**参考:** `lib/app/services/clash_imp/kr_clash_imp.dart:192`
|
|
|
|
---
|
|
|
|
### 问题 2: FFI 初始化崩溃 "Go runtime already initialized"
|
|
|
|
**症状:**
|
|
```
|
|
F/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR)
|
|
E/Clash: Go runtime already initialized
|
|
```
|
|
|
|
**原因:**
|
|
- 多线程并发调用 `_ensureInitialized()`
|
|
- Go 运行时被重复初始化
|
|
|
|
**解决方案:**
|
|
|
|
已在 `kr_clash_imp.dart` 中修复,使用 `Completer` 实现并发安全:
|
|
```dart
|
|
Future<void> _ensureInitialized() async {
|
|
if (_initialized) return;
|
|
if (_initLock != null) {
|
|
await _initLock!.future; // ✅ 等待其他初始化完成
|
|
return;
|
|
}
|
|
_initLock = Completer<void>();
|
|
// ... 执行初始化
|
|
}
|
|
```
|
|
|
|
**验证修复:**
|
|
```dart
|
|
// 并发测试
|
|
await Future.wait([
|
|
KRClashImp().start(...),
|
|
KRClashImp().getAndroidVpnOptions(),
|
|
KRClashImp().getTraffic(),
|
|
]);
|
|
// ✅ 应该不会崩溃
|
|
```
|
|
|
|
**参考:** `lib/app/services/clash_imp/kr_clash_imp.dart:43`
|
|
|
|
---
|
|
|
|
### 问题 3: 模拟器无法联网 "Network unreachable"
|
|
|
|
**症状:**
|
|
```
|
|
E/Clash: 网络不可达
|
|
W/VPNService: 底层网络丢失
|
|
```
|
|
|
|
**原因:**
|
|
- 模拟器需要显式设置底层网络
|
|
- 缺少 `setUnderlyingNetworks()` 调用
|
|
|
|
**解决方案:**
|
|
|
|
已在 `VPNService.kt` 中实现:
|
|
```kotlin
|
|
// Android P+ 需要设置底层网络
|
|
private val defaultNetworkCallback by lazy {
|
|
object : ConnectivityManager.NetworkCallback() {
|
|
override fun onAvailable(network: Network) {
|
|
setUnderlyingNetworks(arrayOf(network)) // ✅ 关键!
|
|
}
|
|
}
|
|
}
|
|
|
|
// 注册回调
|
|
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback)
|
|
```
|
|
|
|
**验证修复:**
|
|
```bash
|
|
# 在 MuMu 模拟器测试
|
|
adb logcat | grep "底层网络"
|
|
# 应该看到: "底层网络可用: NetworkIdentity{...}"
|
|
```
|
|
|
|
**参考:** `android/.../bg/VPNService.kt:32-66`
|
|
|
|
---
|
|
|
|
### 问题 4: 配置文件解析失败 "invalid YAML"
|
|
|
|
**症状:**
|
|
```
|
|
E/Clash: 启动失败: yaml: unmarshal errors:
|
|
line 10: cannot unmarshal !!str `8388` into int
|
|
```
|
|
|
|
**原因:**
|
|
- YAML 类型不匹配 (字符串 vs 整数)
|
|
- 配置格式错误
|
|
|
|
**解决方案:**
|
|
|
|
检查 `ClashConfigGenerator.generate()`:
|
|
```dart
|
|
proxies:
|
|
- name: "Server-1"
|
|
type: ss
|
|
server: "example.com"
|
|
port: 8388 # ✅ 整数,不要引号
|
|
password: "password" # ✅ 字符串,使用引号
|
|
cipher: "aes-256-gcm"
|
|
```
|
|
|
|
**调试方法:**
|
|
```dart
|
|
// 打印生成的配置
|
|
final config = ClashConfigGenerator.generate(...);
|
|
print('配置预览:\n$config');
|
|
|
|
// 保存到文件手动检查
|
|
File('/tmp/clash_debug.yaml').writeAsStringSync(config);
|
|
```
|
|
|
|
**参考:** `lib/app/services/clash_imp/clash_config_generator.dart`
|
|
|
|
---
|
|
|
|
### 问题 5: "quickStart timeout" 启动超时
|
|
|
|
**症状:**
|
|
```
|
|
W/Clash: 启动超时 (10秒)
|
|
E/Clash: quickStart 未收到回调
|
|
```
|
|
|
|
**原因:**
|
|
- Go 核心启动耗时过长
|
|
- ReceivePort 未正确监听
|
|
- 回调被阻塞
|
|
|
|
**解决方案:**
|
|
|
|
1. 增加超时时间:
|
|
```dart
|
|
_isRunning = await completer.future.timeout(
|
|
const Duration(seconds: 20), // ✅ 增加到 20 秒
|
|
onTimeout: () {
|
|
print('⚠️ 启动超时');
|
|
return false;
|
|
},
|
|
);
|
|
```
|
|
|
|
2. 检查 Go 核心日志:
|
|
```kotlin
|
|
// ClashService.kt - 添加日志回调
|
|
tempMethodChannel.setMethodCallHandler { call, result ->
|
|
if (call.method == "log") {
|
|
Log.d(TAG, "Go 核心日志: ${call.arguments}")
|
|
}
|
|
}
|
|
```
|
|
|
|
3. 使用 Service Isolate 避免阻塞:
|
|
```kotlin
|
|
// ClashService.kt:102 - 已实现
|
|
serviceEngine?.dartExecutor?.executeDartEntrypoint(entrypoint)
|
|
```
|
|
|
|
**参考:** `lib/app/services/clash_imp/kr_clash_imp.dart:145`
|
|
|
|
---
|
|
|
|
### 问题 6: 流量统计不更新
|
|
|
|
**症状:**
|
|
```
|
|
I/Clash: 流量统计: {"upload": 0, "download": 0} # 始终为 0
|
|
```
|
|
|
|
**原因:**
|
|
- TUN 设备未正确启动
|
|
- 流量未通过代理
|
|
|
|
**解决方案:**
|
|
|
|
1. 确认 TUN 启动成功:
|
|
```dart
|
|
final tunStarted = await KRClashImp().startTun(fd, protectCallback);
|
|
print('TUN 启动状态: $tunStarted'); // 应该为 true
|
|
```
|
|
|
|
2. 检查路由配置:
|
|
```bash
|
|
# 在设备上检查路由表
|
|
adb shell "ip route show table all | grep tun"
|
|
```
|
|
|
|
3. 测试代理连接:
|
|
```bash
|
|
# 使用 curl 测试
|
|
adb shell "curl -v http://www.google.com"
|
|
# 应该看到代理日志
|
|
```
|
|
|
|
**参考:** `lib/app/services/clash_imp/kr_clash_imp.dart:261`
|
|
|
|
---
|
|
|
|
## 日志分析
|
|
|
|
### 启用详细日志
|
|
|
|
```dart
|
|
// lib/app/services/clash_imp/kr_clash_imp.dart
|
|
// 添加更详细的日志
|
|
print('📨 [Clash] 收到消息: ${jsonEncode(message)}');
|
|
print('📋 [Clash] 配置完整内容:\n$config');
|
|
```
|
|
|
|
### Android Logcat 过滤
|
|
|
|
```bash
|
|
# 只看 Clash 相关日志
|
|
adb logcat -s "A/Clash" "E/Clash" "W/Clash"
|
|
|
|
# 实时监控 FFI 调用
|
|
adb logcat | grep -E "(ClashFFI|quickStart|getAndroidVpnOptions)"
|
|
|
|
# 保存日志到文件
|
|
adb logcat -d > clash_debug.log
|
|
```
|
|
|
|
### Dart Observatory 调试
|
|
|
|
```bash
|
|
# 启用 Dart Observatory
|
|
flutter run --observatory-port=8181
|
|
|
|
# 在浏览器打开
|
|
open http://localhost:8181
|
|
|
|
# 查看 Isolate 状态
|
|
# - Main Isolate (UI)
|
|
# - Service Isolate (Clash 后台)
|
|
```
|
|
|
|
---
|
|
|
|
## 性能问题
|
|
|
|
### 内存泄漏
|
|
|
|
**症状:**
|
|
- 应用内存持续增长
|
|
- 最终 OOM 崩溃
|
|
|
|
**排查:**
|
|
```dart
|
|
// 确保调用 dispose()
|
|
@override
|
|
void dispose() {
|
|
KRClashImp().dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
// 检查 ReceivePort 是否关闭
|
|
receivePort.listen((message) {
|
|
// ...
|
|
receivePort.close(); // ✅ 必须关闭!
|
|
});
|
|
```
|
|
|
|
### CPU 占用过高
|
|
|
|
**症状:**
|
|
- 设备发热
|
|
- 电池消耗快
|
|
|
|
**排查:**
|
|
```bash
|
|
# 查看 CPU 占用
|
|
adb shell "top -m 10"
|
|
|
|
# 使用 Profiler
|
|
flutter run --profile
|
|
# DevTools → CPU Profiler
|
|
```
|
|
|
|
**优化:**
|
|
```dart
|
|
// 降低流量统计频率
|
|
Timer.periodic(const Duration(seconds: 2), (timer) { // ✅ 2秒而非1秒
|
|
final traffic = await KRClashImp().getTraffic();
|
|
// ...
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 崩溃分析
|
|
|
|
### 获取崩溃堆栈
|
|
|
|
```bash
|
|
# Native 崩溃
|
|
adb logcat -d | grep "backtrace"
|
|
|
|
# 使用 ndk-stack 解析
|
|
adb logcat | ndk-stack -sym android/app/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a
|
|
```
|
|
|
|
### 常见崩溃模式
|
|
|
|
#### SIGSEGV (段错误)
|
|
|
|
**原因:** FFI 指针使用错误
|
|
|
|
**示例:**
|
|
```dart
|
|
// ❌ 错误: 释放后使用
|
|
final ptr = 'hello'.toNativeUtf8();
|
|
malloc.free(ptr);
|
|
_ffi!.someFunction(ptr); // 崩溃!
|
|
|
|
// ✅ 正确: 使用后释放
|
|
final ptr = 'hello'.toNativeUtf8();
|
|
_ffi!.someFunction(ptr);
|
|
malloc.free(ptr);
|
|
```
|
|
|
|
#### SIGABRT (异常终止)
|
|
|
|
**原因:** Go panic 未恢复
|
|
|
|
**解决:** 在 Go 侧添加 recover:
|
|
```go
|
|
//export quickStart
|
|
func quickStart(...) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Println("Panic recovered:", r)
|
|
}
|
|
}()
|
|
// ...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 获取帮助
|
|
|
|
### 收集诊断信息
|
|
|
|
创建 Bug 报告时请包含:
|
|
|
|
1. **系统信息:**
|
|
```bash
|
|
adb shell "getprop ro.build.version.release" # Android 版本
|
|
adb shell "getprop ro.product.cpu.abi" # CPU 架构
|
|
```
|
|
|
|
2. **应用日志:**
|
|
```bash
|
|
adb logcat -d > full_log.txt
|
|
```
|
|
|
|
3. **配置文件:**
|
|
```dart
|
|
// 脱敏后的 clash_config.yaml
|
|
print(config.replaceAll(RegExp(r'password:.*'), 'password: ***'));
|
|
```
|
|
|
|
4. **复现步骤:**
|
|
- 详细操作流程
|
|
- 预期结果 vs 实际结果
|
|
- 复现概率
|
|
|
|
### 相关资源
|
|
|
|
- [GitHub Issues](https://github.com/your-repo/issues)
|
|
- [Clash Meta Wiki](https://wiki.metacubex.one/)
|
|
- [Flutter FFI 文档](https://dart.dev/guides/libraries/c-interop)
|