docs(android): Android 设计方案+KillSwitch 知识库(HTML)+文档索引+实现计划

- docs/android-client-design.html / killswitch-design.html / index.html
- docs/superpowers/plans/2026-06-22-android-client.md
- todo/ KillSwitch 三端待办(#1 mac/#2 Android/#3 Windows)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-22 18:44:50 +08:00
parent eea851737f
commit 64c427cf11
6 changed files with 1579 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pangolin Android 客户端设计方案</title>
<style>
:root{
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
.wrap{max-width:920px;margin:0 auto;padding:48px 24px 96px}
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
h2{font-size:21px;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
h3{font-size:16px;margin:28px 0 8px;color:var(--accent2)}
p{margin:10px 0}
code{font-family:var(--mono);font-size:.88em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:13px;line-height:1.55;color:#cdd3df}
pre .c{color:#6b7385}
pre .r{color:var(--bad)}
pre .g{color:var(--ok)}
pre .y{color:var(--warn)}
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:16px 0}
.card.root{border-left:3px solid var(--accent)}
.card h3{margin-top:0}
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:14px}
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
th{color:var(--fg2);font-weight:600;font-size:13px}
td code{font-size:.85em}
.ok-c{color:var(--ok)} .bad-c{color:var(--bad)} .warn-c{color:var(--warn)}
ul,ol{padding-left:22px;margin:10px 0}
li{margin:5px 0}
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
.kbd{font-family:var(--mono);font-size:.85em;color:var(--accent)}
.small{color:var(--fg2);font-size:13px}
hr{border:none;border-top:1px solid var(--border);margin:40px 0}
a{color:var(--accent2)}
.back{display:inline-block;margin-bottom:24px;font-size:13px}
</style>
</head>
<body>
<div class="wrap">
<a class="back" href="index.html">← 文档索引</a>
<h1>Pangolin Android 客户端设计方案</h1>
<p class="sub">目标终点:<b>MVP 端到端连通 + 切节点 + KillSwitch(清掉 TODO 11G</b> · 分支 <code>feature/android</code> · 2026-06-22</p>
<div class="lead">
<strong>一句话定性:</strong><b>不是从零开发</b>,而是把已有的 <b>M2 纸面 PoC 推到真机能连真实节点出网</b>,并补齐切节点 + KillSwitch(清掉 TODO 11G)。Flutter UI 全平台共享、本次零改动。
</div>
<h2>1. 现状盘点</h2>
<p>之前的任务(commit <code>83c23f9</code> "feat(android): 实现 M2 Android VPN 隧道")已写好相当完整的原生层,但<b>从未真正编译运行过</b></p>
<div class="card">
<h3>已具备 <span class="tag ok">现成</span></h3>
<ul>
<li>Flutter UI 全平台共享(连接键三态 / 节点 / 统计 / 账户都现成,Android 直接复用)。</li>
<li>Channel 契约已冻结(<code>pangolin/vpn</code> MethodChannel + <code>pangolin/vpn/status</code><code>pangolin/vpn/stats</code> EventChannel),见 <code>client/lib/bridge/vpn_bridge.dart</code></li>
<li><code>MainActivity.kt</code> —— 通道注册 + VPN 授权流程(<code>VpnService.prepare</code>+ 电池优化豁免引导。</li>
<li><code>PangolinVpnService.kt</code> —— <code>VpnService</code> + libbox <code>BoxService</code> 集成、<code>openTun</code>、统计(CommandClient + TrafficStats 兜底)、前台通知。</li>
<li><code>VpnEventBus.kt</code> —— Service↔Activity 状态总线(主线程回调)。</li>
<li><code>AndroidManifest.xml</code> —— VPN 权限、前台服务 <code>specialUse</code>(适配 Android 14)。</li>
<li><code>build-android.sh</code> —— gomobile 编译 sing-box → <code>libbox.aar</code>sing-box <code>v1.13.12</code> / Go <code>1.24.3</code> / gomobile pin <code>v0.0.0-20240604…</code>tag <code>with_quic,with_utls,with_clash_api,with_gvisor</code>)。</li>
<li><code>poc_config.json</code> —— REALITY 配置模板。</li>
</ul>
</div>
<div class="card">
<h3>缺口(让它真正能跑要补的)<span class="tag bad">待补</span></h3>
<ol>
<li><b><code>libbox.aar</code> 没构建过</b> —— <code>app/kernel/dist/android/</code> 为空。需要 Android NDK + gomobile(硬前置)。</li>
<li><b>原生 libbox API 名是「猜」的</b> —— 代码里多处注释「若编译失败请对照实际 libbox API 微调」(<code>Libbox.newBoxService</code><code>TunOptions.inet4Address()</code> 等从没被真实 aar 校验过)。</li>
<li><b>Dart 侧没接线</b> —— <code>vpn_bridge_provider.dart</code> 目前把 Android 落到 <code>VpnBridgeMock</code>(只处理了 macOS/桌面)。</li>
<li><b>三个 stub</b> —— <code>selectOutbound</code> / <code>getActiveOutbound</code> / <code>setKillSwitch</code> 标了 TODO 11G。</li>
<li><b>从没在真机/模拟器上端到端连过。</b></li>
</ol>
</div>
<h2>2. 整体策略:自底向上「先构建,再对着真实 API 修」</h2>
<p>最大未知是 <b>libbox.aar 能不能构建出来、原生代码里那些「猜」的 API 名对不对</b>。所以第一步就把这个雷踩掉,而不是最后才发现编译不过。</p>
<p><b>正确性锚点</b>:官方 <code>SagerNet/sing-box-for-android</code>SFA)的 Kotlin 源 + 构建出的 aar 里 <code>classes.jar</code> 的真实方法签名。现有 <code>PangolinVpnService.kt</code> 结构是照 SFA 写的,只是 API 名没校验过——拿真 aar 一比对即可定。</p>
<h2>3. 里程碑(垂直切片,每片真机可验)</h2>
<table>
<thead><tr><th>里程碑</th><th>目标</th><th>验收</th></tr></thead>
<tbody>
<tr><td><b>A. 构建内核</b></td><td>跑通 <code>build-android.sh</code> 产出 <code>libbox.aar</code>arm64/arm/amd64tag 含 <code>with_clash_api</code></td><td>aar 存在、<code>unzip -l</code> 三 ABI 的 <code>.so</code></td></tr>
<tr><td><b>B. 编译链接</b></td><td>Dart provider 接 Android→<code>VpnNativeBridge</code>;原生代码对着真 aar 修到 <code>flutter build apk</code> 通过</td><td>APK 构建成功、<code>flutter analyze</code> 零警告</td></tr>
<tr><td><b>C. 端到端连通</b></td><td>真机一键连 RackNerd 节点,TUN 起、DNS 不劫持失败、能打开被墙站点</td><td>真机实测科学上网成功;UI 三态正确(连接键 off→connecting→on <b>严格由内核回调驱动,禁止乐观显示</b></td></tr>
<tr><td><b>D. 统计走字</b></td><td>stats EventChannel 每秒推上/下行字节 + 速率</td><td>统计页数字跳动,与系统流量大致吻合</td></tr>
<tr><td><b>E. 切节点(11G</b></td><td><code>selectOutbound(tag)</code> 经 libbox CommandClient 做 group 选择(复刻桌面 <code>selectProxy</code>);<code>getActiveOutbound</code> 查询当前出口</td><td>节点页切换出口不断连、当前出口高亮正确</td></tr>
<tr><td><b>F. KillSwitch11G</b></td><td><code>setKillSwitch(on)</code><b>L1<code>strict_route</code>+ 引导系统 Always-onL3</b></td><td>开关后内核停止即断网/恢复符合预期;UI 诚实标注「彻底防泄漏需到系统设置开启」</td></tr>
</tbody>
</table>
<h2>4. 关键技术决策(与桌面/契约对齐,不自创)</h2>
<ul>
<li><b>配置来源不变</b>Dart 侧 <code>connect_api.fetchConfig</code> 从服务端 <code>POST /v1/nodes/:id/connect</code><b>完整 sing-box config</b>,原样传 <code>bridge.start(configJson)</code><b>客户端绝不拼配置。</b></li>
<li><b>TUN 由 <code>PlatformInterface.openTun</code></b>libbox 回调里用 <code>VpnService.Builder</code> 配地址/MTU/路由/DNS → <code>establish()</code> 拿 fd。<code>autoDetectInterfaceControl</code><code>VpnService.protect()</code> 防环路。现有代码已写好,重点是<b>校验 <code>TunOptions</code> 的真实 getter 名</b>(已用 try-catch 兜底)。</li>
<li><b>DNS 劫持铁律</b>:和 macOS 一样,服务端 config 的 <code>route.rules</code> 首条必须是 <code>{"action":"hijack-dns","port":[53]}</code>——否则隧道连上也打不开网站。这是服务端职责,Android 侧只需确认下发的 config 带这条。</li>
<li><b>统计</b>:优先 libbox <code>CommandClient</code>command=STATUS, 1s);连不上退回 <code>TrafficStats</code>(按 UID)。现有代码已实现双路。</li>
<li><b>切节点 = Clash group 选择</b>:复刻桌面 <code>clashApiClient.selectProxy(group, tag)</code> 的语义,Android 用 libbox <code>CommandClient</code> 的 group 选择 API。组名对着服务端渲染的 config 确认(config 含 <code>auto</code> urltest 组 + <code>reality-out</code>/<code>hy2-out</code> 出口)。</li>
<li><b>KillSwitch = <code>strict_route</code> + 引导 Always-on</b>:详见 §5 第 1 条与 <a href="killswitch-design.html">KillSwitch 设计知识库</a></li>
</ul>
<h2>5. 三处定调(用户已确认)</h2>
<div class="card root">
<h3>① KillSwitch(里程碑 F</h3>
<p>做「L1 + 引导 L3」的组合——<code>setKillSwitch(on)</code><code>strict_route</code>(与 Windows 桌面统一)+ 在 UI 诚实标注「彻底防泄漏需到系统设置开 Always-on」,并提供跳转引导。<b>不假装 app 内能做到真 KillSwitch。</b> 背景与分级见 <a href="killswitch-design.html">KillSwitch 设计知识库</a></p>
</div>
<div class="card root">
<h3>② 工具链</h3>
<p>默认<b>降 Go 到 1.24.3、保 gomobile pin</b>,贴 sing-box 官方 SFA 验证组合;<b>不升 pin</b>。(本机现为 Go 1.26.1gomobile pin 是 2024-06 旧版,新 Go + 旧 gomobile 可能 <code>gomobile bind</code> 失败。)</p>
</div>
<div class="card root">
<h3>③ 测试基线(里程碑 C</h3>
<p><b>先 x86_64 模拟器跑通</b>(环境最干净、最贴官方验证、排除真机变量)→ <b>Vivo X200Android 16 / API 36AOSP 系,主力真机)</b><b>华为 HarmonyOS 4.2(兼容 Android 版,次要兼容性抽查)</b>。华为后台保活激进、电池优化引导 Intent 需单独适配。</p>
</div>
<h2>6. 已知风险 / 坑</h2>
<ol>
<li><b>Go 1.26 vs gomobile 旧 pin</b>:若 <code>gomobile bind</code> 失败,按定调降 Go 1.24.3(保 pin)。</li>
<li><b>当前没有设备连着</b><code>adb devices</code> 为空)——里程碑 C 起需插真机/开模拟器。模拟器为 x86_64,aar 必须含 amd64(脚本已含)。</li>
<li><b>libbox API 漂移</b>:现有 <code>.kt</code> 方法名是猜的,里程碑 B 逐一对平(TunOptions getter 已 try-catch 兜底)。</li>
<li><b>Android 14/16 前台服务 specialUse</b>manifest 已声明;targetSdk 拉到 36 时复测 FGS 启动是否被限。国内分发免审;未来上 Play 需补用途说明。</li>
<li><b>电池优化 / 厂商保活</b>:现有代码会弹豁免引导;华为/Vivo 后台管理激进,需引导用户允许后台 + 自启动,且厂商设置页深链可能落不准。</li>
</ol>
<h2>7. 不在本次范围(YAGNI</h2>
<ul>
<li>国内分流 <code>.srs</code> 本地预取(现走远程 rule-set)。</li>
<li>应用图标 / 启动页打磨。</li>
<li>签名打包 / 应用市场上架 / 侧载分发流程。</li>
<li>per-app 分应用代理。</li>
<li>macOS 原生侧的 11G<code>selectOutbound</code>/<code>getActiveOutbound</code>/<code>setKillSwitch</code>)补齐——平行未完成,见 <a href="killswitch-design.html">KillSwitch 知识库</a> 与 todo #1,不在本次范围。</li>
</ul>
<h2>8. 参考位置索引</h2>
<table>
<thead><tr><th>主题</th><th>文件</th></tr></thead>
<tbody>
<tr><td>桥接契约</td><td><code>client/lib/bridge/vpn_bridge.dart</code></td></tr>
<tr><td>平台分派(需加 Android→Native</td><td><code>client/lib/bridge/vpn_bridge_provider.dart</code></td></tr>
<tr><td>连接状态机(调 bridge.start</td><td><code>client/lib/state/connection_provider.dart</code></td></tr>
<tr><td>取 config</td><td><code>client/lib/services/connect_api.dart</code></td></tr>
<tr><td>Android 通道注册</td><td><code>client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/MainActivity.kt</code></td></tr>
<tr><td>Android VPN 服务</td><td><code>client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/PangolinVpnService.kt</code></td></tr>
<tr><td>内核构建脚本</td><td><code>app/kernel/build-android.sh</code></td></tr>
<tr><td>内核版本锚点</td><td><code>app/kernel/VERSION</code></td></tr>
<tr><td>桌面参考实现(selectOutbound/killSwitch</td><td><code>client/lib/bridge/desktop_vpn_bridge.dart</code></td></tr>
<tr><td>服务端 config 渲染</td><td><code>server/internal/httpapi/clientconfig.go</code></td></tr>
<tr><td>KillSwitch 设计知识库</td><td><a href="killswitch-design.html">docs/killswitch-design.html</a></td></tr>
</tbody>
</table>
</div>
</body>
</html>