跳到主要内容

GeneralUpdate.Avalonia.Android

命名空间: GeneralUpdate.Avalonia.Android | 主要入口: GeneralUpdateBootstrap.CreateDefault() | NuGet 包: GeneralUpdate.Avalonia.Android

1. 组件简介

1.1 组件概述

GeneralUpdate.Avalonia.Android 是面向 Avalonia 应用的 Android 平台自动更新组件,专为 Android APK 的更新场景设计。它提供版本对比、可断点续传的 APK 下载、SHA256 完整性校验、以及 Android Package Installer 自动启动的全流程编排能力。

Android 平台的更新与桌面应用不同:APK 安装必须通过 Android 系统安装器完成,下载过程可能被用户中断或切换网络,安装前需要检查 "安装未知应用" 权限。该组件封装了这些平台差异,让 Avalonia 开发者只需提供更新包信息即可完成更新。

核心能力:

能力说明
版本对比内置 SystemVersionComparer,通过 IVersionComparer 接口支持自定义版本策略
断点续传下载HttpResumableApkDownloader 支持 HTTP Range 请求头,中断后可续传
SHA256 完整性校验下载完成后自动计算文件 SHA256,与服务端声明的哈希值比对,防止文件损坏或篡改
APK 安装编排通过 AndroidApkInstaller 调用系统 Package Installer,自动处理 FileProvider URI
HTTP 传输配置HttpDownloadOptions 支持 SSL/TLS 证书策略、代理、超时、重试和多种认证方案
多协议认证支持 HMAC-SHA256、Bearer Token、API Key、HTTP Basic 四种认证方案,支持全局和单包粒度
事件通知版本发现、下载进度、完成、失败等事件通知
状态快照GetSnapshot() 随时获取当前更新状态、失败原因和消息
UI 线程调度IUpdateEventDispatcher 支持将事件调度到 UI 线程(如 Avalonia 的 SynchronizationContext
渐进式下载临时文件 .part 和 Sidecar .json 机制,记录已完成字节数,支持进程级断点续传

解决的业务痛点:

  • Avalonia 应用发布 Android 版本后,需要可靠的内置更新能力,但 Android APK 安装必须经由系统安装器
  • 下载大 APK 包时可能被用户中断、网络切换,需要断点续传能力降低重复下载流量
  • 需要对下载的 APK 进行完整性校验,防止传输损坏或被篡改
  • 需要灵活的认证方案对接不同服务端的鉴权要求
Android 更新 ≠ 桌面文件替换

Android 平台不能像桌面端那样直接替换文件。APK 必须通过 Android Package Installer 安装,系统会处理签名验证、权限授予和应用替换。本组件封装了这些平台差异,但 APK 签名、版本号递增等仍需要你在构建流水线中保证

业务使用场景:

  • Avalonia 跨平台应用的 Android APK 自动更新
  • 企业内部 Android 应用的静默或引导式更新
  • 需要通过 CDN / OSS 分发 APK 包的场景

1.2 环境与依赖

项目说明
NuGet 包GeneralUpdate.Avalonia.Android
目标框架net10.0-android(最低 Android API 26+)
依赖包Xamarin.AndroidX.Core(FileProvider 支持)
兼容性Android 8.0 (API 26) 及以上版本

2. 组件功能列表

功能名称功能描述类型是否必填备注限制
版本对比检查比对当前版本与目标版本,判断是否有更新可用基础必选ValidateAsync,支持自定义版本比较器
APK 断点续传下载从服务端下载 APK 包,支持 HTTP Range 断点续传基础必选DownloadAndVerifyAsync
SHA256 校验下载完成后自动计算 SHA256 与服务端哈希比对基础自动通过 IHashValidator 实现
APK 安装触发调用 Android Package Installer 安装 APK基础必选LaunchInstallerAsync
HTTP 传输配置SSL 证书校验、代理、超时、重试、认证拓展可选HttpDownloadOptions
多协议认证HMAC-SHA256 / Bearer / API Key / Basic拓展可选全局或单包粒度配置
进度通知下载进度(速度、字节数、百分比)基础可选AddListenerDownloadProgressChanged
完成通知版本发现、下载完成、安装完成事件基础可选AddListenerUpdateCompleted
失败通知失败原因、异常信息、失败包信息基础可选AddListenerUpdateFailed
验证通知发现可用更新时触发基础可选AddListenerValidate
状态快照随时获取当前更新状态拓展可选GetSnapshot()
UI 线程调度自定义事件调度策略拓展可选实现 IUpdateEventDispatcher
自定义下载器自定义下载实现拓展可选实现 IUpdateDownloader
自定义哈希验证自定义哈希算法拓展可选实现 IHashValidator
自定义版本比较自定义版本对比逻辑拓展可选实现 IVersionComparer
自定义 APK 安装器自定义安装实现拓展可选实现 IApkInstaller
自定义文件存储自定义文件读写实现拓展可选实现 IFileStorage
自定义 SSL 策略自定义 HTTPS 证书校验拓展可选实现 ISslValidationPolicy
自定义 HTTP 认证自定义 HTTP 请求认证拓展可选实现 IHttpAuthProvider

3. API 配置说明

3.1 配置字段(属性 Props)

AndroidUpdateOptions:

字段名数据类型默认值是否必填说明
DownloadDirectoryPathstring""(自动使用 CacheDir/updateTempPath/update可选下载文件存放目录,为空时自动选择
FileProviderAuthoritystring""Android FileProvider authority,需与 AndroidManifest 配置一致
TemporaryFileExtensionstring".part"可选临时下载文件扩展名
SidecarExtensionstring".json"可选断点续传元数据文件扩展名
DownloadBufferSizeint65536(64 KB)可选下载缓冲区大小(字节)
SpeedSmoothingWindowSecondsint4可选下载速度平滑窗口(秒)

HttpDownloadOptions:

字段名数据类型默认值是否必填说明
SslValidationPolicyISslValidationPolicy?null可选自定义 SSL 证书验证策略;null 使用系统默认
RequestTimeoutTimeSpan30s可选单次 HTTP 请求超时
DownloadTimeoutTimeSpan10min可选整体下载操作超时
ProxyIWebProxy?null可选HTTP 代理
UseProxyboolfalse可选是否启用配置的代理
MaxRetryAttemptsint3可选最大重试次数(3 表示初始 1 次 + 2 次重试)
RetryBaseDelayTimeSpan1s可选指数退避基本延迟:baseDelay * 2^attempt
AuthProviderIHttpAuthProvider?null可选全局 HTTP 认证提供器,单包认证优先级更高

UpdatePackageInfo:

字段名数据类型默认值是否必填说明
Versionstring目标版本号
DownloadUrlstringAPK 下载地址
Sha256stringAPK 的 SHA256 哈希值,用于完整性校验
FileSizelong0可选APK 文件大小(字节)
FileNamestring?null可选下载后的文件名
IsForcedboolfalse可选是否强制更新
VersionNamestring?null可选版本展示名称
Descriptionstring?null可选版本描述
PublishTimeDateTimeOffset?null可选发布时间
AuthSchemeAuthScheme?null可选单包认证方案,优先于全局配置
AuthTokenstring?null可选Bearer 或 API Key 认证令牌
AuthSecretKeystring?null可选HMAC-SHA256 签名密钥
BasicUsernamestring?null可选HTTP Basic 用户名
BasicPasswordstring?null可选HTTP Basic 密码

UpdateState 枚举:

枚举值数值说明
None0初始状态
Checking1正在检查更新
UpdateAvailable2发现可用更新
Downloading3正在下载
Verifying4正在校验
ReadyToInstall5准备安装
Installing6正在安装
Completed7更新完成
Failed8更新失败
Canceled9已取消

AuthScheme 枚举:

枚举值说明
HmacHMAC-SHA256 签名认证
BearerBearer Token 认证
ApiKeyAPI Key 认证
BasicHTTP Basic 认证

3.2 实例方法

GeneralUpdateBootstrap(静态工厂):

方法名入参明细使用场景注意事项
CreateDefault(AndroidUpdateOptions, ...)options — 更新选项;可选参数:contextProvider, activityProvider, httpClient, versionComparer, eventDispatcher, logger, httpOptions创建默认的 Android 更新引导实例所有可选参数为 null 时使用内置默认实现

IAndroidBootstrap:

方法名入参明细使用场景注意事项
ValidateAsync(UpdatePackageInfo, string, CancellationToken)packageInfo — 更新包信息;currentVersion — 当前版本;ct — 取消令牌检查是否有可用的更新返回 UpdateCheckResult,包含 UpdateFoundTargetVersion
DownloadAndVerifyAsync(UpdatePackageInfo, CancellationToken)packageInfo — 更新包信息;ct — 取消令牌下载 APK 并校验 SHA256返回 UpdateOperationResult,成功时 FilePath 为 APK 路径
LaunchInstallerAsync(UpdatePackageInfo, string, CancellationToken)packageInfo — 更新包信息;apkFilePath — APK 文件路径;ct — 取消令牌启动 Android Package Installer需要 FileProvider authority 配置正确
GetSnapshot()获取当前更新状态快照返回 UpdateStateSnapshot(State, FailureReason, Message)
Dispose()释放下载器等资源

3.3 回调事件

事件名称回调参数触发时机使用说明
AddListenerValidateValidateEventArgsPackageInfo, CurrentVersion版本对比完成后,发现可用更新时触发可用于 UI 展示新版本信息
AddListenerDownloadProgressChangedDownloadProgressChangedEventArgsProgressPercentage, DownloadSpeedBytesPerSecond, DownloadedBytes, RemainingBytes, TotalBytes, PackageInfo, StatusDescription下载进度更新时可用于进度条展示
AddListenerUpdateCompletedUpdateCompletedEventArgsResultUpdateOperationResult下载完成或安装完成时可用于 UI 切换
AddListenerUpdateFailedUpdateFailedEventArgsResultUpdateOperationResult任何阶段失败时包含失败原因和异常信息

4. 扩展示例(高阶用法)

4.1 组件可扩展能力总览

所有服务均可通过 CreateDefault 的可选参数替换:

接口默认实现说明
IVersionComparerSystemVersionComparer版本对比策略
IUpdateDownloaderHttpResumableApkDownloaderAPK 下载实现
IHashValidatorSha256HashValidatorSHA256 哈希校验
IApkInstallerAndroidApkInstallerAPK 安装触发
IFileStoragePhysicalFileStorage文件存储操作
IUpdateEventDispatcherImmediateEventDispatcher事件调度器(直接调用)
IUpdateLoggerNoOpUpdateLogger日志记录器
IAndroidContextProviderDefaultAndroidContextProviderAndroid Context 提供器
IAndroidActivityProviderNullAndroidActivityProviderAndroid Activity 提供器
ISslValidationPolicy通过 HttpDownloadOptions 配置SSL 证书验证策略
IHttpAuthProvider通过 HttpDownloadOptions 或单包配置HTTP 认证提供器

4.2 分场景示例

场景 1:自定义版本比较策略

【场景说明】应用使用 year.month.day.build 格式版本号,默认的 System.Version 解析会失败,需要自定义版本比较器。

【示例代码】

using GeneralUpdate.Avalonia.Android.Abstractions;

public sealed class CustomDateVersionComparer : IVersionComparer
{
public bool TryCompare(string currentVersion, string targetVersion,
out int compareResult, out string? errorMessage)
{
try
{
var current = ParseVersion(currentVersion);
var target = ParseVersion(targetVersion);
compareResult = target.CompareTo(current);
errorMessage = null;
return true;
}
catch (Exception ex)
{
compareResult = 0;
errorMessage = ex.Message;
return false;
}
}

private static DateTime ParseVersion(string version) =>
DateTime.ParseExact(version, "yyyy.MM.dd.fff", null);
}

// 使用
var bootstrap = GeneralUpdateBootstrap.CreateDefault(
options,
versionComparer: new CustomDateVersionComparer());

【效果&注意事项】

  • 返回的 compareResult 遵循 target.CompareTo(current) 语义:正数表示有新版本
  • 实现失败时返回 false 和错误信息,引导跳过更新检查

场景 2:自定义事件调度器(Avalonia UI 线程调度)

【场景说明】在 Avalonia 应用中,事件回调默认在后台线程触发,更新 UI 需要调度到 UI 线程。

using GeneralUpdate.Avalonia.Android.Abstractions;

public sealed class AvaloniaEventDispatcher : IUpdateEventDispatcher
{
public void Dispatch(Action callback)
{
if (Avalonia.Threading.Dispatcher.UIThread.CheckAccess())
{
callback();
}
else
{
Avalonia.Threading.Dispatcher.UIThread.Post(callback);
}
}
}

// 使用
var bootstrap = GeneralUpdateBootstrap.CreateDefault(
options,
eventDispatcher: new AvaloniaEventDispatcher());

场景 3:自定义下载器(私有文件服务器)

【场景说明】企业内部使用私有文件服务器,需要在下载时注入自定义认证头。

using GeneralUpdate.Avalonia.Android.Abstractions;
using GeneralUpdate.Avalonia.Android.Models;

public sealed class EnterpriseDownloader : IUpdateDownloader
{
private readonly HttpClient _httpClient;
private readonly string _enterpriseToken;

public EnterpriseDownloader(HttpClient httpClient, string enterpriseToken)
{
_httpClient = httpClient;
_enterpriseToken = enterpriseToken;
}

public async Task<DownloadResult> DownloadAsync(
UpdatePackageInfo packageInfo,
Action<DownloadProgressInfo>? progressCallback,
CancellationToken cancellationToken = default)
{
var request = new HttpRequestMessage(HttpMethod.Get, packageInfo.DownloadUrl);
request.Headers.Add("X-Enterprise-Token", _enterpriseToken);

// 自行实现下载和进度报告...
// 返回 DownloadResult
}
}

5. 常规使用示例

5.1 快速入门示例(最简 demo)

using GeneralUpdate.Avalonia.Android;
using GeneralUpdate.Avalonia.Android.Models;

var options = new AndroidUpdateOptions
{
FileProviderAuthority = "com.myapp.fileprovider"
};

var bootstrap = GeneralUpdateBootstrap.CreateDefault(options);

// 1. 检查更新
var updatePackage = new UpdatePackageInfo
{
Version = "2.0.0",
DownloadUrl = "https://update.example.com/app-v2.0.0.apk",
Sha256 = "expected-sha256-hash",
FileSize = 50_000_000,
FileName = "app_update_2.0.0.apk"
};

var checkResult = await bootstrap.ValidateAsync(
updatePackage, currentVersion: "1.0.0");

if (!checkResult.UpdateFound)
{
Console.WriteLine("Already up to date.");
return;
}

// 2. 下载并校验
var downloadResult = await bootstrap.DownloadAndVerifyAsync(updatePackage);
if (!downloadResult.Success)
{
Console.WriteLine($"Download failed: {downloadResult.Message}");
return;
}

// 3. 安装
var installResult = await bootstrap.LaunchInstallerAsync(
updatePackage, downloadResult.FilePath!);

if (installResult.Success)
Console.WriteLine("Installer launched.");

5.2 基础参数组合示例

using GeneralUpdate.Avalonia.Android;
using GeneralUpdate.Avalonia.Android.Abstractions;
using GeneralUpdate.Avalonia.Android.Models;
using GeneralUpdate.Avalonia.Android.Services;

var options = new AndroidUpdateOptions
{
DownloadDirectoryPath = Path.Combine(
Android.App.Application.Context.CacheDir!.AbsolutePath, "myapp-updates"),
FileProviderAuthority = "com.myapp.fileprovider",
TemporaryFileExtension = ".downloading",
DownloadBufferSize = 128 * 1024 // 128 KB buffer
};

// 带 HTTP 配置的启动
var httpOptions = new HttpDownloadOptions
{
RequestTimeout = TimeSpan.FromSeconds(60),
DownloadTimeout = TimeSpan.FromMinutes(30),
MaxRetryAttempts = 5,
RetryBaseDelay = TimeSpan.FromSeconds(2),
// 开发环境使用自签名证书
SslValidationPolicy = new AllowAllSslValidationPolicy()
};

var bootstrap = GeneralUpdateBootstrap.CreateDefault(
options,
httpOptions: httpOptions);

// 事件监听
bootstrap.AddListenerDownloadProgressChanged += (_, e) =>
{
Console.WriteLine(
$"Download: {e.ProgressPercentage:F1}% " +
$"({FormatSize(e.DownloadedBytes)}/{FormatSize(e.TotalBytes)}) " +
$"{FormatSpeed(e.DownloadSpeedBytesPerSecond)}");
};

bootstrap.AddListenerUpdateFailed += (_, e) =>
{
Console.WriteLine($"Failed: {e.Result.Message} (reason: {e.Result.FailureReason})");
};

bootstrap.AddListenerUpdateCompleted += (_, e) =>
{
Console.WriteLine($"Completed: {e.Result.State}");
};

// 执行更新
var package = new UpdatePackageInfo
{
Version = "2.0.0",
DownloadUrl = "https://update.example.com/app-v2.0.0.apk",
Sha256 = "expected-sha256-hash",
FileSize = 50_000_000,
FileName = "app-v2.0.0.apk"
};

var checkResult = await bootstrap.ValidateAsync(package, "1.0.0");
if (!checkResult.UpdateFound) return;

var downloadResult = await bootstrap.DownloadAndVerifyAsync(package);
if (!downloadResult.Success) return;

await bootstrap.LaunchInstallerAsync(package, downloadResult.FilePath!);

static string FormatSize(long bytes) => bytes switch
{
< 1024 => $"{bytes} B",
< 1048576 => $"{bytes / 1024.0:F1} KB",
_ => $"{bytes / 1048576.0:F1} MB"
};

static string FormatSpeed(double bytesPerSecond) =>
$"{bytesPerSecond / 1048576.0:F1} MB/s";

5.3 真实业务落地示例

完整更新工作流,包含 HTTP 认证、断点续传、权限检查和进度 UI:

using GeneralUpdate.Avalonia.Android;
using GeneralUpdate.Avalonia.Android.Enums;
using GeneralUpdate.Avalonia.Android.Models;

public sealed class AppUpdateService
{
private IAndroidBootstrap? _bootstrap;
private bool _disposed;

public event EventHandler<double>? ProgressChanged;
public event EventHandler<string>? StatusChanged;

public void Initialize(string fileProviderAuthority, string serverToken)
{
var httpOptions = new HttpDownloadOptions
{
MaxRetryAttempts = 3,
RetryBaseDelay = TimeSpan.FromSeconds(1),
AuthProvider = new Services.BearerTokenAuthProvider(serverToken)
};

_bootstrap = GeneralUpdateBootstrap.CreateDefault(
new AndroidUpdateOptions
{
FileProviderAuthority = fileProviderAuthority
},
httpOptions: httpOptions);

WireEvents();
}

public async Task<bool> UpdateAsync(
UpdatePackageInfo package, string currentVersion,
CancellationToken ct = default)
{
if (_bootstrap == null)
throw new InvalidOperationException("Not initialized.");

// Phase 1: 版本检查
StatusChanged?.Invoke(this, "Checking for updates...");
var checkResult = await _bootstrap.ValidateAsync(package, currentVersion, ct);
if (!checkResult.UpdateFound)
{
StatusChanged?.Invoke(this, "Already up to date.");
return true;
}

StatusChanged?.Invoke(this, $"Update v{package.Version} found.");

// Phase 2: 下载并校验
StatusChanged?.Invoke(this, "Downloading...");
var downloadResult = await _bootstrap.DownloadAndVerifyAsync(package, ct);
if (!downloadResult.Success)
{
StatusChanged?.Invoke(this,
$"Download failed: {downloadResult.Message}");
return false;
}

StatusChanged?.Invoke(this, "Download complete, verifying...");

// Phase 3: 检查安装权限 (Android 8+)
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
var ctx = Android.App.Application.Context;
var pm = ctx.PackageManager;
if (pm != null && !pm.CanRequestPackageInstalls())
{
StatusChanged?.Invoke(this,
"Please allow install from unknown apps.");
var intent = new Android.Content.Intent(
Android.Provider.Settings.ActionManageUnknownAppSources)
.SetData(Android.Net.Uri.Parse("package:" + ctx.PackageName));
intent.AddFlags(Android.Content.ActivityFlags.NewTask);
ctx.StartActivity(intent);
return false;
}
}

// Phase 4: 启动安装
StatusChanged?.Invoke(this, "Installing...");
var installResult = await _bootstrap.LaunchInstallerAsync(
package, downloadResult.FilePath!, ct);

if (installResult.Success)
{
StatusChanged?.Invoke(this, "Installer launched.");
return true;
}

StatusChanged?.Invoke(this,
$"Install failed: {installResult.Message}");
return false;
}

private void WireEvents()
{
if (_bootstrap == null) return;

_bootstrap.AddListenerDownloadProgressChanged += (_, e) =>
{
ProgressChanged?.Invoke(this, e.ProgressPercentage);
};

_bootstrap.AddListenerUpdateFailed += (_, e) =>
{
StatusChanged?.Invoke(this,
$"Failed: {e.Result.Message} (reason: {e.Result.FailureReason})");
};
}

public void Dispose()
{
if (_disposed) return;
_bootstrap?.Dispose();
_disposed = true;
}
}

6. 全局配置

认证方案

Avalonia 组件支持四种认证方案,可在 UpdatePackageInfo 单包粒度配置:

认证方案AuthScheme所需字段
HMAC-SHA256HmacAuthSecretKey
Bearer TokenBearerAuthToken
API KeyApiKeyAuthToken
HTTP BasicBasicBasicUsername + BasicPassword

也可通过 HttpDownloadOptions.AuthProvider 设置全局认证提供器。单包配置优先于全局配置。

SSL 策略

策略类名使用场景
系统默认(推荐)null生产环境
允许所有(仅开发)AllowAllSslValidationPolicy自签名证书开发环境
自定义实现 ISslValidationPolicy私有 CA 或证书固定

断点续传机制

下载器使用临时文件(.part)和元数据文件(.json)实现断点续传:

CacheDir/update/
├── app-v2.0.0.apk.part # 部分下载的临时文件
├── app-v2.0.0.apk.json # 续传元数据(URL、ETag、预期大小、已下载字节数)
└── app-v2.0.0.apk # 下载完成后的最终文件
  • 中断后重新下载同一 URL 时,自动读取 .json 元数据判断服务端是否支持 Range
  • 支持 Range 时从已下载位置继续(Range: bytes={existingLength}-
  • 不支持 Range 时重新下载
  • 下载完成后 .part 重命名为最终文件名,删除 .json 元数据

AndroidManifest 配置

使用 FileProvider 分享 APK 文件给 Package Installer,需要在 AndroidManifest.xml 中配置:

<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>

file_paths.xml(在 Resources/xml/ 目录下):

<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="cache"
path="update/" />
</paths>

平台差异

项目说明
最低 API26(Android 8.0)
安装权限Android 8.0+ 需要 CanRequestPackageInstalls() 检查
FileProvider必须通过 FileProvider 向系统安装器传递 APK 文件 URI

相关资源