目标终点:MVP 端到端连通 + 切节点 + KillSwitch(清掉 TODO 11G) · 分支 feature/android · 2026-06-22
之前的任务(commit 83c23f9 "feat(android): 实现 M2 Android VPN 隧道")已写好相当完整的原生层,但从未真正编译运行过。
pangolin/vpn MethodChannel + pangolin/vpn/status、pangolin/vpn/stats EventChannel),见 client/lib/bridge/vpn_bridge.dart。MainActivity.kt —— 通道注册 + VPN 授权流程(VpnService.prepare)+ 电池优化豁免引导。PangolinVpnService.kt —— VpnService + libbox BoxService 集成、openTun、统计(CommandClient + TrafficStats 兜底)、前台通知。VpnEventBus.kt —— Service↔Activity 状态总线(主线程回调)。AndroidManifest.xml —— VPN 权限、前台服务 specialUse(适配 Android 14)。build-android.sh —— gomobile 编译 sing-box → libbox.aar(sing-box v1.13.12 / Go 1.24.3 / gomobile pin v0.0.0-20240604…,tag with_quic,with_utls,with_clash_api,with_gvisor)。poc_config.json —— REALITY 配置模板。libbox.aar 没构建过 —— app/kernel/dist/android/ 为空。需要 Android NDK + gomobile(硬前置)。Libbox.newBoxService、TunOptions.inet4Address() 等从没被真实 aar 校验过)。vpn_bridge_provider.dart 目前把 Android 落到 VpnBridgeMock(只处理了 macOS/桌面)。selectOutbound / getActiveOutbound / setKillSwitch 标了 TODO 11G。最大未知是 libbox.aar 能不能构建出来、原生代码里那些「猜」的 API 名对不对。所以第一步就把这个雷踩掉,而不是最后才发现编译不过。
正确性锚点:官方 SagerNet/sing-box-for-android(SFA)的 Kotlin 源 + 构建出的 aar 里 classes.jar 的真实方法签名。现有 PangolinVpnService.kt 结构是照 SFA 写的,只是 API 名没校验过——拿真 aar 一比对即可定。
| 里程碑 | 目标 | 验收 |
|---|---|---|
| A. 构建内核 | 跑通 build-android.sh 产出 libbox.aar(arm64/arm/amd64,tag 含 with_clash_api) | aar 存在、unzip -l 三 ABI 的 .so 齐 |
| B. 编译链接 | Dart provider 接 Android→VpnNativeBridge;原生代码对着真 aar 修到 flutter build apk 通过 | APK 构建成功、flutter analyze 零警告 |
| C. 端到端连通 | 真机一键连 RackNerd 节点,TUN 起、DNS 不劫持失败、能打开被墙站点 | 真机实测科学上网成功;UI 三态正确(连接键 off→connecting→on 严格由内核回调驱动,禁止乐观显示) |
| D. 统计走字 | stats EventChannel 每秒推上/下行字节 + 速率 | 统计页数字跳动,与系统流量大致吻合 |
| E. 切节点(11G) | selectOutbound(tag) 经 libbox CommandClient 做 group 选择(复刻桌面 selectProxy);getActiveOutbound 查询当前出口 | 节点页切换出口不断连、当前出口高亮正确 |
| F. KillSwitch(11G) | setKillSwitch(on) 做 L1(strict_route)+ 引导系统 Always-on(L3) | 开关后内核停止即断网/恢复符合预期;UI 诚实标注「彻底防泄漏需到系统设置开启」 |
connect_api.fetchConfig 从服务端 POST /v1/nodes/:id/connect 取完整 sing-box config,原样传 bridge.start(configJson)。客户端绝不拼配置。PlatformInterface.openTun 建:libbox 回调里用 VpnService.Builder 配地址/MTU/路由/DNS → establish() 拿 fd。autoDetectInterfaceControl 走 VpnService.protect() 防环路。现有代码已写好,重点是校验 TunOptions 的真实 getter 名(已用 try-catch 兜底)。route.rules 首条必须是 {"action":"hijack-dns","port":[53]}——否则隧道连上也打不开网站。这是服务端职责,Android 侧只需确认下发的 config 带这条。CommandClient(command=STATUS, 1s);连不上退回 TrafficStats(按 UID)。现有代码已实现双路。clashApiClient.selectProxy(group, tag) 的语义,Android 用 libbox CommandClient 的 group 选择 API。组名对着服务端渲染的 config 确认(config 含 auto urltest 组 + reality-out/hy2-out 出口)。strict_route + 引导 Always-on:详见 §5 第 1 条与 KillSwitch 设计知识库。做「L1 + 引导 L3」的组合——setKillSwitch(on) 落 strict_route(与 Windows 桌面统一)+ 在 UI 诚实标注「彻底防泄漏需到系统设置开 Always-on」,并提供跳转引导。不假装 app 内能做到真 KillSwitch。 背景与分级见 KillSwitch 设计知识库。
原定「降 Go 保 pin」——但实测 pin 失效,真实情况:用 golang.org/dl/go1.24.3 当启动器(go.mod 为 go 1.24.7,自动切 1.24.7 编译);gomobile 改用官方 SagerNet fork github.com/sagernet/gomobile@v0.1.12(非上游,对照 sing-box go.mod 确认);bind 加 -checklinkname=0 + tags badlinkname,tfogo_checklinkname0;JDK 用 Android Studio JBR;国内 GOPROXY=goproxy.cn。Android 栈升 Gradle 8.7 + AGP 8.6 + Kotlin 2.2.0 + 声明式插件。详见实现计划。
先 x86_64 模拟器跑通(环境最干净、最贴官方验证、排除真机变量)→ Vivo X200(Android 16 / API 36,AOSP 系,主力真机) → 华为 HarmonyOS 4.2(兼容 Android 版,次要兼容性抽查)。华为后台保活激进、电池优化引导 Intent 需单独适配。
gomobile bind 失败,按定调降 Go 1.24.3(保 pin)。adb devices 为空)——里程碑 C 起需插真机/开模拟器。模拟器为 x86_64,aar 必须含 amd64(脚本已含)。.kt 方法名是猜的,里程碑 B 逐一对平(TunOptions getter 已 try-catch 兜底)。.srs 本地预取(现走远程 rule-set)。selectOutbound/getActiveOutbound/setKillSwitch)补齐——平行未完成,见 KillSwitch 知识库 与 todo #1,不在本次范围。| 主题 | 文件 |
|---|---|
| 桥接契约 | client/lib/bridge/vpn_bridge.dart |
| 平台分派(需加 Android→Native) | client/lib/bridge/vpn_bridge_provider.dart |
| 连接状态机(调 bridge.start) | client/lib/state/connection_provider.dart |
| 取 config | client/lib/services/connect_api.dart |
| Android 通道注册 | client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/MainActivity.kt |
| Android VPN 服务 | client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/PangolinVpnService.kt |
| 内核构建脚本 | app/kernel/build-android.sh |
| 内核版本锚点 | app/kernel/VERSION |
| 桌面参考实现(selectOutbound/killSwitch) | client/lib/bridge/desktop_vpn_bridge.dart |
| 服务端 config 渲染 | server/internal/httpapi/clientconfig.go |
| KillSwitch 设计知识库 | docs/killswitch-design.html |