ApkDepot 是一个轻量级的私有化 APK 托管与分发平台,支持版本管理、灰度发布和强制更新控制。
- 地址:
http://<服务器IP>:8888 - 功能: 默认展示所有已上传的 APK 列表,支持下载。
- 点击右上角的 "Login"。
- 输入部署时设置的账号密码(默认:
admin/password,请在docker-compose.yml中修改)。 - 登录成功后,你将看到 "Upload" 入口以及每行 APK 的 "Edit" (策略管理) 和 "Delete" 按钮。
- 点击顶部导航栏的 "Upload"。
- 选择你的
.apk文件(文件名任意,系统会自动重命名为包名_版本号.apk)。 - 上传成功后,系统会自动解析包名、版本号、图标等信息,并更新元数据。
- 注意: 如果上传的版本号大于当前记录的最新版,它将自动成为新的 Latest Version。默认发布状态为 暂停 (0% 灰度)。
在列表页,找到状态为 Latest 的 APK(通常是第一行),点击 "Edit" 按钮。
- Rollout Rate (灰度比例):
- 拖动滑块或直接输入数字来控制发布范围。
0%: 暂停发布,任何人都检测不到此更新。10%: 只有约 10% 的设备能检测到更新(基于设备ID哈希)。100%: 全量发布,所有旧版本设备都会收到提示。
- Min Force Version Code (强制基线):
- 输入一个整数(VersionCode)。
- 所有低于此版本号的用户,APP端会弹出不可关闭的强制更新窗口。
- 示例: 最新版是 102,你发现 100 版有严重Bug,于是将基线设为
101。那么 100 版用户会被强制升级,101 版用户则可选择性升级。
要在你的 Android 应用中集成自动更新功能,请遵循以下步骤。
权限声明 (AndroidManifest.xml):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />FileProvider 配置: (用于 Android 7.0+ 安装 APK)
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>res/xml/provider_paths.xml:
<paths>
<external-cache-path name="external_cache_files" path="." />
</paths>你需要封装一个单例来处理检查和更新逻辑。
为了保证灰度的一致性,你需要一个持久化的唯一ID。
fun getStableDeviceId(context: Context): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var uuid = prefs.getString("device_id", null)
if (uuid == null) {
uuid = UUID.randomUUID().toString()
prefs.edit().putString("device_id", uuid).apply()
}
return uuid!!
}API: GET /api/check-update
参数:
packageName:com.example.appversionCode:100(当前本地版本)deviceId:abc-123...(用于灰度计算)
响应 JSON:
{
"hasUpdate": true, // 是否有新版本
"force": false, // 是否强制更新
"versionCode": 102, // 新版本号
"versionName": "1.0.2",
"downloadUrl": "/apks/com.example.app_102.apk" // 相对路径
}fun checkUpdate(context: Context) {
val url = "$SERVER_URL/api/check-update?..."
httpClient.get(url) { response ->
if (response.hasUpdate) {
// 拼接完整下载地址
val fullUrl = "$SERVER_URL${response.downloadUrl}"
if (response.force) {
showForceUpdateDialog(context, fullUrl)
} else {
showOptionalUpdateDialog(context, fullUrl)
}
}
}
}为测试人员或高级用户提供一个“版本列表”页面。
API: GET /api/version-list?packageName=...
响应:
{
"minForceVersionCode": 101, // 安全基线
"versions": [
{ "versionCode": 102, "versionName": "1.0.2", ... },
{ "versionCode": 101, "versionName": "1.0.1", ... }
]
}开发建议:
- 使用
RecyclerView展示列表。 - 安全校验: 在
onBindViewHolder中,如果item.versionCode < response.minForceVersionCode,则禁用按钮并变灰,提示“版本过低不可用”,防止用户回退到有风险的旧版本。
- Q: 上传后为什么收不到更新?
- A: 检查管理后台,确认 Rollout Rate 是否大于 0。如果是新版本,默认是 0% (暂停发布)。
- Q: 为什么有的手机能收到,有的收不到?
- A: 这是灰度发布的特性。灰度基于 DeviceID。如果你设置了 50%,那么只有一半的设备能收到。若想全员测试,请设为 100%。
- Q: 404 Not Found?
- A: 检查 App 端配置的服务器地址是否多加了
/api后缀。正确格式应为http://192.168.x.x:8888。
- A: 检查 App 端配置的服务器地址是否多加了