Files
honoka-chan/android
YumeMichi 91bbc7f607 Add Android control app and embedded server
Inspired by HNKServer/honoka-chan-apk-server, but implemented as a JNI-based embedded runtime instead of the original approach.

- Add an Android Compose controller app that starts and stops honoka-chan through JNI, shows runtime and health status, manages data directory mounting, and supports backup import/export for data.db
- Add an embeddable Go server entrypoint plus JNI-exported status, health, and reload hooks so desktop and Android builds share the same startup and shutdown path
- Add Android build scripts, runtime packaging, and project documentation, including generated default config.json content for honoka_runtime.zip
- Add runtime bundle hash tracking so updated honoka_runtime.zip assets are automatically redeployed while preserving config.json and user data files
- Add in-app service settings for unlock_all_special_rotation with immediate config persistence and automatic reload when the service is running
- Split SQLite driver selection by platform, using go-sqlite3 on Android and modernc.org/sqlite elsewhere to avoid Android x86_64 seccomp crashes
- Update startup, database initialization, and system health/reload handlers to support the embedded runtime and Android control flow

Signed-off-by: Sean Du <do4suki@gmail.com>
2026-06-12 19:17:21 +08:00
..

Android 控制端

这个目录提供一个 Kotlin/Compose Android App,用来把 honoka-chan 作为本地动态库加载,并通过 JNI 控制启停。

