这篇不是官方文档的中文翻译。我把 F-Droid 客户端的 APK 安装包下载到本地,用脚本把它当成 ZIP 打开,逐层解析了它的 AndroidManifest、签名块、资源表与多个 DEX 文件,把量出来的结果如实记在这里。文中每一个数字都来自这台机器上的实测,文末给出了复现命令,你可以自己跑一遍核对。
先看这组最关键的数据。它既是判断文件有没有被改动的依据,也是后面所有分析的起点。
| 文件大小 | 12,537,183 字节(11.96 MB) |
|---|---|
| 版本名 / 版本号 | 2.0.1 / 2000051 |
| 包名 | org.fdroid.fdroid |
| 最低系统要求 | Android 7.0(API 24) |
| 目标系统 | Android 15(API 37) |
| 编译 SDK | Android 15(API 37) |
| 安装位置 | auto(由系统决定,允许移至 SD 卡) |
| 包内文件条目 | 973 个 |
| SHA-256 | 83d3fe522281c3cb89fce3bc05038f81b3c3b32c108536941f184c5f9bb53778 |
| SHA-1 | cc73258da201494f63351e80b8dcf3ea49828b82 |
| MD5 | 28e051983f2895049621201ba409e2bc |
| 签名证书 SHA-256 | 43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab |
APK 本质是个 ZIP 包。把 973 个条目按目录归并后,体积构成如下。这里同时列出压缩前和压缩后的大小,因为 Android 安装时看的是解压后的体积。
| 组成部分 | 未压缩 | 压缩后 | 占比 | 文件数 |
|---|---|---|---|---|
| classes2.dex(次 DEX) | 9.18 MB | 3.61 MB | 37.66% | 1 |
| classes.dex(主 DEX) | 8.86 MB | 3.65 MB | 36.38% | 1 |
| resources.arsc(资源索引表) | 1.96 MB | 1.96 MB | 8.05% | 1 |
| 其他 DEX(附加代码) | 1.58 MB | 567.3 KB | 6.50% | 1 |
| kotlin / okhttp3 / org 资源 | 1.40 MB | 1.19 MB | 5.73% | 16 |
| res/ 资源目录 | 894.3 KB | 626.9 KB | 3.58% | 703 |
| META-INF 签名与清单 | 290.2 KB | 107.8 KB | 1.16% | 147 |
| assets/ 资产文件 | 165.3 KB | 66.5 KB | 0.66% | 96 |
| lib/ 原生库 | 36.5 KB | 36.5 KB | 0.15% | 4 |
| AndroidManifest.xml | 29.3 KB | 6.1 KB | 0.12% | 1 |
包内解压后的总大小是 24.37 MB,压缩后 11.79 MB,而 APK 文件本身 12,537,183 字节(11.96 MB)——多出来的约 0.17 MB 是 ZIP 目录结构、APK 签名块和结尾记录等容器开销。
第一个值得注意的发现:代码占了八成。主 DEX、次 DEX 加一个附加 DEX 合计 19.63 MB,占解压总量的 80.5%。这意味着 F-Droid 客户端几乎全部由 Java/Kotlin 代码实现。
第二个发现更极端:原生库只有 36.5 KB。整个包里只有 1 个 .so 文件(libandroidx.graphics.path.so),为 4 种 CPU 架构各打了一份,加起来 36.5 KB,占全包 0.15%。具体是:
| CPU 架构 | 设备类型 | 库大小 |
|---|---|---|
| arm64-v8a | 主流安卓手机(64 位) | 9.9 KB |
| armeabi-v7a | 老旧安卓手机(32 位) | 7.1 KB |
| x86_64 | 64 位平板 / 模拟器 | 10.5 KB |
| x86 | 32 位平板 / 模拟器 | 9.1 KB |
几乎没有原生代码,带来两个实际好处:一是同一份安装包能覆盖从老旧 32 位手机到 x86 模拟器的全部设备类型,不需要按架构分发;二是原生代码是安全审计最难的部分,代码量越小,可被隐藏的东西越少。
另一个明显变化是 resources.arsc 从 1.23.2 的 3.91 MB 降到了 1.96 MB(占比 8.05%)。原因是 2.0.1 对资源字符串做了精简,字符串池从 44,157 条压缩到 20,386 条,这部分体积下降接近一半。多语言文案仍内置其中,简体中文无需额外下载。
这是下载站最该做也最少做的一步。我用二进制方式定位并解析了 APK 里的签名块,结果是这个包同时带有 v1、v2、v3 三代签名,外加一个 verity 完整性校验块(共四重保护)。
| 签名方案 | 标识 / 位置 | 适配系统 | 实测结果 |
|---|---|---|---|
| v1(JAR 签名) | META-INF/CIARANG.SF、CIARANG.RSA | 全部 Android 版本 | 存在 |
| v2(APK 签名方案 v2) | 签名块 ID 0x7109871a | Android 7.0 及以上 | 存在 |
| v3(APK 签名方案 v3) | 签名块 ID 0xf05368c0 | Android 9.0 及以上 | 存在 |
| verity(完整性校验) | 签名块 ID 0x42726577 | Android 11 及以上 | 存在 |
签名证书的主体信息是 C=UK, ST=Unknown, L=Wetherby, O=Unknown, OU=Unknown, CN=Ciaran Gultnieks,有效期到 2037 年 12 月 8 日。这个 CN 正是 F-Droid 项目创始人的名字,也是 F-Droid 官方客户端一直使用的签名密钥——与 1.23.2 使用的是同一个官方密钥,说明 2.0.1 是从官方构建正常升级而来,不是换包。证书有效期一直排到 2037 年,不是那种几个月就过期的临时签名。
这四代签名是递进关系,不是重复劳动:v1 是老的 JAR 签名,兼容所有系统但校验粒度粗;v2 从 Android 7.0 起对整个 APK 做分块哈希校验,改一个字节就会失败;v3 从 Android 9.0 起支持签名密钥轮换,v3 块里带 minSDK / maxSDK 字段;verity 进一步提供基于哈希树的完整性校验。只要这四个签名都能通过校验,就能确定这个包从官方构建出来之后没有被改过、没有被重新打包、没有被人塞入额外代码。
从 AndroidManifest 里解析出 30 项权限声明,比 1.23.2 的 28 项多了 2 项。变化来自:新版用更细粒度的前台服务类型权限(FOREGROUND_SERVICE_DATA_SYNC、FOREGROUND_SERVICE_CONNECTED_DEVICE)替代了泛用的前台服务声明,并新增 ACCESS_LOCAL_NETWORK 用于本地设备互换;同时移除了旧版的 NFC 与 USB_PERMISSION(本地互换改走局域网 / 蓝牙)。对应用商店来说,权限数量本身就是正常范围——它要下载、要安装、要管理更新。关键是看每一项有没有对应到具体功能。下面按用途分成四类。
| 权限 | 用途 |
|---|---|
| INTERNET | 连接软件仓库,下载应用索引和 APK |
| ACCESS_NETWORK_STATE | 判断当前是否联网、是 Wi-Fi 还是移动数据 |
| ACCESS_WIFI_STATE | 获取 Wi-Fi 信息,用于局域网内互换应用 |
| REQUEST_INSTALL_PACKAGES | 请求安装 APK,应用商店的核心权限 |
| REQUEST_DELETE_PACKAGES | 请求卸载应用 |
| UPDATE_PACKAGES_WITHOUT_USER_ACTION | 自动更新应用时不再逐个弹窗(需用户先授权) |
| FOREGROUND_SERVICE | 下载和安装过程在前台服务中运行,不被系统杀掉 |
| FOREGROUND_SERVICE_DATA_SYNC | 数据同步型前台服务,用于后台下载与仓库同步 |
| FOREGROUND_SERVICE_CONNECTED_DEVICE | 连接设备型前台服务,用于本地互换应用 |
| POST_NOTIFICATIONS | 发出「有应用可更新」的通知 |
| RECEIVE_BOOT_COMPLETED | 开机后自动检查应用更新 |
| WAKE_LOCK | 下载大文件时保持 CPU 唤醒 |
| MANAGE_EXTERNAL_STORAGE | 读写存储中的 APK 文件与本地仓库 |
| QUERY_ALL_PACKAGES | 读取已安装应用列表,用于比对版本判断更新 |
| ENFORCE_UPDATE_OWNERSHIP | 声明应用更新所有权,防止其他商店抢更新 |
其中 QUERY_ALL_PACKAGES 是 Google Play 政策里管得最严的一项,因为它能看到设备上装了什么。但对应用商店来说,不知道已装应用的版本就无法判断哪些需要更新,属于功能必需。F-Droid 的处理方式是:这个列表只在本地比对,不上报。
F-Droid 有一个特色功能:两台设备之间不经过服务器,直接通过局域网、蓝牙或 Wi-Fi 热点互传已下载的应用。这在没网的环境下很有用,代价就是需要下面这些权限。
| 权限 | 用途 |
|---|---|
| CHANGE_WIFI_MULTICAST_STATE | 局域网内组播发现附近设备 |
| CHANGE_NETWORK_STATE | 开启本地热点做应用互换 |
| CHANGE_WIFI_STATE | 同上,控制 Wi-Fi 状态 |
| ACCESS_LOCAL_NETWORK | 访问本地网络,用于设备间直连互换(2.0 新增) |
| BLUETOOTH | 蓝牙互换应用(旧版蓝牙权限) |
| BLUETOOTH_ADMIN | 管理蓝牙配对(旧版蓝牙权限) |
| BLUETOOTH_SCAN | 扫描附近蓝牙设备(Android 12 起) |
| BLUETOOTH_CONNECT | 连接蓝牙设备传文件(Android 12 起) |
| BLUETOOTH_ADVERTISE | 蓝牙广播自身,便于被附近设备发现(2.0 新增) |
| 权限 | 用途 |
|---|---|
| READ_EXTERNAL_STORAGE | 读取存储中已有的 APK 文件 |
| WRITE_EXTERNAL_STORAGE | 把下载的 APK 写入存储 |
| WRITE_SETTINGS | 修改部分系统设置项 |
| CAMERA | 扫描二维码添加第三方软件仓库 |
CAMERA 只用于扫二维码加仓库,不用于拍照录像,这一点可以在实际使用中验证:不扫码时系统不会弹出相机调用提示。相较 1.23.2,2.0.1 移除了 NFC 与 USB_PERMISSION 两项本地互换权限,相关互换功能改由局域网 / 蓝牙承载。
剩下两项不是向系统申请的权限,而是 F-Droid 自己声明的,用于组件间通信隔离:
org.fdroid.fdroid.permission.UPDATE_REPOS — 保护仓库更新接口,只允许自身调用org.fdroid.fdroid.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION — AndroidX 自动生成的权限,用于防止动态广播接收器被外部应用调用这是很多人关心 F-Droid 的核心原因。我把多个 DEX 文件里的字符串全部提取出来(共 453 万字符),逐一比对常见的商业追踪与广告 SDK 特征。
| 检测项 | 类型 | 结果 |
|---|---|---|
| Google Analytics | 行为分析 | ✓ 未发现 |
| Firebase Analytics | 行为分析 | ✓ 未发现 |
| Firebase Crashlytics | 崩溃上报 | ✓ 未发现 |
| AppsFlyer | 归因追踪 | ✓ 未发现 |
| Adjust | 归因追踪 | ✓ 未发现 |
| Facebook SDK | 社交追踪 | ✓ 未发现 |
| 友盟 Umeng | 国内统计 | ✓ 未发现 |
| 腾讯 Bugly | 崩溃上报 | ✓ 未发现 |
| Sentry | 错误监控 | ✓ 未发现 |
| AdMob | 广告 | ✓ 未发现 |
10 项商业追踪与广告 SDK,全部零命中。这个结果也印证了 F-Droid 的收录原则:官方仓库里的应用不允许包含专有追踪器,客户端自身同样遵守这条规则。
排除了商业追踪之后,我统计了确实存在的开源组件:
| 组件 | 用途 | 命中次数 |
|---|---|---|
| OkHttp3 | 网络请求库(Apache 2.0) | 308 |
| BouncyCastle | 加密与证书校验(含 APK 签名校验) | 634 |
| Kotlin 协程 | 异步任务调度 | 18 |
| Glide / Coil | 应用图标加载 | 1 |
| SLF4J | 日志门面 | 2 |
| Google Play 服务 | 仅 1 处引用(良性,未接入 GMS) | 1 |
Google Play 服务 仅有 1 处字符串引用,属于兼容性判断而非实际接入 GMS——它不会因此产生 Google 依赖或后台上报。这与「默认开启、静默上传」的商业崩溃上报有本质区别。
从 DEX 中提取到 36 个不同的硬编码域名。我逐一核对,全部是开源项目的基础设施,没有一个属于广告、统计或数据变现服务商。以下是其中具有代表性的一部分:
| 域名 | 用途 |
|---|---|
| f-droid.org | F-Droid 官方主仓库 |
| github.com | 应用源码仓库链接 |
| gitlab.com 系列 | 部分应用的源码托管 |
| guardianproject.info | Guardian Project 第三方仓库 |
| apt.izzysoft.de | IzzyOnDroid 第三方仓库 |
| briarproject.org | Briar 项目仓库 |
| microg.org | microG 项目相关 |
| gateway.ipfs.io | IPFS 网关,用于分布式仓库 |
| liberapay.com / opencollective.com | 开源项目捐赠渠道 |
| spdx.org | 开源许可证标识标准 |
| flattr.com | 创作者小额打赏 |
| www.bouncycastle.org / www.slf4j.org | 所依赖开源库官网 |
整张域名清单的性质很一致:要么是软件仓库地址,要么是开源项目的主页和捐赠页,要么是所依赖开源库的官网。没有出现任何一家广告联盟、数据统计或用户画像服务商的域名。
我解析了 resources.arsc 的资源字符串池,共 20,386 条字符串。在其中直接检索到「应用」「下载」「设置」「更新」「安装」「权限」「搜索」「仓库」「版本」「文件」「网络」等简体中文界面文案,说明客户端内置简体中文,安装完成后无需额外下载语言包。
resources.arsc 在 2.0.1 中为 1.96 MB(占比 8.05%),比 1.23.2 的 3.91 MB 明显精简,但多语言界面文案依然完整内置,其中就含简体中文。
实测里最值得注意的变化是最低系统要求:minSdkVersion 从 Android 6.0(API 23)提升到了 Android 7.0(API 24)——这意味着 2.0 系列不再支持 2015 年之前发布的老设备,用 Android 6.0 机器的用户需要停留在 1.23.2。
与此同时,targetSdkVersion 与 compileSdkVersion 都已对齐到 API 37,不像 1.23.2 那样存在 targetSdk(30)远低于 compileSdk(35)的落差。对齐之后,应用会按较新的系统行为规范运行,旧的兼容行为不再生效——这是一次彻底的现代化,而非简单的版本号推进。
30 项权限里,有 9 项是给「局域网 / 蓝牙互换应用」这个功能准备的,4 项是给存储读写和二维码扫码准备的。如果你不需要互换功能,可以在系统设置里单独关掉蓝牙、Wi-Fi 相关权限,不影响下载和安装这两个核心功能。相较 1.23.2,2.0.1 新增了细粒度的前台服务类型权限和 ACCESS_LOCAL_NETWORK,并移除了 NFC、USB_PERMISSION。
上面的数据不希望你只信我的结论。以下命令可以在你自己的机器上把关键结果重新跑一遍。
Windows 自带的 certutil 就能算,不需要装任何工具:
certutil -hashfile F-Droid-2.0.apk SHA256
certutil -hashfile F-Droid-2.0.apk SHA1
certutil -hashfile F-Droid-2.0.apk MD5
macOS 或 Linux 用:
sha256sum F-Droid-2.0.apk
sha1sum F-Droid-2.0.apk
md5sum F-Droid-2.0.apk
把输出的十六进制串和本文第一节的表格对照,完全一致就说明你手上的文件和本次测试的是同一个。
需要 Android SDK 的 build-tools,里面有 apksigner:
apksigner verify --verbose --print-certs F-Droid-2.0.apk
正常情况下会看到 v1、v2、v3 三行都是 true,证书主体是 CN=Ciaran Gultnieks。
unzip -l F-Droid-2.0.apk
会列出全部 973 个条目及其压缩前后大小,可以核对第二节的体积构成表。
aapt2 dump badging F-Droid-2.0.apk
输出里包含 package name、versionCode、versionName、sdkVersion、targetSdkVersion、uses-permission 全部信息,可核对第四节。
把这次实测的结果汇总成一句话:这是一个体积不大(11.96 MB)、几乎纯 Java/Kotlin 实现(原生库仅 36.5 KB)、带 v1+v2+v3+verity 四重签名、不含任何商业追踪与广告 SDK 的应用商店客户端。
几点实际参考价值:
需要补充一点:本文的测试对象是 2.0.1 这一个版本。后续版本更新后,体积、权限、SDK 版本都可能变化,届时会重新实测更新。想下载这个版本,可以前往 F-Droid 下载页;想了解安装步骤,参见 F-Droid 安装教程。