Merge branch 'main' into feature/pangolin-ios
# Conflicts: # client/lib/bridge/vpn_bridge_provider.dart # docs/index.html
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
# Pangolin Android 客户端 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 把已有的 M2 Android PoC 推到真机能连真实节点出网,并补齐切节点 + KillSwitch(清 TODO 11G)。
|
||||
|
||||
**Architecture:** Flutter UI 全平台共享、零改动。Android 走 `VpnService` + 内嵌 sing-box `libbox.aar`(gomobile 绑定),经冻结的 `pangolin/vpn` MethodChannel/EventChannel 与 Dart 通信。配置由服务端渲染、客户端原样下发。本质是「先构建 aar → 对着真实 libbox API 修原生代码到能编译运行 → 接线 Dart → 真机端到端 → 补 11G」。
|
||||
|
||||
**Tech Stack:** Flutter 3.44 / Dart、Kotlin、Android `VpnService`、sing-box `v1.13.12` libbox(gomobile)、Go 1.24.3、Android NDK。
|
||||
|
||||
**设计依据:** `docs/android-client-design.html`、`docs/killswitch-design.html`。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- sing-box 内核版本 `v1.13.12`(`app/kernel/VERSION` 锚定,禁止脚本内硬编码)。
|
||||
- Go 工具链:用官方 `golang.org/dl/go1.24.3` 当启动器(本机 Go 1.26.1 不动);sing-box go.mod 为 `go 1.24.7`,GOTOOLCHAIN=auto 会自动下载并切到 **1.24.7** 编译。
|
||||
- **gomobile = `github.com/sagernet/gomobile@v0.1.12`**(SagerNet fork,非上游 `golang.org/x/mobile`;对照 sing-box v1.13.12 go.mod 与 `cmd/internal/build_libbox`)。原 VERSION 里的上游 pin 是失效 revision。
|
||||
- libbox bind ldflags 必须含 **`-checklinkname=0`**(放行 `badlinkname` 的 `//go:linkname`,Go 1.23+ 默认 checklinkname=1 会拒 `os.checkPidfdOnce` 等,缺则链接失败);build tags 含 `badlinkname,tfogo_checklinkname0`。
|
||||
- 构建需 **JDK**(gomobile bind 末段 javac):用 Android Studio JBR(OpenJDK 21);macOS `/usr/bin/javac` 仅桩。
|
||||
- 国内网络:`GOPROXY=https://goproxy.cn,direct` + `GOSUMDB=sum.golang.google.cn`(proxy.golang.org 连不上)。
|
||||
- Android 构建栈(Flutter 3.44 要求):**Gradle 8.7 + 声明式 plugins block + AGP 8.6.0 + Kotlin 2.2.0**(旧脚手架是 Gradle 8.0/AGP 8.1/命令式插件,全需升级迁移)。
|
||||
- libbox build tags:`with_quic,with_utls,with_clash_api,with_gvisor`(脚本默认,勿改)。
|
||||
- `minSdkVersion 21`(libbox `-androidapi 21` 要求),`targetSdkVersion` 跟随 flutter。
|
||||
- Channel 契约冻结(`pangolin/vpn` + `pangolin/vpn/status` + `pangolin/vpn/stats`),方法签名见 `client/lib/bridge/vpn_bridge.dart`,**不得私改**。
|
||||
- 配置来源:服务端 `POST /v1/nodes/:id/connect`,**客户端绝不拼配置**。
|
||||
- **禁止乐观显示**:连接键 `on` 必须由 `bridge.statusStream`(内核回调)确认,`start()` 返回 ≠ on。
|
||||
- 测试基线:x86_64 模拟器跑通 → Vivo X200(Android 16)主力真机 → 华为 HarmonyOS 4.2 兼容性抽查。
|
||||
- NDK:`/Users/wangjia/Library/Android/sdk/ndk/27.0.12077973`(构建前 export `ANDROID_NDK_HOME`)。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Go 1.24.3 工具链就位
|
||||
|
||||
**Files:** 无(环境配置)。
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: 可执行 `go1.24.3`(`$(go env GOPATH)/bin/go1.24.3`),供 Task 2 构建用。
|
||||
|
||||
- [ ] **Step 1: 安装官方 Go 1.24.3 多版本 shim**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
go install golang.org/dl/go1.24.3@latest
|
||||
go1.24.3 download
|
||||
```
|
||||
Expected: `Downloaded ... go1.24.3 ...`,无错误。
|
||||
|
||||
- [ ] **Step 2: 验证版本与 NDK**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
go1.24.3 version
|
||||
ls -d /Users/wangjia/Library/Android/sdk/ndk/27.0.12077973
|
||||
```
|
||||
Expected: `go version go1.24.3 darwin/arm64`;NDK 目录存在。
|
||||
|
||||
- [ ] **Step 3: 无需 commit**(纯环境,无文件改动)。
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 构建 libbox.aar(里程碑 A)
|
||||
|
||||
**Files:**
|
||||
- 产出: `app/kernel/dist/android/libbox.aar`(git 忽略产物,不入库)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 1 的 `go1.24.3`。
|
||||
- Produces: `libbox.aar`,含 `go.libbox.*` 类,供 Task 3 解析、Task 5 链接。
|
||||
|
||||
- [ ] **Step 1: 用 Go 1.24.3 跑构建脚本**
|
||||
|
||||
`build-android.sh` 内部用 `command -v go` 取 Go,需临时让 `go` 指向 1.24.3。用 PATH 注入 shim 目录的方式(不改脚本):
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android/app/kernel
|
||||
export ANDROID_NDK_HOME=/Users/wangjia/Library/Android/sdk/ndk/27.0.12077973
|
||||
GOROOT_1243="$(go1.24.3 env GOROOT)"
|
||||
PATH="${GOROOT_1243}/bin:${PATH}" go version # 确认此 shell 内 go=1.24.3
|
||||
PATH="${GOROOT_1243}/bin:${PATH}" bash build-android.sh
|
||||
```
|
||||
Expected: 末尾 `✓ 构建完成: .../dist/android/libbox.aar` + 三个 `.so`(arm64-v8a/armeabi-v7a/x86_64)列出。
|
||||
注意:首次会 `git clone sing-box`(约数分钟)+ `gomobile bind`(数分钟~十几分钟)。
|
||||
|
||||
- [ ] **Step 2: 验证产物三 ABI 齐**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
unzip -l app/kernel/dist/android/libbox.aar | grep -E '\.so$'
|
||||
```
|
||||
Expected: 至少 `jni/arm64-v8a/libgojni.so`、`jni/armeabi-v7a/...`、`jni/x86_64/...` 三条。
|
||||
|
||||
- [ ] **Step 3: 无需 commit**(aar 为忽略产物)。若 `gomobile bind` 失败,记录报错——多半是 Go/gomobile 组合问题,回到 Task 1 复核版本。
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 解析真实 libbox API(里程碑 B 发现)
|
||||
|
||||
**Files:**
|
||||
- 创建: `app/kernel/dist/android/libbox-api.txt`(javap 输出存档,便于 Task 5/9 对照;可入库或留作临时参考)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 2 的 `libbox.aar`。
|
||||
- Produces: `go.libbox` 关键类的真实方法签名清单(`Libbox`、`BoxService`、`TunOptions`、`PlatformInterface`、`CommandClient`、`CommandClientHandler`、`CommandClientOptions`、`StatusMessage`、`OutboundGroupIterator`)。
|
||||
|
||||
- [ ] **Step 1: 解包 aar 取 classes.jar,javap 导出签名**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android/app/kernel/dist/android
|
||||
mkdir -p .inspect && cd .inspect
|
||||
unzip -o ../libbox.aar classes.jar >/dev/null
|
||||
for c in Libbox BoxService TunOptions PlatformInterface CommandClient CommandClientHandler CommandClientOptions StatusMessage OutboundGroupIterator BoxService\$Companion; do
|
||||
echo "===== go.libbox.$c ====="
|
||||
javap -classpath classes.jar "go.libbox.$c" 2>/dev/null || echo "(类不存在,名称可能不同)"
|
||||
done | tee ../libbox-api.txt
|
||||
```
|
||||
Expected: 各类方法签名打印到屏幕与 `libbox-api.txt`。重点记录:
|
||||
- 创建 BoxService 的入口(`Libbox.newService` / `new BoxService(...)` / 参数个数与类型)。
|
||||
- `TunOptions` 的 getter 真名(`getInet4Address()` vs `inet4Address()`、`getMTU()`/`getMtu()`)。
|
||||
- `CommandClient` 选 outbound 的方法(如 `selectOutbound(group, tag)`)。
|
||||
- `StatusMessage` 流量字段(`getUploadTotal()` 等)。
|
||||
|
||||
- [ ] **Step 2: 比对现有 Kotlin 的「猜测点」**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -n "// libbox API" /Users/wangjia/code/pangolin/.claude/worktrees/feature+android/client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/PangolinVpnService.kt
|
||||
```
|
||||
Expected: 列出所有标注「libbox API」的行;逐条对照 `libbox-api.txt` 标记需改的方法名(用于 Task 5)。
|
||||
|
||||
- [ ] **Step 3: Commit(存档 API 清单)**
|
||||
|
||||
```bash
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android
|
||||
git add app/kernel/dist/android/libbox-api.txt
|
||||
git commit -m "docs(android): 存档 libbox.aar 真实 API 签名清单(Task 3)"
|
||||
```
|
||||
(若 `dist/` 被 .gitignore 忽略,改存到 `docs/` 下并相应调整路径。)
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Dart provider 接 Android → VpnNativeBridge
|
||||
|
||||
**Files:**
|
||||
- Modify: `client/lib/bridge/vpn_bridge_provider.dart`(约 27 行,加 Android 分派)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: 现有 `VpnNativeBridge`(`vpn_bridge.dart`,走 `pangolin/vpn` 通道)。
|
||||
- Produces: Android 运行时返回 `VpnNativeBridge` 而非 mock。
|
||||
|
||||
- [ ] **Step 1: 改平台分派**
|
||||
|
||||
把 `vpn_bridge_provider.dart` 中:
|
||||
```dart
|
||||
if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) {
|
||||
return DesktopVpnBridge();
|
||||
}
|
||||
```
|
||||
之前加入 Android 分支:
|
||||
```dart
|
||||
if (Platform.isAndroid) {
|
||||
return VpnNativeBridge();
|
||||
}
|
||||
```
|
||||
并把文件顶部注释「其他平台(iOS / Web / 测试)→ VpnBridgeMock」更新为「iOS / Web / 测试 → VpnBridgeMock;Android → VpnNativeBridge」。
|
||||
|
||||
- [ ] **Step 2: 静态分析**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android/client
|
||||
flutter analyze lib/bridge/vpn_bridge_provider.dart
|
||||
```
|
||||
Expected: `No issues found!`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android
|
||||
git add client/lib/bridge/vpn_bridge_provider.dart
|
||||
git commit -m "feat(android): Dart provider 接 Android→VpnNativeBridge(Task 4)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 修原生代码对齐真实 API 并编译通过(里程碑 B)
|
||||
|
||||
**Files:**
|
||||
- Modify: `client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/PangolinVpnService.kt`(按 Task 3 清单逐处校正方法名)。
|
||||
- Modify(按需): `MainActivity.kt`、`VpnEventBus.kt`。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 2 的 `libbox.aar`(`build.gradle` 已配 `implementation files(libboxAar)`)、Task 3 的 `libbox-api.txt`。
|
||||
- Produces: 可编译的 debug APK。
|
||||
|
||||
- [ ] **Step 1: 按 Task 3 清单校正 `PangolinVpnService.kt` 的 libbox 调用**
|
||||
|
||||
对照 `libbox-api.txt`,逐处替换标注 `// libbox API` 的方法名(`Libbox.newBoxService`/`service.start()`/`TunOptions.inet4Address()`/`mtu()`/`CommandClientOptions` 字段/`StatusMessage` getter 等)为真实签名。`TunOptions` getter 已有 try-catch 兜底,优先改成真名以免走兜底默认值。
|
||||
|
||||
- [ ] **Step 2: 构建 debug APK(只 arm64 加速首轮)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android/client
|
||||
flutter build apk --debug --target-platform android-arm64
|
||||
```
|
||||
Expected: `✓ Built build/app/outputs/flutter-apk/app-debug.apk`。若报 Kotlin 编译错(未知方法/类型不符)→ 回 Step 1 对照 `libbox-api.txt` 再修。
|
||||
|
||||
- [ ] **Step 3: 全量 analyze**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
flutter analyze
|
||||
```
|
||||
Expected: `No issues found!`(或仅既有无关告警)。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android
|
||||
git add client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/
|
||||
git commit -m "fix(android): PangolinVpnService 对齐真实 libbox API,编译通过(Task 5)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 模拟器冒烟——启动 + 通道连通
|
||||
|
||||
**Files:** 无代码改动(验证任务)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 4/5 产物。
|
||||
- Produces: 确认 app 在 x86_64 模拟器启动、通道注册、初始状态 `off`。
|
||||
|
||||
- [ ] **Step 1: 起 x86_64 模拟器并装运行**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
adb devices # 确认有 emulator-xxxx;无则先在 Android Studio 启一个 x86_64 AVD
|
||||
cd /Users/wangjia/code/pangolin/.claude/worktrees/feature+android/client
|
||||
flutter run -d emulator --debug # 或 flutter run 选模拟器
|
||||
```
|
||||
Expected: app 启动到登录/主页,无崩溃。
|
||||
|
||||
- [ ] **Step 2: 验证通道初始状态**
|
||||
|
||||
观察 logcat:
|
||||
```bash
|
||||
adb logcat -s PangolinMainActivity PangolinVpnService flutter | head -40
|
||||
```
|
||||
Expected: 见 `MethodChannel: getStatus`、status channel `onListen` 推 `off`,UI 连接键为 off 态。
|
||||
|
||||
- [ ] **Step 3: 无需 commit。**
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 模拟器端到端连通(里程碑 C)
|
||||
|
||||
**Files:** 无代码改动(验证任务;若发现 openTun/DNS 问题则回 Task 5 修)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: 服务端真实 config(经登录→选节点→`fetchConfig` 自动获取)。
|
||||
- Produces: 真隧道连通、可访问被墙站点。
|
||||
|
||||
- [ ] **Step 1: 登录并连接**
|
||||
|
||||
在 app 内登录(真账号)→ 选 RackNerd 节点 → 点连接。授予 VPN 授权弹窗。
|
||||
观察:
|
||||
```bash
|
||||
adb logcat -s PangolinVpnService | grep -E "openTun|BoxService started|postStatus"
|
||||
```
|
||||
Expected: `openTun: fd=...` → `BoxService started` → status `on`;连接键转 on(计时开始)。
|
||||
|
||||
- [ ] **Step 2: 验证真出网 + DNS(铁律)**
|
||||
|
||||
在模拟器浏览器或 `adb shell` 访问被墙站点:
|
||||
```bash
|
||||
adb shell curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" https://www.google.com
|
||||
```
|
||||
Expected: `200 ...`。若超时但能连其它国内站 → 多半 DNS 劫持规则缺失,检查服务端下发 config 的 `route.rules` 首条是否 `hijack-dns`。
|
||||
|
||||
- [ ] **Step 3: 断开验证**
|
||||
|
||||
点断开 → status `off`、连接键复位、`doStop` 日志干净(无重入崩溃)。
|
||||
|
||||
- [ ] **Step 4: 无代码则无 commit;若 Task 5 文件有修复则 commit。**
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 统计走字(里程碑 D)
|
||||
|
||||
**Files:** 验证为主;若字段不准则 Modify `PangolinVpnService.kt`(`writeStatus` 映射)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: libbox `CommandClient` STATUS(或 TrafficStats 兜底)。
|
||||
- Produces: `pangolin/vpn/stats` 每秒一帧,UI 统计页跳动。
|
||||
|
||||
- [ ] **Step 1: 观察 stats 帧**
|
||||
|
||||
连接状态下:
|
||||
```bash
|
||||
adb logcat -s PangolinVpnService | grep -E "CommandClient connected|TrafficStats fallback"
|
||||
```
|
||||
Expected: 见 `CommandClient connected`(优先)或兜底日志;UI 统计页上/下行字节与速率每秒更新。
|
||||
|
||||
- [ ] **Step 2: 校正字段(如需)**
|
||||
|
||||
若速率恒 0 或字节不增 → 对照 Task 3 的 `StatusMessage` getter 真名修 `writeStatus` 映射,重编重测。
|
||||
|
||||
- [ ] **Step 3: Commit(如有修复)**
|
||||
|
||||
```bash
|
||||
git add client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/PangolinVpnService.kt
|
||||
git commit -m "fix(android): 校正统计字段映射(Task 8)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: 切节点 selectOutbound + getActiveOutbound(里程碑 E / 11G)
|
||||
|
||||
**Files:**
|
||||
- Modify: `PangolinVpnService.kt`(加 libbox CommandClient group 选择 + 查询)。
|
||||
- Modify: `MainActivity.kt`(`selectOutbound`/`getActiveOutbound` 从 stub 改为转发到 Service)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 3 的 CommandClient 选 outbound API 签名;服务端 config 的出口组名(`auto` urltest + `reality-out`/`hy2-out`)。
|
||||
- Produces: `selectOutbound(tag)` 切换不断连;`getActiveOutbound()` 返回当前出口 tag。语义复刻桌面 `desktop_vpn_bridge.dart` 的 `selectProxy`/`getProxies`。
|
||||
|
||||
- [ ] **Step 1: 确认组名**
|
||||
|
||||
```bash
|
||||
grep -n "\"tag\"\|selector\|urltest\|proxyTags" /Users/wangjia/code/pangolin/.claude/worktrees/feature+android/server/internal/httpapi/clientconfig.go
|
||||
```
|
||||
Expected: 确认可选组名(当前为 urltest `auto`;如需手动选需服务端含 `selector` 组——若没有则 selectOutbound 作用于 `auto` 组内成员或触发服务端补 selector,按实际 config 决定)。
|
||||
|
||||
- [ ] **Step 2: 实现 Service 侧 selectOutbound/getActiveOutbound**
|
||||
|
||||
在 `PangolinVpnService` 暴露静态/单例方法,用 libbox `CommandClient`(按 Task 3 真名)执行 group 选择与查询;`MainActivity` 的对应 MethodChannel 分支从 stub 改为调用之。
|
||||
|
||||
- [ ] **Step 3: 编译 + 真机/模拟器验证**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd client && flutter build apk --debug --target-platform android-arm64
|
||||
```
|
||||
连接后在节点页切换出口:观察不断连、`getActiveOutbound` 返回值随之变、UI 高亮正确。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/
|
||||
git commit -m "feat(android): selectOutbound/getActiveOutbound 经 libbox CommandClient 切节点(Task 9,11G)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: KillSwitch L1 + 引导系统 Always-on(里程碑 F / 11G)
|
||||
|
||||
**Files:**
|
||||
- Modify: `MainActivity.kt`(`setKillSwitch` 从 stub 改为:缓存偏好 + 触发以 `strict_route` 重启隧道;并暴露跳系统 Always-on 设置的方法)。
|
||||
- Modify: `PangolinVpnService.kt`(启动时按偏好将 config TUN `strict_route` 置位——可复用桌面 `applyKillSwitchToConfig` 的等价 JSON 改写逻辑,在 Kotlin 侧实现)。
|
||||
- Modify(UI 文案): `client/lib/l10n/strings_zh.dart` + `strings_en.dart`(KillSwitch 旁诚实标注「彻底防泄漏需到系统设置开启 Always-on」)。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: 设计依据 `docs/killswitch-design.html`(L1 语义 + 引导 L3)。
|
||||
- Produces: `setKillSwitch(on)` 落 `strict_route`(与 Windows 一致)+ 系统 Always-on 引导入口。
|
||||
|
||||
- [ ] **Step 1: Kotlin 侧实现 strict_route 改写**
|
||||
|
||||
在 Service 启动前对 configJson 做 JSON 改写:`inbounds` 中 `type==tun` 的项设 `strict_route = on`。`setKillSwitch(on)` 若隧道在跑则以新偏好重启(短暂重连,与桌面一致)。
|
||||
|
||||
- [ ] **Step 2: Always-on 引导入口**
|
||||
|
||||
`setKillSwitch(on)` 为 true 时(或 UI 按钮)跳转系统 VPN 设置:
|
||||
```kotlin
|
||||
startActivity(Intent("android.settings.VPN_SETTINGS"))
|
||||
```
|
||||
(部分 ROM 落不到「Always-on」子页,落到 VPN 列表即可;华为/Vivo 单独适配留 Task 11。)
|
||||
|
||||
- [ ] **Step 3: UI 文案诚实标注**
|
||||
|
||||
在 KillSwitch 开关旁加说明文案(走 l10n,**无红线词**),明确 app 内 = L1、彻底防泄漏需系统 Always-on。
|
||||
|
||||
- [ ] **Step 4: 编译 + analyze + 验证**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd client && flutter build apk --debug --target-platform android-arm64 && flutter analyze
|
||||
```
|
||||
连接下开 KillSwitch:观察重连后 `strict_route` 生效(停内核即断网);点引导跳到系统 VPN 设置。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/ client/lib/l10n/
|
||||
git commit -m "feat(android): KillSwitch L1(strict_route)+引导系统 Always-on(Task 10,11G)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 11: 真机验证(Vivo X200 + 华为 4.2)
|
||||
|
||||
**Files:** 验证为主;厂商保活引导若需适配则 Modify `MainActivity.kt`。
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: 全部前序产物。
|
||||
- Produces: 真机端到端连通 + 后台保活验证 + 厂商电池优化引导适配。
|
||||
|
||||
- [ ] **Step 1: Vivo X200(Android 16,主力)全链路**
|
||||
|
||||
插线 `adb devices` 确认 → `flutter run -d <vivo> --release` 或装 release APK。跑通:登录→连接→访问被墙站→切节点→KillSwitch→断开。重点验证 **targetSdk 高版本下前台服务 specialUse 不被限**、后台挂 30 分钟不被杀(需在 Vivo 设置允许后台+自启动)。
|
||||
|
||||
- [ ] **Step 2: 华为 HarmonyOS 4.2(兼容抽查)**
|
||||
|
||||
装 APK 跑通连接主链路。华为后台保活更激进——验证电池优化引导 Intent 是否落对页面;若落不到,加华为定制 Intent 兜底(`Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` 之外的 ROM 专属页)。
|
||||
|
||||
- [ ] **Step 3: Commit(如有厂商适配)**
|
||||
|
||||
```bash
|
||||
git add client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/MainActivity.kt
|
||||
git commit -m "fix(android): 厂商(Vivo/华为)后台保活引导适配(Task 11)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 标记 todo #2 完成待验收**
|
||||
|
||||
```bash
|
||||
node ~/.claude/skills/todo/todo.mjs status 2 done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验收总览(对应里程碑)
|
||||
|
||||
| 里程碑 | Task | 完成判据 |
|
||||
|---|---|---|
|
||||
| A 构建内核 | 2 | libbox.aar 三 ABI 齐 |
|
||||
| B 编译链接 | 3–5 | debug APK 构建成功、analyze 零警告 |
|
||||
| C 端到端连通 | 6–7 | 模拟器实测科学上网、UI 三态正确 |
|
||||
| D 统计走字 | 8 | stats 每秒跳动且数值合理 |
|
||||
| E 切节点 | 9 | 切出口不断连、当前出口正确 |
|
||||
| F KillSwitch | 10 | strict_route 生效 + Always-on 引导 + 诚实文案 |
|
||||
| 真机 | 11 | Vivo X200 全链路 + 华为兼容抽查 + 后台保活 |
|
||||
Reference in New Issue
Block a user