当前功能

  • 启动 / 停止本地 honoka-chan 服务
  • 直接通过 JNI 查询运行状态、健康信息,并在修改配置后即时重载
  • 将指定目录以符号链接方式挂到运行时 /static/Android
  • 支持导出 / 导入用户数据备份(data.db
  • 使用前台服务 + PARTIAL_WAKE_LOCK 做后台保活
  • 首次启动时解压运行时资源包到应用私有目录
  • 打包运行时资源时自动生成默认 config.json
  • 检测到未加入电池优化白名单时,仅弹出说明提示,不强制跳转系统设置

目录约定

  • Go 动态库放到:android/app/src/main/jniLibs/<abi>/libhonokachan.so
  • 运行时资源包放到:android/app/src/main/assets/honoka_runtime.zip

目前工程里已经预建了:

  • android/app/src/main/jniLibs/arm64-v8a/
  • android/app/src/main/jniLibs/armeabi-v7a/
  • android/app/src/main/jniLibs/x86_64/
  • android/app/src/main/assets/

1. 开发环境要求

Linux 下建议直接使用 android/scripts/*.sh 先准备运行时资源与 JNI 动态库,最终 APK 由 Android Studio 构建。

需要准备:

  • Go
  • zip
  • Android SDK
  • Android NDK
  • Android Studio

常用环境变量:

export ANDROID_SDK_ROOT=/path/to/android-sdk
export ANDROID_NDK_HOME=/path/to/android-ndk

默认 MIN_SDK=26,和当前 Android App 的 minSdk 保持一致。如果你确实需要改动原生层最低版本,可以在执行脚本前自行覆盖:

export MIN_SDK=26

建议先跑环境检查:

./android/scripts/check_env.sh

说明:

  • 不需要预先在仓库根目录准备 config.jsonprepare_runtime_zip.sh 会自动生成默认配置并打包进去。

2. 一键准备运行时资源

如果环境已经准备好,直接执行:

./android/scripts/build_all.sh

它会依次完成:

  1. 打包 honoka_runtime.zip
  2. 编译 arm64-v8alibhonokachan.so
  3. 编译 armeabi-v7alibhonokachan.so
  4. 编译 x86_64libhonokachan.so

完成后再用 Android Studio 打开 android/ 并构建 APK。

3. 单步脚本

3.1 打包运行时资源

./android/scripts/prepare_runtime_zip.sh

3.2 编译 Go 动态库

需要使用 Android NDK 的 clang 作为 CC,并启用 c-shared

脚本方式:

./android/scripts/build_go_android.sh arm64-v8a
./android/scripts/build_go_android.sh armeabi-v7a
./android/scripts/build_go_android.sh x86_64

手工方式,arm64-v8a 示例:

export ANDROID_NDK_HOME=/path/to/android-ndk
export CC="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang"
CGO_ENABLED=1 GOOS=android GOARCH=arm64 \
  go build -buildmode=c-shared \
  -o android/app/src/main/jniLibs/arm64-v8a/libhonokachan.so \
  ./cmd/honoka-android

手工方式,armeabi-v7a 示例:

export ANDROID_NDK_HOME=/path/to/android-ndk
export CC="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi26-clang"
CGO_ENABLED=1 GOOS=android GOARCH=arm GOARM=7 \
  go build -buildmode=c-shared \
  -o android/app/src/main/jniLibs/armeabi-v7a/libhonokachan.so \
  ./cmd/honoka-android

手工方式,x86_64 示例:

export ANDROID_NDK_HOME=/path/to/android-ndk
export CC="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android26-clang"
CGO_ENABLED=1 GOOS=android GOARCH=amd64 \
  go build -buildmode=c-shared \
  -o android/app/src/main/jniLibs/x86_64/libhonokachan.so \
  ./cmd/honoka-android

说明:

  • 这里直接使用 ./cmd/honoka-android,不需要额外 JNI 头文件。
  • Android App 自带的 honoka_android.cpp 会在运行时通过 dlopen("libhonokachan.so") 找到导出的 ServerStart / ServerStop / ServerStatusJSON 等符号。
  • go build -buildmode=c-shared 会顺带生成 libhonokachan.h,脚本会自动删除这个头文件,因为当前 Android 工程并不会使用它。

4. 打包运行时资源

honoka-chan 在 Android 上仍然按仓库根目录结构读取文件,所以 zip 包内必须直接包含这些顶层条目:

  • assets/
  • static/
  • config.json

最小建议内容:

  • assets/main.db
  • assets/serverdata/
  • assets/certs/
  • static/templates/
  • static/css/
  • static/js/
  • static/images/
  • static/font/
  • config.json

如果你希望把已有用户数据也一起带进去,可以额外放入:

  • assets/data.db

prepare_runtime_zip.sh 会自动生成一份默认配置,内容等同于服务端 config.DefaultConfigs()

  • app_name: honoka-chan
  • settings.listen_port: 8080
  • settings.cdn_server: http://127.0.0.1:8080/static
  • settings.reload_token: ""
  • settings.unlock_all_special_rotation: false

手工打包命令:

zip -r android/app/src/main/assets/honoka_runtime.zip assets static config.json

注意:

  • 压缩包根目录下必须直接看到 assetsstaticconfig.json,不要多包一层目录。
  • App 会记录 honoka_runtime.zip 的内容哈希;如果资源包发生变化,下次启动服务或刷新挂载时会自动重新解压。
  • 自动重新解压时会保留运行时目录里现有的 config.json,避免覆盖用户已经修改过的服务设置。
  • 自动重新解压时也会保留运行时目录里现有的 assets/data.db 及其 -wal/-shm,避免覆盖用户数据。

5. 构建 APK

当前不再提供命令行脚本构建 APK,直接使用 Android Studio 打开 android/ 构建即可。

如果你主要用 Android Studio 模拟器测试,建议保留 x86_64 ABI;很多模拟器镜像不会加载仅包含 ARM so 的 APK。

6. 数据目录

App 会把你选择的目录挂到运行时:

  • /static/Android

实现方式不是 Linux bind mount,而是:

  • 在应用私有运行时目录中创建 static/Android -> 你的目录 的符号链接

默认数据目录:

  • /sdcard/Download/data

如果 Android 版本较高,点击“选择目录”时会先跳转系统设置申请“所有文件访问权限”,授权后自动继续打开目录选择器。

7. 备份导入导出

  • 备份导出和导入的对象都是运行时目录里的 assets/data.db
  • 执行前需要先停止服务,避免数据库仍在使用中
  • 导出前 App 会先执行一次 wal_checkpoint(TRUNCATE),这样单独导出的 data.db 就是完整的
  • 导入备份时会提示“将覆盖当前用户数据,是否继续”

8. 运行流程

  1. 准备好 libhonokachan.so
  2. 准备好 honoka_runtime.zip
  3. 用 Android Studio 构建并安装 APK
  4. 打开 App
  5. 首次进入时按系统提示授予通知权限
  6. 根据需要处理“所有文件访问权限”与数据目录选择
  7. 如有需要,打开“解锁全部日替”开关
  8. 点击“启动服务”
  9. 用“刷新状态”确认当前进程健康状态正常

说明:

  • 如果系统尚未将本应用加入电池优化白名单,App 只会弹出说明提示,不会自动跳转设置页。
  • 即使没有加入白名单,服务仍然可以启动。
  • 在部分国产系统上,如果退到后台后服务仍会断开,除了忽略电池优化、自启动和后台运行,还可能需要在最近任务中手动锁定本应用。

9. 其他说明

  • 这个 Android 子目录默认只提交源码与脚本;gradlewgradle/、构建产物、jniLibs/assets/ 等本地生成内容当前都被 android/.gitignore 排除了。
  • 如果你希望把 Gradle wrapper 一起纳入仓库,可以自行调整 android/.gitignore
  • 前台服务会持有 PARTIAL_WAKE_LOCK,停止服务时会释放。
  • App 内部的状态、健康检查、配置重载都直接通过 JNI 调用 Go 导出符号。
  • /system/health/system/reload 仍然保留,方便外部调试、脚本探活和浏览器访问。
  • Android Studio 升级到 AGP 8.11.2 后,如果项目放在 Samba 映射盘上,可能会出现 Cannot create directory .../merged.dir/values 之类的 AAPT 目录创建失败。这个更像文件系统兼容问题,建议改回本地磁盘构建。

10. Reload 与资源更新说明

  • App 内部修改服务设置时会直接在进程内执行 reload
  • /system/reload HTTP 接口只用于外部调用,仍然保留 token 校验
  • 无论通过 JNI 还是 HTTP,reload 都只会重新加载运行时目录中的 config.json
  • 它不会重新解压 honoka_runtime.zip
  • 如果你更新了数据库、证书、serverdata、模板等资源,需要重新生成 honoka_runtime.zip 并重新安装或覆盖资源包