首发日期 2025-06-30
, 以下为原文内容:
说实话, 窝并不是很喜欢 Android 操作系统. 窝喜欢 GNU/Linux (比如 ArchLinux, Debian, Fedora, Alpine, Ubuntu 等). 但是在手机上, 并没有更好的选择, 所以对于 Android 系统的各种缺点, 忍了 !
Termux 把很多 GNU/Linux 的体验搬到了 Android 系统, 所以 Termux 可能是窝最喜欢的 app !
但是 Termux 只是一个应用, 如何把 Termux 用于自己的应用 ? 为了把 Termux 的体验方便的加入 Android 应用, 窝又造了一个轮子: 胖喵安初 (azi 库).
这里是 穷人小水滴, 专注于 穷人友好型 低成本技术. (本文为 76 号作品. )
相关文章:
- 《小水滴系列文章目录 (整理)》 juejin.cn/post/752209…
- 《在 Android 设备上写代码 (Termux, code-server)》 juejin.cn/post/751018…
- 《在 Termux 中签名 apk 文件》 juejin.cn/post/751548…
- 《高版本 Android 如何访问 sdcard/Android/data 目录中的文件 (翻译)》 juejin.cn/post/734390…
参考资料:
- github.com/agnostic-ap…
- termux.dev/en/
- gitlab.com/fdroid/fdro…
- gitlab.com/fdroid/fdro…
- gitlab.com/fdroid/fdro…
- gitlab.com/fdroid/fdro…
- gitlab.com/fdroid/fdro…
- dev.to/sanandmv7/h…
- crates.io/crates/stat…
目录
- 1 主要设计与实现
- 2 举栗 (示例应用)
- 2.1 发布 fdroid
- 2.2 安装运行
- 2.3 运行文件服务器
- 3 总结与展望
1 主要设计与实现
胖喵安初 (azi) 的设计非常简单粗暴 (KISS 原则, 保持简单愚蠢):
应用首次启动时, 胖喵安初从 apk
的 assets
中解压 (释放) 一个 zip 压缩包
(比如 test-init.azi.zip
) 到应用的 sdcard/Android/data
目录,
然后调用 /system/bin/sh
(Android 系统自带 shell) 执行 azi_init.sh
脚本, 来进行初始化操作.
初始化成功后, 创建 azi_ok
文件, 避免重复初始化.
由于初始化 zip 包被内嵌到 apk 的 assets 中, 一个 apk 文件就是完整的, 安装之后可以直接初始化并运行.
使用胖喵安初的应用可以在 zip 包中放入任意内容, 且初始化 shell 脚本也在 zip 包中.
shell 脚本为初始化操作提供了很高的灵活性, 有很大的发挥空间.
sdcard/Android/data
目录中的文件方便用户查看修改, 也方便开发调试.
为了方便下游应用使用, 胖喵安初编译后是 AAR
库.
下游应用只需引入单个文件 app/libs/azi.aar
即可使用胖喵安初.
此外, 胖喵安初对 Android WebView 进行了简单封装, 方便使用 vue 等 web 技术栈来开发应用.
胖喵安初的主要模块有:
-
azi-unzip
: 用于解压 zip 包的可执行程序, 使用 rust 编写, 并编译为 Android 可执行的 ELF 二进制文件. -
ui-loader
: 一个简单的加载界面, 在进行初始化时显示, 使用 vue 框架编写 (js/vite). -
azi: 初始化库本体, 使用 kotlin 编写.
-
azi demo: 示例应用, 演示如何使用胖喵安初这个初始化库.
2 举栗 (示例应用)
由于胖喵安初进行了封装, 下游应用可以简单的使用.
文件 demo-apk/app/src/main/java/io/github/fm_elpac/azi_demo/DemoApp.kt
:
package io.github.fm_elpac.azi_demo
import android.app.Application
import io.github.fm_elpac.azi.Azi
class DemoApp: Application() {
override fun onCreate() {
super.onCreate()
// init azi
Azi.setContext(this)
}
}
这个是在应用启动时, 对胖喵安初库进行初始化.
文件 demo-apk/app/src/main/java/io/github/fm_elpac/azi_demo/MainActivity.kt
:
package io.github.fm_elpac.azi_demo
import android.app.Activity
import android.os.Bundle
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.JavascriptInterface
import io.github.fm_elpac.azi.Azi
import io.github.fm_elpac.azi.AziCb
import io.github.fm_elpac.azi.AziWebView
class MainActivity: Activity() {
var aw: AziWebView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Azi.log("azi-demo MainActivity.onCreate()")
val w = AziWebView(this)
// 显示 WebView
setContentView(w.getWebView())
// status bar color (black)
window.statusBarColor = 0xff000000.toInt()
// 添加自定义 js api
w.addJsApi("demo", DemoApi(this))
w.addJsApi("azi_api", LoaderApi())
// 显示 ui-loader
w.loadLoader()
aw = w
// 开始 (后台) 初始化
val cb = object: AziCb {
override fun ok() {
aw?.loadSdcard(Azi.AZI_DIR_SDCARD_DATA, "demo/index.html")
}
}
Azi.initZip("test-init.azi.zip", "demo", cb)
}
// 在受限的 WebView 环境中加载页面
fun openCleanWebView(url: String) {
Azi.log("Demo.openCleanWebView() " + url)
val w = WebView(this)
w.settings.javaScriptEnabled = true
w.setWebViewClient(WebViewClient())
setContentView(w)
w.loadUrl(url)
}
}
class DemoApi(val a: MainActivity) {
// 打开外部页面 URL
@JavascriptInterface
fun openPage(url: String) {
a.aw?.runOnUiThread {
a.openCleanWebView(url)
}
}
}
class LoaderApi() {
// azi_api.getJsLoadList()
@JavascriptInterface
fun getJsLoadList(): List<String> {
return listOf<String>()
}
// azi_api.checkInit()
@JavascriptInterface
fun checkInit(): String {
return "加载中 .. ."
}
}
这个是在应用主界面启动时, 调用胖喵安初进行初始化, 并在初始化完成后显示 web 界面.
2.1 发布 fdroid
fdroid 是一个可以发布 apk 的很特殊的地方: 你把所有源代码交给 fdroid, 然后 fdroid 替你编译出 apk, 替你签名, 替你发布.
如何安装 fdroid 请见文章 《在 Android 设备上写代码 (Termux, code-server)》.
然而, 为了实现 fdroid 编译, 费了好大劲, 失败了几十次. 胖喵安初同时使用了 rust, vue, kotlin, aar, 所以编译起来稍微有点麻烦.
总之, 最后成功的方案如下:
文件: .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
jobs:
build-fdroid:
#needs: build
runs-on: ubuntu-latest
container:
image: registry.gitlab.com/fdroid/fdroidserver:buildserver
steps:
- uses: actions/checkout@v4
- run: apt-get update && apt-get dist-upgrade
- name: setup env
run: |
. /etc/profile.d/bsenv.sh
# save env
echo "ANDROID_HOME=${ANDROID_HOME}" >> "$GITHUB_ENV"
echo "DEBIAN_FRONTEND=${DEBIAN_FRONTEND}" >> "$GITHUB_ENV"
echo "home_vagrant=${home_vagrant}" >> "$GITHUB_ENV"
echo "fdroidserver=${fdroidserver}" >> "$GITHUB_ENV"
echo "LC_ALL=${LC_ALL}" >> "$GITHUB_ENV"
# more env
echo PYTHONPATH="$fdroidserver:$fdroidserver/examples" >> "$GITHUB_ENV"
echo JAVA_HOME=$(java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | awk -F'=' '{print $2}' | tr -d ' ') >> "$GITHUB_ENV"
# PATH
echo PATH="$fdroidserver:$PATH" >> "$GITHUB_ENV"
# debug
cat "$GITHUB_ENV"
- run: sdkmanager "platform-tools" "build-tools;31.0.0"
# fdroiddata
- run: git clone --depth=1 --single-branch https://gitlab.com/fdroid/fdroiddata /build
- run: cp .fdroid.yml /build/metadata/io.github.fm_elpac.azi_demo.yml
# fdroidserver
- run: git clone --depth=1 --single-branch https://gitlab.com/fdroid/fdroidserver $fdroidserver
- name: "> fdroid readmeta && fdroid rewritemeta"
run: |
cd /build
fdroid readmeta
fdroid rewritemeta io.github.fm_elpac.azi_demo
# DEBUG
- run: cat /build/metadata/io.github.fm_elpac.azi_demo.yml
- name: "> fdroid checkupdates"
run: |
cd /build
fdroid checkupdates --allow-dirty io.github.fm_elpac.azi_demo
- name: "> fdroid fetchsrclibs"
run: |
cd /build
fdroid fetchsrclibs --verbose io.github.fm_elpac.azi_demo
- name: "> fdroid build"
run: |
cd /build
fdroid build --verbose --on-server --no-tarball io.github.fm_elpac.azi_demo
- uses: actions/upload-artifact@v4
with:
name: unsigned-fdroid
path: /build/unsigned
这个是使用 github actions 运行 fdroidserver
来编译 apk 的方法.
文件 .fdroid.yml
:
Categories:
- Development
License: MIT
AuthorName: secext2022
AuthorEmail: secext2022@outlook.com
SourceCode: https://github.com/fm-elpac/azi
IssueTracker: https://github.com/fm-elpac/azi/issues
RepoType: git
Repo: https://github.com/fm-elpac/azi
Builds:
- versionName: 0.1.0
versionCode: 2
commit: HEAD
sudo:
- apt-get update
- apt-get install -y make npm zip
output: demo-apk/app/build/outputs/apk/release/app-release-unsigned.apk
srclibs:
- rustup@1.28.2
prebuild: $$rustup$$/rustup-init.sh -y --default-toolchain 1.87.0 --target aarch64-linux-android
build:
- source $HOME/.cargo/env
- make BUILD=fdroid all
ndk: r28b
#AutoUpdateMode: Version
#UpdateCheckMode: Tags v[0-9].*
#UpdateCheckData: demo-apk/app/build.gradle.kts|versionCode\s=\s(\d+)|.|versionName\s=\s"([^"]+)"
#CurrentVersion: 0.1.0
#CurrentVersionCode: 2
这个是 fdroid 用于编译 apk 的说明文件.
编译成功之后, 就可以按照 fdroid 官方文档, 去提 MR (合并请求) 即可. 当然这个过程又费了好大劲. 总之, 最后成功了 !
2.2 安装运行
在 fdroid 中找到胖喵安初:
额 .. . 或者搜索:
点进去查看详情:
安装后运行:
对应的应用信息:
2.3 运行文件服务器
上面只是胖喵安初的初步测试运行, 下面进一步展示胖喵安初的能力.
下载 github.com/fm-elpac/az…
然后放在 /sdcard/Android/data/io.github.fm_elpac.azi_demo/cache/test-init.azi.zip
(注意重命名).
并删除 /sdcard/Android/data/io.github.fm_elpac.azi_demo/files/demo/
目录, /sdcard/Android/data/io.github.fm_elpac.azi_demo/files/azi/azi_ok
文件.
重新启动应用, 文件服务器就会运行. 在浏览器中打开 http://localhost:3333
查看结果.
此处的文件服务器 (static-web-server
) 来自: crates.io/crates/stat…
只需一个简单的 shell 脚本即可在胖喵安初运行 (文件 demo/azi_init.sh
):
#!/system/bin/sh
# test file server
echo "init file server"
cp $AZI_DIR_SDCARD_DATA/demo/server/static-web-server $AZI_DIR_APP_DATA
echo "start server (background)"
/system/bin/linker64 $AZI_DIR_APP_DATA/static-web-server -p 3333 -d $AZI_DIR_SDCARD_DATA -z &
sleep 2
/system/bin/sh $AZI_DIR_SDCARD_DATA/demo/rm_ok.sh &
echo exit
3 总结与展望
胖喵安初 (azi) 是一个简单的 Android 应用初始化库, 可以把类似 Termux 的运行环境引入 apk. 胖喵安初编译后是 aar 包, 方便下游应用引入使用. 胖喵安初对 WebView 进行了简单封装, 方便使用 web 技术栈 (比如 vue) 开发应用. 胖喵安初有一个简单的示例应用, 可以从 fdroid 下载安装.
使用胖喵安初来制造有趣的 Android 应用吧 !
本文使用 CC-BY-SA 4.0 许可发布.