svg
开源 Android 应用的 F-Droid 上架之旅

开源 Android 应用的 F-Droid 上架之旅

2026 年 4 月 7 日

最近把几年前的开源 Android 项目 WakeUpScreen 提交到了 F-Droid。流程本身不复杂,但第一次处理时,容易在元数据格式、版本检测和本地验证环境上浪费时间。本文按实际提交过程整理一遍,覆盖准备工作、提交流程和几个常见问题。

一、为什么选择 F-Droid

在 Google Play 之外,F-Droid 适合作为开源 Android 应用的另一条分发渠道。两者的差异主要有三点:

  • 应用必须是 FOSS(Free and Open Source Software),不能依赖闭源组件。Google Play Services、Firebase 这类常见依赖通常不符合要求。
  • F-Droid 收录的是源代码和元数据,而不是开发者直接上传的 APK。服务器会从指定的提交重新构建,并使用 F-Droid 的签名发布。
  • 提交过程通过 GitLab Merge Request 完成,没有注册费,审核重点也放在可构建性、许可证和元数据完整性上。

适合提交到 F-Droid 的应用通常具备这些特征:代码开源、不依赖 Google 服务、重视隐私、希望提供 Google Play 之外的下载方式。WakeUpScreen 基本符合这组条件:使用 GPL-3.0 协议、不联网、不申请网络权限,也没有统计或广告 SDK。

二、提交前的准备

正式提交前,先处理三件事:依赖和许可证自检、补齐 fastlane metadata、准备好可复用的签名 APK 发布流程。

2.1 依赖与许可证自检

最先需要确认的是依赖是否触碰 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:mmkvcom.blankj:utilcodex、Glide、AndroidX、Compose 都符合要求。这里最容易出问题的通常是统计、推送和广告类 SDK。

2.2 按 fastlane 结构补齐 metadata

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/
    └── ...

这里有几个细节需要提前处理:

  • 每个 locale 目录彼此独立,标题、短描述和完整描述都需要单独提供。
  • changelog 文件名必须使用 versionCode,不是 versionName
  • 图标建议使用 512 x 512 尺寸,可以直接复用 Android 项目中的 ic_launcher-web.png

WakeUpScreen 的截图直接复用了现有素材,描述文本则从 README 提炼而来。因为已有英文、中文和意大利语三份 README,这部分准备成本不高。

2.3 准备自动发布签名 APK

这一步不是强制要求,但实际能省下不少时间。提交 F-Droid 时,需要填写签名证书的 SHA-256 指纹。如果已经有 GitHub Actions 自动构建并发布签名 APK,这个值只需要下载 release 后执行一次 apksigner verify 即可。

WakeUpScreen 使用一份 release.yml 工作流监听 gradle.properties 中的 AppVersionName。每次版本号变更后,工作流会自动完成这些步骤:

  • 构建签名 APK
  • 打 tag
  • 创建 GitHub Release

这样在准备 F-Droid 元数据时,不需要再手动处理 keystore、签名和上传。

三、提交流程

整体流程可以拆成四步:fork fdroiddata 仓库、编写 metadata 文件、本地验证、提交 Merge Request。

3.1 注册 GitLab 并 fork fdroiddata

F-Droid 的应用元数据集中维护在 fdroid/fdroiddata 仓库中,平台是 GitLab,不是 GitHub。提交流程也是标准的 fork + Merge Request 模式。

处理步骤如下:

  1. 注册 GitLab 账号。
  2. 访问 fdroid/fdroiddata 并 fork 到自己的命名空间。
  3. clone 本地仓库并新建分支。
  4. 从模板复制一份新的 metadata 文件。

命令如下:

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

3.2 编写 metadata yml

这一步最容易出错。下面是 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 的预设分类,例如 SystemConnectivityInternetMultimedia
  • License:必须填写 SPDX 标识符,例如 GPL-3.0-onlyApache-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: 对应的值,去掉空格并转换为小写。

3.3 本地验证

本地验证分为两类:

  • 轻量检查:readmetarewritemetalintcheckupdates
  • 完整构建:fdroid 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 规则依赖闭源库。

3.4 提交 Merge Request

正式提 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 时需要特别确认两件事:

  1. target project 和 target branch 是否指向上游 fdroid/fdroiddatamaster
  2. description 是否完整填写,尤其是 RFP 关联信息和 Reproducible Builds 选项

提交后,GitLab CI 会自动执行格式检查、版本检查和构建流程。CI 通过后,再等待 packager review。

