技术探索记录 在 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
相关推荐
qetfw1 小时前
Debian 配置 AIDE 文件完整性检测:基线初始化、变更检查与数据库更新
linux·网络·数据库·debian
俊哥工具1 小时前
文件批量转换工具 本地运行更快更安全
运维·服务器·django·音视频·tornado
小生不才yz2 小时前
Linux设备与存储精读 · L02-02 | inode 与目录项:名字、元数据、数据分别在哪
linux
AOwhisky4 小时前
Linux(CentOS)系统管理入门笔记(第二十一期)——防火墙管理(Firewalld)——zone、服务端口、富规则与端口转发
linux·运维·笔记·安全·centos·防火墙
tg_xianheyun10 小时前
CDN节点分布如何影响网页加载速度和用户体验
服务器·cdn加速·全球访问优化
lsh曙光10 小时前
延时at指令和定时cron指令
linux·服务器·网络
圆山猫10 小时前
[Virtualization](四):Linux KVM/RISC-V 的 vCPU 运行路径
java·linux·risc-v
似的83511 小时前
一步一步学习使用FireMonkey动画() 使用TAnimator类创建动画
linux·学习·nginx
summerkissyou198711 小时前
Android - 摄像头 - hal - 开发教程,例子,常见问题,分析方法,解决方案
android