Files
pangolin/client/android/app/build.gradle
T
wangjia 83c23f968a feat(android): 实现 M2 Android VPN 隧道 — PangolinVpnService 接入 libbox [tsk_w1NvZdmwGPyd]
接线 11B libbox.aar + 11C Dart 桥接骨架,完成 Android 端 M2 PoC:

### PangolinVpnService(完整实现)
- `handleStart(configJson)` → 后台线程 → `Libbox.newBoxService(platformInterface, json)` → `start()`
- 内 inner class `PangolinPlatformInterface` 实现 libbox `PlatformInterface`:
  - `openTun(TunOptions)`: 调用 `VpnService.Builder()` 配置地址/MTU/路由,`establish()` 取 TUN fd
  - `autoDetectInterfaceControl(fd)`: `VpnService.protect(fd)` 防环路
  - 安全 getter 兜底:TunOptions 方法名与 libbox.aar 不符时降级到 PoC 默认值
- 统计双轨:优先 libbox `CommandClient`(status/traffic)→ 失败时退回 `TrafficStats` 定时器
- 幂等 `doStop()`:commandClient.disconnect → boxService.close → tunPfd.close → stopForeground
- `onRevoke()` 推 error 状态再 doStop,不留假 on

### VpnEventBus
- 新增 `currentStatus` 字段(volatile),供 `getStatus()` 一次性查询

### MainActivity
- `VpnService.prepare()` 权限流程:未授权 → 弹 prepare Intent → `onActivityResult` 后启动
- `getStatus()` 从 `VpnEventBus.currentStatus` 返回真实状态
- 首次连接时引导 `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`(后台存活)

### AndroidManifest.xml
- `android:foregroundServiceType="specialUse"`(Android 14 API 34 强制要求)
- 新增权限:`FOREGROUND_SERVICE_SPECIAL_USE`、`REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`

### build.gradle
- `implementation files('../../../app/kernel/dist/android/libbox.aar')`(条件存在检查)
- `minSdkVersion` 提升至 21(gomobile -androidapi 21 要求)

### 新增文件
- `assets/poc_config.json`: PoC 单节点测试配置模板(含占位符 __NODE_HOST__ 等)

通知文案已过红线词扫描:「加速已开启」「正在连接…」「穿山甲加速」,无 VPN/翻墙等禁词。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:45:13 +08:00

97 lines
3.1 KiB
Groovy
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
namespace "com.pangolin.pangolin_vpn"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
applicationId "com.pangolin.pangolin_vpn"
// libbox requires minSdk 21 (gomobile -androidapi 21)
minSdkVersion Math.max(flutter.minSdkVersion as Integer, 21)
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
signingConfig signingConfigs.debug
}
}
// libbox.aar 包含 arm64-v8a / armeabi-v7a / x86_64 三个 ABI
// 若只需调试,可缩减 abiFilters 以加快构建速度。
// splits {
// abi {
// enable true
// reset()
// include 'arm64-v8a', 'x86_64'
// universalApk true
// }
// }
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ── sing-box libboxgomobile AAR)──────────────────────────────
// 产物由 app/kernel/build-android.sh 生成:
// cd app/kernel && ./build-android.sh
// 产物路径(相对于本 build.gradle):../../../app/kernel/dist/android/libbox.aar
//
// 若文件不存在,构建会报错:请先运行 build-android.sh 生成产物。
// 路径说明:client/android/app → ../../.. → repo root → app/kernel/dist/android/
def libboxAar = file("${projectDir}/../../../app/kernel/dist/android/libbox.aar")
if (libboxAar.exists()) {
implementation files(libboxAar)
} else {
// AAR 尚未构建 — 保留占位;IDE 会报红线,但不影响非 libbox 代码编辑。
// 运行 `app/kernel/build-android.sh` 后重新 sync 即可。
logger.warn("⚠ libbox.aar not found at ${libboxAar.absolutePath}")
logger.warn(" Run: cd app/kernel && ./build-android.sh")
}
}