最近把几年前的开源 Android 项目 WakeUpScreen 提交到了 F-Droid。流程本身不复杂,但第一次处理时,容易在元数据格式、版本检测和本地验证环境上浪费时间。本文按实际提交过程整理一遍,覆盖准备工作、提交流程和几个常见问题。
在 Google Play 之外,F-Droid 适合作为开源 Android 应用的另一条分发渠道。两者的差异主要有三点:
适合提交到 F-Droid 的应用通常具备这些特征:代码开源、不依赖 Google 服务、重视隐私、希望提供 Google Play 之外的下载方式。WakeUpScreen 基本符合这组条件:使用 GPL-3.0 协议、不联网、不申请网络权限,也没有统计或广告 SDK。
正式提交前,先处理三件事:依赖和许可证自检、补齐 fastlane metadata、准备好可复用的签名 APK 发布流程。
最先需要确认的是依赖是否触碰 F-Droid 的限制。可以先全局搜索常见的 Google 依赖:
grep -rE "play-services|firebase|com\.google\.android\.gms" .
如果存在匹配项,就需要先移除,或者接受应用被标记为 anti-feature。
许可证方面,F-Droid 接受主流 FOSS 协议,例如 Apache 2.0、MIT、BSD、GPL。完整列表可以查看 fdroiddata 仓库的 templates 目录。WakeUpScreen 使用的是 GPL-3.0,没有问题。
还需要确认第三方依赖本身也是 FOSS。WakeUpScreen 使用的 com.tencent:mmkv、com.blankj:utilcodex、Glide、AndroidX、Compose 都符合要求。这里最容易出问题的通常是统计、推送和广告类 SDK。
F-Droid 可以直接读取应用描述、截图和更新记录,但前提是文件结构符合 fastlane 约定,目录应放在 fastlane/metadata/android/ 下。例如:
fastlane/metadata/android/
├── en-US/
│ ├── title.txt
│ ├── short_description.txt
│ ├── full_description.txt
│ ├── changelogs/
│ │ └── 30300.txt
│ └── images/
│ ├── icon.png
│ └── phoneScreenshots/
│ ├── 1.png
│ └── 2.png
├── zh-CN/
│ └── ...
└── it/
└── ...
这里有几个细节需要提前处理:
changelog 文件名必须使用 versionCode,不是 versionName。512 x 512 尺寸,可以直接复用 Android 项目中的 ic_launcher-web.png。WakeUpScreen 的截图直接复用了现有素材,描述文本则从 README 提炼而来。因为已有英文、中文和意大利语三份 README,这部分准备成本不高。
这一步不是强制要求,但实际能省下不少时间。提交 F-Droid 时,需要填写签名证书的 SHA-256 指纹。如果已经有 GitHub Actions 自动构建并发布签名 APK,这个值只需要下载 release 后执行一次 apksigner verify 即可。
WakeUpScreen 使用一份 release.yml 工作流监听 gradle.properties 中的 AppVersionName。每次版本号变更后,工作流会自动完成这些步骤:
这样在准备 F-Droid 元数据时,不需要再手动处理 keystore、签名和上传。
整体流程可以拆成四步:fork fdroiddata 仓库、编写 metadata 文件、本地验证、提交 Merge Request。
fdroiddataF-Droid 的应用元数据集中维护在 fdroid/fdroiddata 仓库中,平台是 GitLab,不是 GitHub。提交流程也是标准的 fork + Merge Request 模式。
处理步骤如下:
fdroid/fdroiddata 并 fork 到自己的命名空间。命令如下:
git clone --depth=1 git@gitlab.com:<你的用户名>/fdroiddata.git
cd fdroiddata
git checkout -b com.example.myapp
cp templates/build-gradle.yml metadata/com.example.myapp.yml
分支名和 metadata 文件名都应使用应用的 applicationId,不是项目名。WakeUpScreen 对应的是 com.symeonchen.wakeupscreen。
这一步最容易出错。下面是 WakeUpScreen 的一个示例配置:
Categories:
- System
License: GPL-3.0-only
AuthorName: riko
AuthorEmail: example@example.com
WebSite: https://riko2chen.github.io/WakeUpScreen/
SourceCode: https://github.com/riko2chen/WakeUpScreen
IssueTracker: https://github.com/riko2chen/WakeUpScreen/issues
Changelog: https://github.com/riko2chen/WakeUpScreen/blob/HEAD/docs/CHANGELOG.md
AutoName: WakeUpScreen
RepoType: git
Repo: https://github.com/riko2chen/WakeUpScreen.git
Builds:
- versionName: 3.0.3
versionCode: 30300
commit: 456b21655618b0baffc27328e01199aa2c2f9a28
subdir: app
gradle:
- yes
AllowedAPKSigningKeys: <SHA-256 cert 指纹,小写无空格>
AutoUpdateMode: Version
UpdateCheckMode: Tags
CurrentVersion: 3.0.3
CurrentVersionCode: 30300
需要重点关注的字段如下:
Categories:使用 F-Droid 的预设分类,例如 System、Connectivity、Internet、Multimedia。License:必须填写 SPDX 标识符,例如 GPL-3.0-only、Apache-2.0。Builds.commit:必须填写完整的 commit hash,不要写 tag 名。AllowedAPKSigningKeys:限制只接受由指定证书签名的版本,属于供应链安全相关字段。Changelog:推荐使用 /HEAD/ 指向默认分支,避免写死 /master/ 或 /main/。签名证书摘要可以这样提取:
~/Library/Android/sdk/build-tools/35.0.0/apksigner verify --print-certs your-release.apk
取 Signer #1 certificate SHA-256 digest: 对应的值,去掉空格并转换为小写。
本地验证分为两类:
readmeta、rewritemeta、lint、checkupdatesfdroid build轻量检查通常都应执行:
brew install fdroidserver
cd ~/Project/fdroiddata
fdroid readmeta
fdroid rewritemeta com.example.myapp
fdroid lint com.example.myapp
fdroid checkupdates --allow-dirty com.example.myapp
这几条命令的作用分别是:
rewritemeta:按 F-Droid 的格式规范重写 YAML,第一次提交前建议必跑。lint:检查字段完整性、类别合法性和 URL 规范。checkupdates:验证版本检测配置是否能识别新版本。如果本地环境合适,还可以继续执行完整构建验证。官方 buildserver 镜像的典型用法如下:
git clone --depth=1 https://gitlab.com/fdroid/fdroidserver ~/fdroidserver
docker run --rm -itu vagrant --entrypoint /bin/bash \
-v ~/Project/fdroiddata:/build:z \
-v ~/fdroidserver:/home/vagrant/fdroidserver:Z \
registry.gitlab.com/fdroid/fdroidserver:buildserver
进入容器后再执行:
. /etc/profile
export PATH="$fdroidserver:$PATH" PYTHONPATH="$fdroidserver"
export JAVA_HOME=$(java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | awk -F'=' '{print $2}' | tr -d ' ')
cd /build
fdroid build -v -l com.example.myapp
如果构建失败,常见原因包括:构建时依赖外部下载、JDK 或 NDK 版本不匹配、ProGuard 规则依赖闭源库。
正式提 MR 之前,建议先到 Request For Packaging 仓库 搜索应用名。很多项目在开发者提交前,已经被用户提过 RFP 请求。
WakeUpScreen 对应的记录是 rfp#2023 “New APP: WakeUpScreen”。如果找到相关 issue,可以在 MR 描述中加入 Closes rfp#2023,合并后会自动关闭。
命令示例:
curl -sSL "https://gitlab.com/api/v4/projects/fdroid%2Frfp/issues?search=YOUR_APP_NAME&state=all" \
| python3 -m json.tool
提交 metadata 的基本步骤如下:
git add metadata/com.example.myapp.yml
git commit -m "New App: com.example.myapp"
git push origin com.example.myapp
创建 MR 时需要特别确认两件事:
fdroid/fdroiddata 的 master提交后,GitLab CI 会自动执行格式检查、版本检查和构建流程。CI 通过后,再等待 packager review。
下面几项问题都在实际提交中遇到过。如果项目结构相似,基本都可能复现。
checkupdates 不能直接解析 gradle.properties 引用UpdateCheckMode: Tags 是最常见的配置,但默认解析逻辑更适合这种写法:
versionCode 30300
versionName '3.0.3'
如果 build.gradle 中写的是:
versionCode project.AppVersionCode
versionName project.AppVersionName
那么 fdroid checkupdates 很可能会报:
ERROR: Couldn't find any version information
原因不是 Gradle 配置有误,而是 fdroidserver 的版本检测并不会执行 Gradle,只会按规则读取文件内容。
这里一开始尝试过三种方案:
build.gradleUpdateCheckMode: NoneHTTP 从 gradle.properties 的 raw URL 提取版本最初选的是第三种,但在 review 阶段收到更合理的建议:继续使用 Tags,同时通过 UpdateCheckData 指定仓库内文件路径和提取规则。例如:
AutoUpdateMode: Version
UpdateCheckMode: Tags
UpdateCheckData: gradle.properties|AppVersionCode=(\d+)|.|AppVersionName=([\d.]+)
这种写法的好处是:
gradle.properties 作为版本号唯一来源的项目结构F-Droid 文档建议在提交前执行一次 fdroid build。但在 Apple Silicon 上,这一步通常成本很高,原因主要有三层:
brew install docker 只会安装 CLI,不会提供 daemon,通常还需要 Colima 或 Docker Desktop。registry.gitlab.com/fdroid/fdroidserver:buildserver 镜像长期只有 amd64 版本。amd64 容器需要通过 QEMU 模拟,Android 构建速度和稳定性都不理想。理论上可以通过以下方式勉强运行:
colima delete
colima start --arch x86_64 --memory 8 --cpu 4
但镜像需要重新下载,整体耗时较长,对一次性提交流程并不划算。
更实际的做法是:
rewritemeta、lint、checkupdatesfdroid build 的原因对 Apple Silicon 用户来说,这通常是更省时间的路径。
rewritemeta 和 CI 结果可能不一致提交后第一次 CI 失败,提示 metadata 文件需要重新执行 rewritemeta。典型报错类似这样:
These files need rewritemeta:
metadata/com.symeonchen.wakeupscreen.yml
These are the formatting issues:
- UpdateCheckData: https://raw.githubusercontent.com/riko2chen/WakeUpScreen/HEAD/gradle.properties|...
+ UpdateCheckData:
+ https://raw.githubusercontent.com/riko2chen/WakeUpScreen/HEAD/gradle.properties|...
问题在于,CI 使用的 fdroidserver 版本和本地通过 Homebrew 安装的版本不一定一致,输出格式也可能不同。实际遇到的情况是:
Changelog、AllowedAPKSigningKeys、UpdateCheckData 都改成了换行形式UpdateCheckData 换行最后的处理方式不是完全相信本地输出,而是以 CI 给出的 diff 为准,逐项对齐。
这里的经验比较直接:
fdroid rewritemetaMR 模板里有一个选项是「Enable Reproducible Builds」。这个选项不只是构建优化,而是会影响后续升级路径。
两种模式的差异如下:
难点在于,想做到可重现构建,往往需要继续处理时间戳、路径、压缩顺序等细节,对首次提交并不友好。
如果应用本身是轻量工具,且没有强烈的跨商店切换需求,第一次提交先不启用 Reproducible Builds 往往更现实。相反,如果应用保存大量本地数据,例如密码管理器、笔记工具或数据库类产品,就值得单独规划一次可重现构建改造。
CI 全部通过后,packager 的 review 仍然给出了两条关键意见。
第一条是:Builds.commit 必须使用完整 commit hash,不能写 tag。
原因在于 tag 是可移动引用,而 hash 才能稳定指向唯一内容。获取 hash 的方式很简单:
git rev-list -n 1 v3.0.3
git ls-remote https://github.com/riko2chen/WakeUpScreen.git refs/tags/v3.0.3
第二条是:版本检测应优先使用 Tags,并通过 UpdateCheckData 补上对 gradle.properties 的支持,而不是改用 HTTP。最终推荐配置如下:
Builds:
- versionName: 3.0.3
versionCode: 30300
commit: 456b21655618b0baffc27328e01199aa2c2f9a28
subdir: app
gradle:
- yes
AllowedAPKSigningKeys: 7044dcb7b20edcefdb22923e649c84187e3aa818fc3a58dfcb2eccc92cddef1c
AutoUpdateMode: Version
UpdateCheckMode: Tags
UpdateCheckData: gradle.properties|AppVersionCode=(\d+)|.|AppVersionName=([\d.]+)
这部分的经验可以归纳成两点:
把一个开源 Android 应用提交到 F-Droid,核心步骤可以压缩成下面几项:
fdroiddata,编写 metadata yml。rewritemeta、lint、checkupdates。和 Google Play 相比,F-Droid 的流程多了元数据维护、公开 review 和服务器构建这几步,但换来的好处也很明确:提交记录、构建过程和元数据修改都可以追溯,整个分发链路保持开放。
对开源 Android 应用来说,F-Droid 仍然是一个值得维护的发布渠道。即使不把它作为主渠道,至少也可以把它作为对外分发的补充选项。WakeUpScreen 这次的提交过程没有特别复杂的技术难点,真正花时间的部分,主要是理解 F-Droid 的约束和习惯用法。如果项目本身已经满足开源、无闭源依赖和可重复发布这几个前提,后续流程基本都可以标准化。
本文基于 WakeUpScreen 的实际提交过程整理。文中的命令、字段示例和问题记录都来自真实操作。
If you have anything you'd like to discuss, any ideas you want to bounce around, please send me a message.