技术探索全过程记录
2026/6/29
项目目标
在 Android 手机上运行 One API(AI API 中转站),要求:
- 无 Root
- 纯 APK 方案(不接受 Termux)
- 可启停
- 有 GUI 管理界面
环境
- 电脑: Windows, 192.168.31.x (EasyTier 192.168.123.1)
- 手机: Android (API 28+), 连接便携 WiFi (Symmetric NAT)
- 网络: EasyTier v2.6.4 组网, relay 中继 128-150ms
Phase 1: EasyTier 组网 (成功)
电脑端
- 程序:
D:\devtools\easytier-gui\easytier-gui.exev2.6.4 - 网络:
daqingda, IP192.168.123.1/24, MTU 1360 - 公网中继服务器 (4个):
et.gbc.moe:11010easytier.weiai.org.cn:11010boi.de5.net:11010ros.scpsl.com.cn:11010
手机端
- Android App 配置相同网络
- 通过 relay 中继连接, 128-150ms 延迟
- 手机 IP:
192.168.123.2
问题: SOCKS5 代理无效
EasyTier v2.6.4 的 SOCKS5 代理在 Android 上有 bug(Issue #1825),导致无法使用 SOCKS5 穿透。不走 SOCKS5 也能正常通信。
Phase 2: Termux + proot-distro (成功但用户不满足)
方案
在 Termux 内安装 proot-distro Ubuntu, 然后运行 One API。
步骤
- Termux 安装 proot-distro
proot-distro install ubuntu(Ubuntu 26.04)- 进入 Ubuntu:
proot-distro login ubuntu - 安装 Go, 编译 One API
- 运行:
./one-api --port 3000 - PM2 管理进程生命周期
结果
- One API 正常运行在 Termux proot 环境中
- 电脑浏览器通过 EasyTier 访问 http://192.168.123.2:3000 (root/123456)
- PM2 实现开机自启和进程守护
为什么放弃
- 用户要求纯 APK 方案
- Termux 需要单独安装和配置
- 界面不够优雅
Phase 3: 首次交叉编译尝试
环境
- Go 1.24.1
- 目标: Android ARM64 (arm64-v8a)
- 项目: github.com/songquanpeng/one-api (使用 go-sqlite3, CGO 依赖)
第一次尝试: CGO_ENABLED=0
$env:CGO_ENABLED="0"
$env:GOOS="android"
$env:GOARCH="arm64"
go build -o one-api-arm64
结果: 编译成功, 但运行时崩溃
原因: go-sqlite3 需要 CGO, CGO_ENABLED=0 时使用 stub, 数据库操作报错
第二次尝试: NDK 22 + CGO_ENABLED=1
$env:CC="<NDK22>/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang"
$env:CGO_ENABLED="1"
结果: 链接失败, undefined reference to pthread_create 等
原因: NDK 22 太老, 没有 Go 1.24 需要的 pthread 符号
第三次尝试: NDK r27c + CGO_ENABLED=1
下载 NDK r27c (27.0.12077973)
结果: 编译成功, 但运行时还是有问题
- 需要
-buildmode=pie(Android 5.0+ 要求 PIE) - 需要
netgo osusergo标签 (但 netgo 后来发现导致 DNS 问题) - 需要
-extldflags "-Wl,-z,max-page-size=4096"
最终参数:
CC=<NDK>/bin/aarch64-linux-android31-clang.cmd
CGO_ENABLED=1 GOOS=android GOARCH=arm64
go build -tags 'netgo osusergo' -buildmode=pie
-ldflags '-s -w -extldflags "-Wl,-z,max-page-size=4096"'
Phase 4: TLS Alignment Bug
症状
编译出的 32MB 二进制在 Android 上运行时 闪退, logcat 无明确错误。
根因
ARM64 Android Bionic libc 要求 PT_TLS segment 的 p_align 必须是 64。
Go 1.24.1 交叉编译出的 ELF 中 PT_TLS p_align=8, 不兼容。
修复方式
编写 align_fix.py 修补二进制:
python
# 读取 ELF header, 找到 Program Headers
# 扫描 p_type==7 (PT_TLS)
# 如果 p_align < 64, 改为 64
with open(sys.argv[1], 'r+b') as f:
hdr = f.read(16)
if hdr[4] == 2: # ELF64
f.seek(32)
offset = struct.unpack('<Q', f.read(8))[0] # e_phoff
f.seek(54)
phsize = struct.unpack('<H', f.read(2))[0] # e_phentsize
phnum = struct.unpack('<H', f.read(2))[0] # e_phnum
for i in range(phnum):
f.seek(offset + i * phsize)
t = struct.unpack('<I', f.read(4))[0]
if t == 7: # PT_TLS
f.seek(44, 1)
align = struct.unpack('<Q', f.read(8))[0]
if align < 64:
f.seek(-8, 1)
f.write(struct.pack('<Q', 64))
用法: python align_fix.py one-api-android
关于 Go 官方修复
GitHub Issue: golang/go#68541 (ARM64 Android TLS alignment)
预计 Go 1.25 会修复此问题, 届时不再需要 align_fix.py。
Phase 5: 初步 APK 构建 (纯 WebView)
方案
简单 Android 项目, 布局是 WebView (加载 http://127.0.0.1:3000), 启动时 OneApiService 在后台执行二进制。
核心组件
MainActivity.java: WebView 全屏, 启动时 startForegroundServiceOneApiService.java: 提取 token file → spawn 二进制 → 读取 stdout
问题: Token Encoder 下载失败
[FATAL] failed to get gpt-3.5-turbo token encoder:
Get "https://openaipublic.blob.core.windows.net/...":
lookup ... on [::1]:53: connection refused
二进制在 APK 子进程里无法解析 DNS。经过 3 天尝试:
- ✅
GODEBUG=netdns=cgo=1→ 没生效 (因为用了 netgo 标签) - ❌ 重新编译去掉
netgo→ 还是不行 (Android 子进程没有 Binder context) - ✅ LD_PRELOAD hook + 自定义 resolv.conf +
GODEBUG=netdns=go=1
前端白屏问题
- 原因:
web/build/目录为空 (.gitkeep 占位) - Go
//go:embed web/build/*嵌入了空目录 - 需要先
npm run build编译 React 前端, 产物放web/build/default/
Phase 6: 最终方案 --- 可配置 Java 外壳
新增组件
MainActivity.java--- 原生配置 UI (SharedPreferences 存配置)WebViewActivity.java--- 独立 WebView 页面cpp/dns_hook.c--- LD_PRELOAD DNS 重定向库 (NDK 编译, ~10KB)res/layout/activity_main.xml--- 配置界面布局res/layout/activity_webview.xml--- WebView 布局
DNS 修复原理
用户配置 DNS (8.8.8.8)
↓
Java 写 filesDir/resolv.conf:
nameserver 8.8.8.8
nameserver 8.8.4.4
↓
LD_PRELOAD libdns_hook.so:
open("/etc/resolv.conf") → 返回自定义文件
fopen("/etc/resolv.conf") → 返回自定义文件
↓
GODEBUG=netdns=go=1:
Go 内置解析器读 resolv.conf → 8.8.8.8 → DNS 正常
数据流
用户输入端口/DNS → SharedPreferences → Intent → OneApiService
→ 写 resolv.conf → setenv(LD_PRELOAD, GODEBUG, CUSTOM_RESOLV_CONF)
→ ProcessBuilder → liboneapi.so 进程
→ stdout → logcat + logBuffer → MainActivity 日志滚动显示
问题汇总
| 问题 | 阶段 | 原因 | 解决方案 |
|---|---|---|---|
| SOCKS5 不通 | Phase 1 | EasyTier v2.6.4 bug #1825 | 不用 SOCKS5, 直接通信 |
| go-sqlite3 stub | Phase 3 | CGO_ENABLED=0 | 改为 CGO_ENABLED=1 + NDK |
| NDK 22 缺 pthread | Phase 3 | NDK 版本太老 | 升级 NDK r27c |
| 闪退无错误 | Phase 3 | 缺少 PIE flag | -buildmode=pie |
| 闪退无错误 | Phase 4 | TLS p_align=8 不兼容 | align_fix.py 补丁 |
| DNS 解析失败 | Phase 5 | netgo 标签禁用 cgo | 去掉 netgo + LD_PRELOAD |
| DNS 解析失败 | Phase 5 | 子进程无 Binder context | LD_PRELOAD 劫持 resolv.conf |
| 前端白屏 | Phase 5 | web/build/ 为空 | npm run build |
| Token 下载崩溃 | Phase 5 | logger.FatalLog | TIKTOKEN_CACHE_DIR 预缓存 |
Android 版本兼容性
| 层级 | 最低版本 | 说明 |
|---|---|---|
| APK (Java) | Android 9 (API 28) | minSdk 28 |
| Go 二进制 | Android 12 (API 31) | aarch64-linux-android31-clang |
| ARM64 | Android 5.0+ (API 21+) | 仅 arm64-v8a |
| PIE | Android 5.0+ (API 21+) | -buildmode=pie |
实际支持: Android 12+ (API 31), arm64-v8a
降级到 Android 9-11: 编译器改 aarch64-linux-android28-clang。
文件清单
C:\Users\a1\Desktop\oneapi-apk\--- Android APK 项目C:\Users\a1\Desktop\one-api-android--- 最终编译的 Go 二进制 (40MB, 含前端)C:\Users\a1\Desktop\one-api-arm64--- 早期 CGO_ENABLED=0 二进制 (无效)C:\Users\a1\Desktop\one-api\--- One API 源代码D:\devtools\Android\Sdk\ndk\27.0.12077973\--- NDK r27cD:\devtools\easytier-gui\easytier-gui.exe--- EasyTier GUI v2.6.4