← 文档索引

Pangolin Android 客户端设计方案

目标终点:MVP 端到端连通 + 切节点 + KillSwitch(清掉 TODO 11G) · 分支 feature/android · 2026-06-22

一句话定性:不是从零开发,而是把已有的 M2 纸面 PoC 推到真机能连真实节点出网,并补齐切节点 + KillSwitch(清掉 TODO 11G)。Flutter UI 全平台共享、本次零改动。

1. 现状盘点

之前的任务(commit 83c23f9 "feat(android): 实现 M2 Android VPN 隧道")已写好相当完整的原生层,但从未真正编译运行过

已具备 现成

缺口(让它真正能跑要补的)待补

  1. libbox.aar 没构建过 —— app/kernel/dist/android/ 为空。需要 Android NDK + gomobile(硬前置)。
  2. 原生 libbox API 名是「猜」的 —— 代码里多处注释「若编译失败请对照实际 libbox API 微调」(Libbox.newBoxServiceTunOptions.inet4Address() 等从没被真实 aar 校验过)。
  3. Dart 侧没接线 —— vpn_bridge_provider.dart 目前把 Android 落到 VpnBridgeMock(只处理了 macOS/桌面)。
  4. 三个 stub —— selectOutbound / getActiveOutbound / setKillSwitch 标了 TODO 11G。
  5. 从没在真机/模拟器上端到端连过。

2. 整体策略:自底向上「先构建,再对着真实 API 修」

最大未知是 libbox.aar 能不能构建出来、原生代码里那些「猜」的 API 名对不对。所以第一步就把这个雷踩掉,而不是最后才发现编译不过。

正确性锚点:官方 SagerNet/sing-box-for-android(SFA)的 Kotlin 源 + 构建出的 aar 里 classes.jar 的真实方法签名。现有 PangolinVpnService.kt 结构是照 SFA 写的,只是 API 名没校验过——拿真 aar 一比对即可定。

3. 里程碑(垂直切片,每片真机可验)

里程碑目标验收
A. 构建内核跑通 build-android.sh 产出 libbox.aar(arm64/arm/amd64,tag 含 with_clash_apiaar 存在、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 诚实标注「彻底防泄漏需到系统设置开启」

4. 关键技术决策(与桌面/契约对齐,不自创)

5. 三处定调(用户已确认)

① KillSwitch(里程碑 F)

做「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 + 声明式插件。详见实现计划。

③ 测试基线(里程碑 C)

先 x86_64 模拟器跑通(环境最干净、最贴官方验证、排除真机变量)→ Vivo X200(Android 16 / API 36,AOSP 系,主力真机)华为 HarmonyOS 4.2(兼容 Android 版,次要兼容性抽查)。华为后台保活激进、电池优化引导 Intent 需单独适配。

6. 已知风险 / 坑

  1. Go 1.26 vs gomobile 旧 pin:若 gomobile bind 失败,按定调降 Go 1.24.3(保 pin)。
  2. 当前没有设备连着adb devices 为空)——里程碑 C 起需插真机/开模拟器。模拟器为 x86_64,aar 必须含 amd64(脚本已含)。
  3. libbox API 漂移:现有 .kt 方法名是猜的,里程碑 B 逐一对平(TunOptions getter 已 try-catch 兜底)。
  4. Android 14/16 前台服务 specialUse:manifest 已声明;targetSdk 拉到 36 时复测 FGS 启动是否被限。国内分发免审;未来上 Play 需补用途说明。
  5. 电池优化 / 厂商保活:现有代码会弹豁免引导;华为/Vivo 后台管理激进,需引导用户允许后台 + 自启动,且厂商设置页深链可能落不准。

7. 不在本次范围(YAGNI)

8. 参考位置索引

主题文件
桥接契约client/lib/bridge/vpn_bridge.dart
平台分派(需加 Android→Native)client/lib/bridge/vpn_bridge_provider.dart
连接状态机(调 bridge.start)client/lib/state/connection_provider.dart
取 configclient/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