四、几个容易踩的坑

下面几项问题都在实际提交中遇到过。如果项目结构相似,基本都可能复现。

4.1 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,只会按规则读取文件内容。

这里一开始尝试过三种方案:

  1. 直接把版本号写回 build.gradle
  2. 使用 UpdateCheckMode: None
  3. 改用 HTTPgradle.properties 的 raw URL 提取版本

最初选的是第三种,但在 review 阶段收到更合理的建议:继续使用 Tags,同时通过 UpdateCheckData 指定仓库内文件路径和提取规则。例如:

AutoUpdateMode: Version
UpdateCheckMode: Tags
UpdateCheckData: gradle.properties|AppVersionCode=(\d+)|.|AppVersionName=([\d.]+)

这种写法的好处是:

  • 仍然沿用 tag 作为版本发现来源
  • 不依赖外部 HTTP 请求
  • 能兼容 gradle.properties 作为版本号唯一来源的项目结构

4.2 Apple Silicon 上很难本地跑通 buildserver

F-Droid 文档建议在提交前执行一次 fdroid build。但在 Apple Silicon 上,这一步通常成本很高,原因主要有三层:

  1. brew install docker 只会安装 CLI,不会提供 daemon,通常还需要 Colima 或 Docker Desktop。
  2. registry.gitlab.com/fdroid/fdroidserver:buildserver 镜像长期只有 amd64 版本。
  3. 在 Apple Silicon 上运行 amd64 容器需要通过 QEMU 模拟,Android 构建速度和稳定性都不理想。

理论上可以通过以下方式勉强运行:

colima delete
colima start --arch x86_64 --memory 8 --cpu 4

但镜像需要重新下载,整体耗时较长,对一次性提交流程并不划算。

更实际的做法是:

  • 本地只执行 rewritemetalintcheckupdates
  • 把完整构建留给 F-Droid 的 CI
  • 在 MR 描述中说明本地未跑 fdroid build 的原因

对 Apple Silicon 用户来说,这通常是更省时间的路径。

4.3 本地 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 安装的版本不一定一致,输出格式也可能不同。实际遇到的情况是:

  • 本地版本把 ChangelogAllowedAPKSigningKeysUpdateCheckData 都改成了换行形式
  • CI 只要求 UpdateCheckData 换行

最后的处理方式不是完全相信本地输出,而是以 CI 给出的 diff 为准,逐项对齐。

这里的经验比较直接:

  • 第一次提交前,先执行一次本地 fdroid rewritemeta
  • 如果 CI 仍报格式问题,优先按 CI diff 修正
  • wrapped 行触发的 trailing space warning 通常不阻塞提交

4.4 Reproducible Builds 是一次性决策

MR 模板里有一个选项是「Enable Reproducible Builds」。这个选项不只是构建优化,而是会影响后续升级路径。

两种模式的差异如下:

  • 不启用 Reproducible Builds:F-Droid 使用自己的签名发布 APK。这样 F-Droid 和 Google Play 上的安装包签名不同,用户无法在两个渠道之间直接覆盖升级。
  • 启用 Reproducible Builds:需要先证明本地构建和 F-Droid 服务器构建结果逐字节一致,然后可以沿用开发者自己的签名,用户可以跨渠道升级。

难点在于,想做到可重现构建,往往需要继续处理时间戳、路径、压缩顺序等细节,对首次提交并不友好。

如果应用本身是轻量工具,且没有强烈的跨商店切换需求,第一次提交先不启用 Reproducible Builds 往往更现实。相反,如果应用保存大量本地数据,例如密码管理器、笔记工具或数据库类产品,就值得单独规划一次可重现构建改造。

4.5 Review 才会指出的配置问题

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.]+)

这部分的经验可以归纳成两点:

  • CI 通过不代表配置已经是最佳实践
  • 第一次提交时,packager 的 review 往往比文档更接近当前惯例

五、总结

把一个开源 Android 应用提交到 F-Droid,核心步骤可以压缩成下面几项:

  1. 清理依赖,确认许可证符合 FOSS 要求。
  2. 按 fastlane 结构补齐标题、描述、截图和更新记录。
  3. 为每个 release 打 tag,并准备可验证的签名 APK。
  4. 在 GitLab fork fdroiddata,编写 metadata yml。
  5. 本地执行 rewritemetalintcheckupdates
  6. 提交 Merge Request,并等待 CI 与人工 review。

和 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.

svg