技术探索记录 在 Android 手机上运行 One API

技术探索全过程记录

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.exe v2.6.4
  • 网络: daqingda, IP 192.168.123.1/24, MTU 1360
  • 公网中继服务器 (4个):
    • et.gbc.moe:11010
    • easytier.weiai.org.cn:11010
    • boi.de5.net:11010
    • ros.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。

步骤

  1. Termux 安装 proot-distro
  2. proot-distro install ubuntu (Ubuntu 26.04)
  3. 进入 Ubuntu: proot-distro login ubuntu
  4. 安装 Go, 编译 One API
  5. 运行: ./one-api --port 3000
  6. PM2 管理进程生命周期

结果

  • One API 正常运行在 Termux proot 环境中
  • 电脑浏览器通过 EasyTier 访问 http://192.168.123.2:3000 (root/123456)
  • PM2 实现开机自启和进程守护

为什么放弃

  • 用户要求纯 APK 方案
  • Termux 需要单独安装和配置
  • 界面不够优雅

Phase 3: 首次交叉编译尝试

环境

第一次尝试: 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 全屏, 启动时 startForegroundService
  • OneApiService.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 天尝试:

  1. GODEBUG=netdns=cgo=1 → 没生效 (因为用了 netgo 标签)
  2. ❌ 重新编译去掉 netgo → 还是不行 (Android 子进程没有 Binder context)
  3. ✅ 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 外壳

新增组件

  1. MainActivity.java --- 原生配置 UI (SharedPreferences 存配置)
  2. WebViewActivity.java --- 独立 WebView 页面
  3. cpp/dns_hook.c --- LD_PRELOAD DNS 重定向库 (NDK 编译, ~10KB)
  4. res/layout/activity_main.xml --- 配置界面布局
  5. 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 r27c
  • D:\devtools\easytier-gui\easytier-gui.exe --- EasyTier GUI v2.6.4
相关推荐
夜雪一千41 分钟前
MySQL 全局锁是什么?原理、风险、备份踩坑完整实战
android·mysql·adb
吠品1 小时前
Wine 在 Linux 上运行 Windows 软件完整指南
java·linux·服务器
深念Y3 小时前
从 Windows 迁移到 Linux 开发环境的记录
linux·windows·链接·bun·ram·imdisk
AuTumn-L3 小时前
使用 SSH 密钥认证实现安全免密登录
服务器·安全·ssh
知无不研4 小时前
Linux I/O复用之epoll
linux·服务器·epoll·socket编程
dogstarhuang5 小时前
OpenAI GPT-5.6 降价后如何重算 API 账单?多模型路由与成本治理实战
服务器·网络·人工智能·大模型·api·ai应用开发·接口管理
苏宸啊5 小时前
linux网络编程udp服务器和客户端代码编写(echo版本和字典版本)
linux·网络
~|Bernard|6 小时前
Linux 定时任务(Cron)完整教程
linux·运维·服务器
小周学学学6 小时前
vmware-horizon第一章:horizon服务器安装
运维·服务器·vmware
小雪崩6 小时前
嵌入式学习 day25:哈希表及排序与查找
linux·c语言·数据结构·学习·排序算法