GeneralUpdate.Avalonia.Android — 执行流程详解
目标读者: 需要在 Avalonia Android 应用中集成自动更新的开发者
阅读 完你将理解:
ValidateAsync如何从服务端查询并挑选最新 APK(调用方只提供当前版本)PrepareUpdateAsync与分阶段三步 API 的取舍HttpResumableApkDownloader的断点续传机制:HEAD 探测 + Range 请求 + Sidecar 元数据 + 原子重命名- 文件大小与 SHA256 的双重校验
- Android APK 安装的 FileProvider URI 授权流程
- 安装结果闭环:为什么"安装器已拉起"不等于"安装成功"
SemaphoreSlim(1,1)操作门与Dispose生命周期约定IUpdateEventDispatcher的 UI 线程调度机制- 多协议认证架构(HMAC / Bearer / API Key / Basic)与其发送范围
目录
- 架构总览
- 入口:GeneralUpdateBootstrap 工厂
- 两种调用姿势:一体化与分阶段
- ValidateAsync — 服务端查询与版本校验
- DownloadAndVerifyAsync — 下载与校验
- 断点续传:HttpResumableApkDownloader 深度解析
- 哈 希校验与文件大小验证
- LaunchInstallerAsync — APK 安装触发
- 安装结果闭环
- 并发安全与线程模型
- 事件调度与 UI 线程
- 多协议认证架构
- 关键代码路径索引
1. 架构总览
1.1 全接口抽象 + 工厂装配
Avalonia.Android 采用全接口抽象 + 工厂装配的设计,每个核心能力都有接口,可独立替换:
┌──────────────────────────────────────────────────────────────────────┐
│ GeneralUpdateBootstrap(静态工厂) │
│ CreateDefault(options, ...) → IAndroidBootstrap │
├──────────────────────────────────────────────────────────────────────┤
│ AndroidBootstrap(编排层) │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────┐ │
│ │ IVersion │ │ IUpdatePackage │ │ IUpdateDownloader │ │
│ │ Comparer │ │ Source │ │ HTTP 断点续传 │ │
│ │ 版本对比 │ │ 服务端查询协议 │ │ │ │
│ └────────────────┘ └────────────────┘ └────────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────┐ │
│ │ IHashValidator │ │ IApkInstaller │ │ IFileStorage │ │
│ │ SHA256 校验 │ │ 系统安装器 │ │ 文件系统抽象 │ │
│ └────────────────┘ └────────────────┘ └────────────────────┘ │
│ │
│ ┌────────────────────┐ ┌────────────────────┐ ┌───────────────┐ │
│ │ IInstallationStore │ │ IUpdateEvent │ │ IUpdateLogger │ │
│ │ 安装记录持久化 │ │ Dispatcher │ │ 日志 │ │
│ │ │ │ UI 线程调度 │ │ │ │
│ └────────────────────┘ └────────────────────┘ └───────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ HttpDownloadOptions(横切配置) │ │
│ │ SSL 策略 / 代理 / 超时 / 重试 / 认证 │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
默认实现与抽象接口的对应关系见组件参考文档。
1.2 两种调用姿势
| 姿势 | 方法 | 适用场景 |
|---|---|---|
| 一体化 | PrepareUpdateAsync(currentVersion, ct) | 宿主只需要"准备一个已校验的 APK",同一个操作锁内完成查询、比较、pre-check、下载与校验 |
| 分阶段 | ValidateAsync → DownloadAndVerifyAsync → LaunchInstallerAsync | 需要在检查后展示更新提示、在下载中显示进度条、在安装前要求用户确认 |
| 步骤 | 方法 | 职责 | 调用方控制点 |
|---|---|---|---|
| 0 | ValidateAsync(currentVersion, ct) | 查询服务端 + 版本对比 + pre-check | 决定是否继续 |
| 1 | DownloadAndVerifyAsync(packageInfo, ct) | 下载 + 大小校验 + SHA256 校验 | 显示进度 UI |
| 2 | LaunchInstallerAsync(packageInfo, path, ct) | 写入安装记录 + 触发系统安装器 | 用户确认后调用 |
| — | CheckInstallationAsync(currentVersion, ct) | 离线核对上次安装是否真正生效 | 启动 / 返回时调用 |
从 v0.0.1-beta.10 起,ValidateAsync 不再接收 UpdatePackageInfo:包元数据由组件依据
AndroidUpdateOptions.UpdateServer(或注入的 IUpdatePackageSource)自行查询,调用方只需提供设备上的当前版本,
拿到 UpdateCheckResult.PackageInfo 后再传给下载与安装方法。
2. 入口:GeneralUpdateBootstrap 工厂
public static class GeneralUpdateBootstrap
{
public static IAndroidBootstrap CreateDefault(
AndroidUpdateOptions options,
IAndroidContextProvider? contextProvider = null, // 默认 DefaultAndroidContextProvider
IAndroidActivityProvider? activityProvider = null, // 默认 NullAndroidActivityProvider
HttpClient? httpClient = null, // 外部客户端由宿主管理
IVersionComparer? versionComparer = null, // 默认 SystemVersionComparer
IUpdateEventDispatcher? eventDispatcher = null, // 默认 ImmediateEventDispatcher
IUpdateLogger? logger = null, // 默认 NoOpUpdateLogger
HttpDownloadOptions? httpOptions = null, // SSL / 代理 / 超时 / 重试 / 认证
IUpdatePackageSource? packageSource = null, // 默认 HttpUpdatePackageClient
IInstallationStore? installationStore = null) // 默认 JsonFileInstallationStore
{
// 1. 解析下载目录:options.DownloadDirectoryPath → <CacheDir>/update → <TempPath>/update
// 2. 解析安装记录路径:options.InstallationStateFilePath → <FilesDir>/update/installation.json
// 3. 创建共享的一个 HttpClient(传入外部客户端时借用,不改写其 Timeout/handler)
// 4. 装配 downloader / packageSource / hashValidator / installer / store 并返回 AndroidBootstrap
}
}
工厂的两个关键副作用值得注意:
- 下载目录与安装记录路径会被回填到
options(with表达式产生的副本),以便下载器与安装器使用同一份最终路径 DownloadBufferSize <= 0会立即抛ArgumentOutOfRangeException;Context不可用且未显式指定InstallationStateFilePath时抛InvalidOperationException
3. 两种调用姿势:一体化与分阶段
3.1 一体化:PrepareUpdateAsync
PrepareUpdateAsync 不会打开安装器或权限页面:它只负责把 APK 准备到"已校验、可安装"的状态。无更新或被 pre-check 跳过时 Success = true、IsReadyToInstall = false、UpdateFound = false。
3.2 分阶段:三步显式 API
3.3 完整生命周期与状态机
状态通过 SetState() 在锁内更新,可通过 GetSnapshot() 随时查询:
public UpdateStateSnapshot GetSnapshot()
{
lock (_sync)
{
return _snapshot; // { State, FailureReason, Message }
}
}
4. ValidateAsync — 服务端查询与版本校验
4.1 完整流程
4.2 服务端查询协议
IUpdatePackageSource 是查询入口,默认实现 HttpUpdatePackageClient 支持两种模式:
| 模式 | 触发条件 | 行为 |
|---|---|---|
| GeneralUpdate 验证协议 | UpdateServer.UseJsonEndpoint = false(默认) | POST 请求体 version / appKey / appType / platform / productId,解析 {"code":200,"body":[...]} |
| 静态 JSON | UpdateServer.UseJsonEndpoint = true | GET 单个 UpdatePackageInfo JSON;HTTP 204 或 null 表示无更新 |
默认协议下的包筛选规则:
body 中逐条检查
├── entry.IsFreeze == true → 跳过(冻结版本不推送)
├── entry.PackageType ∉ {null, 0, 2} → 跳过(非完整包)
├── entry.Format 不为 apk/.apk → 跳过(需 URL 以 .apk 结尾)
├── ValidateMetadata() 失败 → 抛 InvalidDataException
└── 通过 → 与当前 latest 比较,保留版本更高的
ValidateMetadata() 要求:Version 非空、DownloadUrl 是绝对 HTTP(S) 地址、FileSize >= 0、Sha256 为 64 位十六进制字符串。
4.3 失败分类
| 异常 | 上报结果 |
|---|---|
HttpRequestException / OperationCanceledException / IOException / TimeoutException | FailureReason = NetworkError |
JsonException / InvalidDataException / ArgumentException | FailureReason = InvalidMetadata |
未配置 UpdateServer 且未注入包源 | InvalidDataException("Configure UpdateServer or supply an IUpdatePackageSource.") → InvalidMetadata |
| 请求期间主动取消 | State = Canceled、FailureReason = Canceled |
| 等待操作锁时取消 | 抛 OperationCanceledException(不返回结果对象) |
请求、协议与元数据错误只通过 UpdateCheckResult.Success = false、FailureReason 和 AddListenerUpdateFailed 上报,
pre-check 回调不会执行,避免业务层把"查询失败"当作"有更新"。
4.4 版本比较器接口
public interface IVersionComparer
{
bool TryCompare(string currentVersion, string targetVersion,
out int compareResult, out string? errorMessage);
// compareResult > 0: targetVersion 更大(有更新)
// compareResult = 0: 相同
// compareResult < 0: targetVersion 更小(降级)
}
默认实现 SystemVersionComparer 使用 System.Version 进行语义化比较。查询阶段在挑选 body 中最新包时也会复用它。
5. DownloadAndVerifyAsync — 下载与校验
DownloadAndVerifyAsync 接受 packageInfo 参数,因此既可配合 ValidateAsync 的结果使用,也可在包信息来自业务自有渠道(例如从配置中心下发)时单独调用。
6. 断点续传:HttpResumableApkDownloader 深度解析
6.1 核心机制
下载流程
│
├── Phase 0: 元数据前置校验
│ DownloadUrl 是绝对 HTTP(S) 且 Sha256 非空,否则 → InvalidMetadata
│ 非 HTTPS 且未开启 AllowInsecureHttpDownloads → InvalidMetadata
│
├── Phase 1: HEAD 探测
│ 获取 Accept-Ranges / ETag / Content-Length / Last-Modified
│ HEAD 返回 405/501 → 退回 GET 探测
│
├── Phase 2: 检测已有部分下载
│ 检查 {filename}.part + {filename}.json(sidecar)
│
├── Phase 3: 续传一致性校验
│ 文件名 / ETag / LastModified 任一不一致 → 丢弃临时文件,从头下载
│ sidecar 为无效 JSON → 丢弃,从头下载
│
├── Phase 4: GET with Range
│ 从头下载:GET {url}
│ 续传: GET {url} + Range: bytes={existingLength}-
│ 服务端返回 200 OK(不支持 Range)→ 丢弃临时文件,重新下载
│ 流式写入 {filename}.part,实时回调进度
│
├── Phase 5: 原子重命名
│ 下载完成 → {filename}.part → 最终文件名(覆盖)
│ 删除 sidecar
│
└── Progress 报告
已下载字节数、总 大小、速度(bytes/s)、百分比、状态描述(Downloading/Resuming/Download completed)
6.2 Sidecar 元数据
DownloadResumeMetadata 的持久化字段(不再记录"已下载字节数",长度直接取自 .part 文件):
{
"downloadUrl": "https://cdn.example.com/app-v2.0.apk",
"expectedSha256": "a1b2c3d4...",
"expectedFileSize": 52428800,
"fileName": "app-v2.0.apk",
"etag": "\"abc123\"",
"lastModified": "2026-06-01T12:00:00Z"
}
6.3 速度计量
速度计量内联在下载器内部(滑动窗口,窗口长度由 AndroidUpdateOptions.SpeedSmoothingWindowSeconds 控制,默认 4 秒),并把结果放进 DownloadProgressInfo.DownloadSpeedBytesPerSecond。
6.4 重试策略
| 情况 | 是否重试 |
|---|---|
| HEAD / GET / 响应流的瞬态网络失败(超时、429、5xx、无状态码) | 重试并续传 |
| 元数据查询失败 | 不重试 |
| 永久 HTTP 错误(如 401) | 不重试 |
| 主动取消 | 不重试 |
MaxRetryAttempts 默认 3,表示"首次 1 次 + 重试 2 次";退避延迟为 min(30s, RetryBaseDelay * 2^attempt)。
7. 哈希校验与文件大小验证
7.1 双重验证
DownloadAndVerifyAsync 验证顺序:
1. FilePath 非空 → 否则 FileIoError
2. 文件大小验证(FileSize > 0 时)
→ 不匹配 → 删除文件,返回 FileIoError
3. SHA256 验证
→ 不匹配 → 删除文件,返回 HashMismatch
→ 主动取消 → 返回 Canceled(保留文件,便于续传)