From 82e7d2c7c39884890c7d1cad220fda2923b11e78 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Thu, 4 Jun 2026 10:54:52 +0800 Subject: [PATCH] docs: remove Android auto-update page and refresh Core component docs - Remove Android auto-update page from docs, en i18n, and zh-Hans i18n - Remove Android auto-update from sidebar navigation - Update GeneralUpdate.Core.md to reflect latest source code (v10.5.0-beta.2): - Add multi-target framework info (netstandard2.0; net8.0; net10.0) - Add AOT/Trim compatibility and JSON source generator details - Document AuthScheme enum (Hmac/Bearer/ApiKey/Basic) and new config fields - Add cross-platform strategy scenario (Windows/Linux/Mac) - Add new feature entries (multi-auth, file tree diff, Environments IPC) Co-Authored-By: Claude Opus 4.8 --- website/docs/doc/Android auto-update.md | 423 ------------------ website/docs/doc/GeneralUpdate.Core.md | 142 +++++- .../current/doc/Android auto-update.md | 423 ------------------ .../current/doc/Android auto-update.md | 423 ------------------ website/sidebars.js | 1 - 5 files changed, 134 insertions(+), 1278 deletions(-) delete mode 100644 website/docs/doc/Android auto-update.md delete mode 100644 website/i18n/en/docusaurus-plugin-content-docs/current/doc/Android auto-update.md delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/Android auto-update.md diff --git a/website/docs/doc/Android auto-update.md b/website/docs/doc/Android auto-update.md deleted file mode 100644 index 5b8509b..0000000 --- a/website/docs/doc/Android auto-update.md +++ /dev/null @@ -1,423 +0,0 @@ ---- -sidebar_position: 13 -title: Android 自动更新 ---- - -# Android 自动更新 - -## 组件概览 - -GeneralUpdate 为 .NET Android 应用提供了两套独立的自动更新组件: - -| 组件 | NuGet 包 | 适用框架 | 仓库 | -|------|----------|----------|------| -| **GeneralUpdate.Maui.Android** | `GeneralUpdate.Maui.Android` | .NET MAUI(Android) | [GitHub](https://github.com/GeneralLibrary/GeneralUpdate.Maui) | -| **GeneralUpdate.Avalonia.Android** | `GeneralUpdate.Avalonia.Android` | Avalonia 12+(Android) | [GitHub](https://github.com/GeneralLibrary/GeneralUpdate.Avalonia) | - -两个组件都是**无 UI** 的 Android 自动更新核心库,专注于可复用的更新编排。它们不提供任何界面,由调用方自行实现更新提示 UI。 - -### 共同特性 - -- **版本校验**:对比当前版本与远程版本,判断是否需要更新 -- **断点续传下载**:基于 HTTP Range 的 APK 断点续传下载 -- **SHA256 完整性校验**:自动验证下载文件的哈希值 -- **系统安装器触发**:通过 `Intent` + `FileProvider` 调起 Android 系统安装器 -- **事件通知**:下载进度、完成、失败等生命周期事件 - -### 更新流程(端到端) - -```text -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ 1. 版本验证 │ -> │ 2. 断点下载 │ -> │ 3. 哈希校验 │ -> │ 4. 触发安装 │ -│ ValidateAsync │ │ Download │ │ SHA256 Check │ │ Installer │ -└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ -``` - ---- - -## GeneralUpdate.Maui.Android - -### 安装 - -目标框架:`net10.0; net10.0-android` - -```bash -dotnet add package GeneralUpdate.Maui.Android -``` - -### 依赖注入(DI) - -```csharp -using GeneralUpdate.Maui.Android.Services; -using Microsoft.Extensions.DependencyInjection; - -var services = new ServiceCollection(); -services.AddGeneralUpdateMauiAndroid(); - -using var provider = services.BuildServiceProvider(); -var bootstrap = provider.GetRequiredService(); -``` - -也支持直接创建(无需 DI 容器): - -```csharp -var bootstrap = GeneralUpdateBootstrap.CreateDefault(); -``` - -### 快速开始 - -```csharp -using GeneralUpdate.Maui.Android.Models; -using GeneralUpdate.Maui.Android.Services; - -var bootstrap = GeneralUpdateBootstrap.CreateDefault(); - -// ── 事件监听 ────────────────────────────────── -bootstrap.AddListenerValidate += (_, e) => -{ - Console.WriteLine($"发现新版本: {e.PackageInfo.Version}"); -}; - -bootstrap.AddListenerDownloadProgressChanged += (_, e) => -{ - var s = e.Statistics; - Console.WriteLine( - $"{s.ProgressPercentage:F2}% | {s.DownloadedBytes}/{s.TotalBytes} | " + - $"剩余: {s.RemainingBytes} | 速度: {s.BytesPerSecond:F0} B/s"); -}; - -bootstrap.AddListenerUpdateCompleted += (_, e) => -{ - Console.WriteLine($"阶段={e.Stage}, 文件={e.PackagePath}"); -}; - -bootstrap.AddListenerUpdateFailed += (_, e) => -{ - Console.WriteLine($"失败: {e.Reason}, {e.Message}"); -}; - -// ── 构造更新包信息 ──────────────────────────── -var package = new UpdatePackageInfo -{ - Version = "2.0.0", - VersionName = "2.0", - ReleaseNotes = "性能和稳定性改进", - DownloadUrl = "https://example.com/app-release.apk", - Sha256 = "3A0D2F...F9C2", - PackageSize = 52_428_800 -}; - -// ── 执行更新 ────────────────────────────────── -var options = new UpdateOptions -{ - CurrentVersion = "1.5.0", - InstallOptions = new AndroidInstallOptions - { - FileProviderAuthority = $"{AppInfo.PackageName}.fileprovider" - } -}; - -var check = await bootstrap.ValidateAsync(package, options, CancellationToken.None); -if (check.IsUpdateAvailable) -{ - var result = await bootstrap.ExecuteUpdateAsync(package, options, CancellationToken.None); - Console.WriteLine(result.IsSuccess ? "更新流程已完成。" : $"更新失败: {result.Message}"); -} -``` - -### 核心 API - -#### IAndroidBootstrap - -| 方法 | 说明 | -|------|------| -| `ValidateAsync(package, options, ct)` | 校验远程版本是否高于当前版本 | -| `ExecuteUpdateAsync(package, options, ct)` | 执行完整更新流程(下载 + 校验 + 安装) | - -#### 事件 - -| 事件 | 触发时机 | -|------|----------| -| `AddListenerValidate` | 检测到更高版本时触发 | -| `AddListenerDownloadProgressChanged` | 下载进度更新(速度、已下载字节、剩余字节、百分比) | -| `AddListenerUpdateCompleted` | 工作流里程碑:`DownloadCompleted`、`VerificationCompleted`、`InstallationTriggered` | -| `AddListenerUpdateFailed` | 更新失败,附带 `UpdateFailureReason` 和错误消息 | - -#### UpdateOptions - -| 属性 | 类型 | 说明 | -|------|------|------| -| `CurrentVersion` | `string` | 当前应用版本号 | -| `DownloadDirectory` | `string?` | 下载目录(可选,默认使用应用缓存目录) | -| `TemporaryFileExtension` | `string` | 下载中临时文件扩展名,默认 `.downloading` | -| `DeleteCorruptedPackageOnFailure` | `bool` | 失败时是否删除损坏的包,默认 `true` | -| `ProgressReportInterval` | `TimeSpan` | 进度报告间隔,默认 500ms | -| `InstallOptions` | `AndroidInstallOptions` | Android 安装选项 | - -#### UpdatePackageInfo - -| 属性 | 类型 | 说明 | -|------|------|------| -| `Version` | `string` | 远程版本号(必填) | -| `VersionName` | `string?` | 版本名称(展示用) | -| `ReleaseNotes` | `string?` | 更新日志 | -| `DownloadUrl` | `string` | APK 下载地址(必填) | -| `Sha256` | `string` | APK 文件 SHA256 哈希值(必填) | -| `PackageSize` | `long?` | 包大小(字节),用于进度估算 | -| `ForceUpdate` | `bool` | 是否强制更新 | - ---- - -## GeneralUpdate.Avalonia.Android - -### 安装 - -目标框架:`net8.0-android`(兼容 `net9.0-android`+) - -```bash -dotnet add package GeneralUpdate.Avalonia.Android -``` - -### Avalonia UI 线程调度 - -Avalonia 需要在 UI 线程上更新界面。实现 `IUpdateEventDispatcher` 将回调调度到 Avalonia UI 线程: - -```csharp -using GeneralUpdate.Avalonia.Android.Abstractions; - -public sealed class AvaloniaUiDispatcher : IUpdateEventDispatcher -{ - public void Dispatch(Action callback) - { - Avalonia.Threading.Dispatcher.UIThread.Post(callback); - } -} -``` - -### 快速开始 - -```csharp -using GeneralUpdate.Avalonia.Android; -using GeneralUpdate.Avalonia.Android.Abstractions; -using GeneralUpdate.Avalonia.Android.Models; - -// ── 配置选项 ────────────────────────────────── -var options = new AndroidUpdateOptions -{ - DownloadDirectoryPath = Path.Combine( - Android.App.Application.Context.CacheDir!.AbsolutePath!, "update"), - FileProviderAuthority = "com.example.app.generalupdate.fileprovider" -}; - -var bootstrap = GeneralUpdateBootstrap.CreateDefault( - options, - eventDispatcher: new AvaloniaUiDispatcher()); - -// ── 事件监听 ────────────────────────────────── -bootstrap.AddListenerValidate += (_, e) => -{ - Console.WriteLine($"发现新版本: {e.PackageInfo.Version} → 当前: {e.CurrentVersion}"); -}; - -bootstrap.AddListenerDownloadProgressChanged += (_, e) => -{ - var d = e.Download; - Console.WriteLine($"{d.ProgressPercentage:F1}% | {d.Downloaded}/{d.Total} | 速度: {d.Speed:F0} B/s"); -}; - -bootstrap.AddListenerUpdateCompleted += (_, e) => -{ - Console.WriteLine($"完成: {e.Result.Stage}, 文件: {e.Result.FilePath}"); -}; - -bootstrap.AddListenerUpdateFailed += (_, e) => -{ - Console.WriteLine($"失败: {e.Result.Reason}, {e.Result.Message}"); -}; - -// ── 构造更新包信息 ──────────────────────────── -var packageInfo = new UpdatePackageInfo -{ - Version = "2.3.0", - VersionName = "2.3", - Description = "新增暗色模式支持,修复若干问题", - DownloadUrl = "https://example.com/app-release.apk", - Sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - FileSize = 52_428_800, - FileName = "app-release.apk", - IsForced = false -}; - -// ── 三阶段更新流程 ──────────────────────────── -var check = await bootstrap.ValidateAsync(packageInfo, "2.2.1", CancellationToken.None); -if (check.UpdateFound) -{ - var prepared = await bootstrap.DownloadAndVerifyAsync(packageInfo, CancellationToken.None); - if (prepared.Success && prepared.FilePath is not null) - { - await bootstrap.LaunchInstallerAsync(packageInfo, prepared.FilePath, CancellationToken.None); - } -} -``` - -### 核心 API - -#### IAndroidBootstrap - -Avalonia 采用**三阶段 API**,将下载验证与安装触发分离,给予调用方更多控制: - -| 方法 | 说明 | -|------|------| -| `ValidateAsync(packageInfo, currentVersion, ct)` | 版本校验,返回 `UpdateCheckResult` | -| `DownloadAndVerifyAsync(packageInfo, ct)` | 下载 APK 并校验 SHA256,返回 `UpdateOperationResult` | -| `LaunchInstallerAsync(packageInfo, apkFilePath, ct)` | 调起 Android 系统安装器 | -| `GetSnapshot()` | 获取当前更新状态快照 | - -#### 事件 - -| 事件 | 参数 | 说明 | -|------|------|------| -| `AddListenerValidate` | `ValidateEventArgs` | 版本验证完成 | -| `AddListenerDownloadProgressChanged` | `DownloadProgressChangedEventArgs` | 下载进度(速度、已下载、剩余、百分比、状态消息) | -| `AddListenerUpdateCompleted` | `UpdateCompletedEventArgs` | 各阶段完成通知 | -| `AddListenerUpdateFailed` | `UpdateFailedEventArgs` | 更新失败详情 | - -#### AndroidUpdateOptions - -| 属性 | 类型 | 默认值 | 说明 | -|------|------|--------|------| -| `DownloadDirectoryPath` | `string` | 空(使用应用缓存) | APK 下载目录 | -| `TemporaryFileExtension` | `string` | `.part` | 下载临时文件扩展名 | -| `SidecarExtension` | `string` | `.json` | 断点续传 sidecar 元数据扩展名 | -| `FileProviderAuthority` | `string` | 空(必填) | Android `FileProvider` authority | -| `DownloadBufferSize` | `int` | `64 * 1024` | 下载缓冲区大小 | -| `SpeedSmoothingWindowSeconds` | `int` | `4` | 速度平滑窗口(秒) | - -#### UpdatePackageInfo - -| 属性 | 类型 | 说明 | -|------|------|------| -| `Version` | `string` | 远程版本号(必填) | -| `VersionName` | `string?` | 版本名称 | -| `Description` | `string?` | 更新描述 | -| `DownloadUrl` | `string` | APK 下载地址(必填) | -| `FileSize` | `long` | 文件大小(字节) | -| `Sha256` | `string` | APK SHA256 哈希(必填) | -| `FileName` | `string?` | 下载文件名 | -| `IsForced` | `bool` | 是否强制更新 | -| `PublishTime` | `DateTimeOffset?` | 发布时间 | - ---- - -## Android Project 配置 - -两个组件都需要在 Android 项目中配置 `FileProvider`: - -### AndroidManifest.xml - -```xml - - - - - -``` - -> 提示:如果应用已有 `FileProvider`,可复用现有的 authority。 - -### Resources/xml/generalupdate_file_paths.xml - -```xml - - - - - -``` - ---- - -## Maui vs Avalonia 对比 - -| 维度 | GeneralUpdate.Maui.Android | GeneralUpdate.Avalonia.Android | -|------|---------------------------|-------------------------------| -| 目标框架 | `net10.0-android` | `net8.0-android`(+ 更高版本) | -| DI 支持 | ✅ 内置 `AddGeneralUpdateMauiAndroid()` | 手动创建 | -| API 风格 | 两阶段:`ValidateAsync` + `ExecuteUpdateAsync`(合并下载+校验+安装) | 三阶段:`ValidateAsync` + `DownloadAndVerifyAsync` + `LaunchInstallerAsync` | -| UI 线程调度 | 通过 .NET MAUI 自动处理 | 需自行实现 `IUpdateEventDispatcher` | -| 断点续传元数据 | 内置 | Sidecar JSON 文件(`.json` 扩展名) | -| 速度平滑 | 默认 500ms 报告间隔 | 可配置平滑窗口(秒) | -| 状态快照 | — | `GetSnapshot()` 返回 `UpdateStateSnapshot` | -| 版本比较 | 内置 | 可替换 `IVersionComparer`(默认 `SystemVersionComparer`) | - -### 选型建议 - -- **.NET MAUI 项目**:选择 `GeneralUpdate.Maui.Android`,享受 DI 集成和更简洁的两阶段 API -- **Avalonia 项目**:选择 `GeneralUpdate.Avalonia.Android`,具有更细粒度的三阶段控制和可替换策略 -- **需要最大灵活性的场景**:Avalonia 的分离式 API 和可替换组件提供更多定制空间 - ---- - -## 服务端要求 - -两个组件都是**纯客户端库**,对服务端无强制要求。你需要提供一个可下载 APK 的 HTTP(S) 端点和一个返回以下信息的版本检查接口(自行实现): - -```json -{ - "version": "2.0.0", - "versionName": "2.0", - "releaseNotes": "性能和稳定性改进", - "downloadUrl": "https://cdn.example.com/app-release.apk", - "sha256": "3a0d2f...f9c2", - "packageSize": 52428800, - "forceUpdate": false -} -``` - -> 建议:可将版本信息托管在 OSS 静态文件服务器上,与 GeneralUpdate.Core 的 OSS 模式共用 `versions.json`。 - ---- - -## 常见问题 - -### 下载中断后能否恢复? - -可以。两个组件都支持 HTTP Range 断点续传。Maui 使用单纯的 Range 请求恢复;Avalonia 额外使用 sidecar JSON 文件记录断点元数据。 - -### SHA256 校验失败怎么办? - -组件会自动删除损坏的 APK 文件。Maui 由 `DeleteCorruptedPackageOnFailure` 控制(默认 `true`);Avalonia 始终会丢弃校验失败的文件。校验失败后会抛出 `UpdateFailed` 事件。 - -### 如何实现强制更新? - -在 `UpdatePackageInfo` 中设置 `ForceUpdate = true`(Maui)或 `IsForced = true`(Avalonia)。你的应用自行决定在收到强制更新标记后是否阻止用户继续使用旧版本。 - -### FileProvider authority 怎么填? - -格式为 `{应用包名}.{任意后缀}`,例如 `com.example.app.generalupdate.fileprovider`。`AndroidManifest.xml` 中的 `authorities` 必须与代码中的 `FileProviderAuthority` 一致。 - -### 需要哪些 Android 权限? - -```xml - - - -``` - -> Android 8.0+ 需要 `REQUEST_INSTALL_PACKAGES` 才能安装来自未知来源的 APK。 - ---- - -## 相关资源 - -- [GeneralUpdate.Maui 仓库](https://github.com/GeneralLibrary/GeneralUpdate.Maui) -- [GeneralUpdate.Avalonia 仓库](https://github.com/GeneralLibrary/GeneralUpdate.Avalonia) -- [GeneralUpdate.Core 组件文档](./GeneralUpdate.Core.md) -- [GeneralUpdate.Core 组件文档](./GeneralUpdate.Core.md) -- [入门实战手册](../quickstart/Beginner cookbook.md) diff --git a/website/docs/doc/GeneralUpdate.Core.md b/website/docs/doc/GeneralUpdate.Core.md index 0554ed6..bf08797 100644 --- a/website/docs/doc/GeneralUpdate.Core.md +++ b/website/docs/doc/GeneralUpdate.Core.md @@ -44,9 +44,9 @@ sidebar_position: 5 | 项目 | 说明 | | --- | --- | | **版本** | `10.5.0-beta.2` | -| **目标框架** | `netstandard2.0`(兼容 .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+) | -| **依赖包** | `GeneralUpdate.Differential`(差分算法)、`System.Text.Json`、`Microsoft.Extensions.Logging.Abstractions` | -| **兼容性** | Windows(主支持)/ Linux / macOS;支持 x86 / x64 / ARM64 | +| **目标框架** | `netstandard2.0`; `net8.0`; `net10.0`(兼容 .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+;`net8.0`+ 支持 AOT/Trim) | +| **依赖包** | `GeneralUpdate.Differential`(差分算法)、`System.Text.Json`、`Microsoft.AspNetCore.SignalR.Client` | +| **兼容性** | Windows / Linux / macOS;支持 x86 / x64 / ARM64 | --- @@ -76,6 +76,10 @@ sidebar_position: 5 | SignalR 实时推送 | 服务端主动推送版本更新通知,客户端订阅接收,支持点对点和广播推送 | 拓展 | 可选 | `UpgradeHubService`,命名空间 `GeneralUpdate.Core.Hubs` | | 推送重连机制 | 断线自动重连(随机退避策略),连接生命周期管理 | 拓展 | 可选 | `RandomRetryPolicy` | | 推送事件订阅 | 接收消息、在线状态、重连通知、关闭通知四种事件 | 拓展 | 可选 | 通过 `AddListener*` 方法注册 | +| 多协议认证 | 支持 HMAC-SHA256、Bearer Token、API Key、HTTP Basic 四种认证方案 | 基础 | 可选 | 通过 `AuthScheme` 枚举或 `HttpAuth()` 自定义 | +| 跨平台策略 | 内置 Windows / Linux / macOS 三种 OS 级更新策略,自动选择 | 基础 | 自动 | 根据运行时平台自动选择对应策略 | +| 文件树比对 | 新旧版本目录结构级差异对比,生成增量文件清单 | 基础 | 可选 | `FileTree` / `FileTreeDiffer` / `FileTreeComparer` | +| AOT/Trim 兼容 | `net8.0`+ 目标框架支持 AOT 发布和裁剪,含源生成 JSON 序列化上下文 | 基础 | 可选 | `JsonContext` 命名空间下 9 个序列化上下文 | --- @@ -99,8 +103,11 @@ sidebar_position: 5 | `ProductId` | `string` | — | 可选 | — | 产品标识,多产品时用于区分 | | `UpdatePath` | `string` | `InstallPath` | 可选 | 有效目录路径 | 升级程序所在目录 | | `Bowl` | `string` | `null` | 可选 | 有效文件名 | 更新前需关闭的辅助进程名 | -| `Scheme` | `string` | `null` | 可选 | `"Bearer"` 等 | 认证方案 | +| `Scheme` | `string` | `null` | 可选 | `"Bearer"` 等 | 认证方案(已废弃,推荐使用 `AuthScheme`) | | `Token` | `string` | `null` | 可选 | — | 认证令牌 | +| `AuthScheme` | `AuthScheme?` | `null` | 可选 | `Hmac`, `Bearer`, `ApiKey`, `Basic` | 认证方案枚举,设置后自动选择对应 Provider | +| `BasicUsername` | `string` | `null` | 可选 | — | HTTP Basic 认证用户名(`AuthScheme = Basic` 时必填) | +| `BasicPassword` | `string` | `null` | 可选 | — | HTTP Basic 认证密码(`AuthScheme = Basic` 时必填) | | `Files` | `List` | `null` | 可选 | — | 更新时跳过的指定文件列表 | | `Formats` | `List` | `null` | 可选 | — | 更新时跳过的扩展名列表 | | `Directories` | `List` | `null` | 可选 | — | 更新时跳过的目录列表 | @@ -394,9 +401,55 @@ await new GeneralUpdateBootstrap() - 自定义 `IDownloadSource` 会完全替换默认的 HTTP 版本检查逻辑 - 需要同时注册 `DownloadOrchestrator()` 时,orchestrator 会接管完整下载流程 -#### 场景 4:自定义 HTTP 认证 +#### 场景 4:多协议 HTTP 认证 -【场景说明】为 Core 发出的 HTTP 请求追加 JWT Bearer Token 认证头。 +【场景说明】Core 内置四种认证方案,通过 `AuthScheme` 枚举一键切换,也可通过 `IHttpAuthProvider` 完全自定义。 + +**方式一:使用内置 `AuthScheme` 枚举(推荐)** + +```csharp +using GeneralUpdate.Core.Configuration; + +// HMAC-SHA256 签名认证(默认) +var request = new UpdateRequest +{ + UpdateUrl = "https://update.example.com/api/upgrade/verification", + AppSecretKey = "your-app-secret", + AuthScheme = AuthScheme.Hmac +}; + +// Bearer Token 认证 +var bearerRequest = new UpdateRequest +{ + UpdateUrl = "https://update.example.com/api/upgrade/verification", + AppSecretKey = "your-app-secret", + AuthScheme = AuthScheme.Bearer, + Token = "your-jwt-token" +}; + +// API Key 认证 +var apiKeyRequest = new UpdateRequest +{ + UpdateUrl = "https://update.example.com/api/upgrade/verification", + AppSecretKey = "your-app-secret", + AuthScheme = AuthScheme.ApiKey, + Token = "your-api-key" +}; + +// HTTP Basic 认证 +var basicRequest = new UpdateRequest +{ + UpdateUrl = "https://update.example.com/api/upgrade/verification", + AppSecretKey = "your-app-secret", + AuthScheme = AuthScheme.Basic, + BasicUsername = "admin", + BasicPassword = "password123" +}; +``` + +**方式二:自定义 `IHttpAuthProvider`(高级)** + +【场景说明】为 Core 发出的 HTTP 请求追加自定义认证逻辑(如从配置中心动态获取 Token)。 【示例代码】 @@ -429,8 +482,10 @@ await new GeneralUpdateBootstrap() ``` 【效果&注意事项】 -- 认证提供器在每次 HTTP 请求前被调用 -- 需要在无参构造函数中自行读取配置 +- 内置 Provider:`HmacAuthProvider`(默认)、`BearerTokenAuthProvider`、`ApiKeyAuthProvider`、`BasicAuthProvider` +- `HttpAuthProviderFactory` 根据 `AuthScheme` 自动选择对应 Provider +- 自定义 Provider 需要在无参构造函数中自行读取配置 +- HMAC 签名算法:`HMAC-SHA256(body|timestamp)`,请求头 `X-Update-Timestamp` + `X-Update-Signature` #### 场景 5:静默更新 + 进程退出触发升级 @@ -575,6 +630,43 @@ builder.Services.AddSingleton(sp => - DI 容器管理生命周期,避免手动 Dispose - 可将配置从 `appsettings.json` 注入 +#### 场景 8:跨平台自适应策略 + +【场景说明】Core 根据运行时平台自动选择 `WindowsStrategy` / `LinuxStrategy` / `MacStrategy`,无需手动指定。也可通过 `PlatformType` 显式控制或自定义平台策略。 + +【示例代码】 + +```csharp +using GeneralUpdate.Core; +using GeneralUpdate.Core.Configuration; +using GeneralUpdate.Core.Strategy; + +// 方式一:自动检测(推荐) +// Core 会根据 RuntimeInformation 自动选择对应平台策略 +await new GeneralUpdateBootstrap() + .SetSource( + updateUrl: "https://update.example.com/api/upgrade/verification", + appSecretKey: "your-app-secret") + .SetOption(Option.AppType, AppType.Client) + .LaunchAsync(); +// Windows → WindowsStrategy +// Linux → LinuxStrategy(无 Bowl 支持) +// macOS → MacStrategy + +// 方式二:显式指定平台策略(高级) +await new GeneralUpdateBootstrap() + .SetConfig(request) + .Strategy() + .SetOption(Option.AppType, AppType.Client) + .LaunchAsync(); +``` + +【效果&注意事项】 +- 三种平台策略均继承 `AbstractStrategy`,共享 Hash → Compress → Patch 管道 +- `WindowsStrategy` 额外支持 Bowl 辅助进程管理 +- `LinuxStrategy` 建议配合 `UnixPermissionHooks` 使用以自动赋予执行权限 +- 平台策略可被自定义 `IStrategy` 完全替换 + --- ## 5. 常规使用示例 @@ -874,6 +966,40 @@ GeneralTracer.SetTracingEnabled(true); GeneralTracer.Dispose(); ``` +### AOT / Trim 兼容性 + +`net8.0` 和 `net10.0` 目标框架支持 AOT 发布和裁剪。Core 在 `JsonContext` 命名空间下提供了 9 个源生成 JSON 序列化上下文,覆盖所有配置和模型类型的序列化需求,避免反射导致的裁剪问题: + +| 上下文类 | 覆盖类型 | +| --- | --- | +| `BaseResponseJsonContext` | `BaseResponseDTO`、`VersionRespDTO` | +| `VersionEntryJsonContext` | `VersionEntry`、`VersionIdentity` | +| `UpdateRequestJsonContext` | `UpdateRequest`、`UpdateConfiguration` | +| `ProcessContractJsonContext` | `ProcessContract` | +| `ManifestInfoJsonContext` | `ManifestInfo` | +| `OssVersionRecordJsonContext` | `OssVersionRecord` | +| `DownloadAssetJsonContext` | `DownloadAsset` | +| `BlackPolicyJsonContext` | `BlackPolicy` | +| `PushPayloadJsonContext` | `PushPayload` | + +### 进程环境变量(IPC 辅助) + +`Environments` 静态类通过 AES 加密临时文件在 Client 与 Upgrade 进程之间传递键值对环境变量,用于升级失败追踪等场景: + +| 环境变量 Key | 说明 | +| --- | --- | +| `UpgradeFail` | 记录上次升级失败的版本号,避免重复尝试失败版本 | + +```csharp +using GeneralUpdate.Core.Configuration; + +// 设置环境变量(Client 端写入) +Environments.Set("UpgradeFail", "2.0.0"); + +// 读取环境变量(Upgrade 端读取) +var failedVersion = Environments.Get("UpgradeFail"); +``` + --- ## 相关资源 diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/Android auto-update.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/Android auto-update.md deleted file mode 100644 index e1f888a..0000000 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/Android auto-update.md +++ /dev/null @@ -1,423 +0,0 @@ ---- -sidebar_position: 13 -title: Android Auto-Update ---- - -# Android Auto-Update - -## Overview - -GeneralUpdate provides two independent auto-update components for .NET Android applications: - -| Component | NuGet Package | Framework | Repository | -|-----------|---------------|-----------|------------| -| **GeneralUpdate.Maui.Android** | `GeneralUpdate.Maui.Android` | .NET MAUI (Android) | [GitHub](https://github.com/GeneralLibrary/GeneralUpdate.Maui) | -| **GeneralUpdate.Avalonia.Android** | `GeneralUpdate.Avalonia.Android` | Avalonia 12+ (Android) | [GitHub](https://github.com/GeneralLibrary/GeneralUpdate.Avalonia) | - -Both libraries are **UI-less** Android auto-update cores focused on reusable update orchestration. They do not provide any UI — callers implement their own update prompt UI. - -### Shared capabilities - -- **Version validation**: compare current version against remote to decide whether to update -- **Resumable APK download**: HTTP Range-based resume support -- **SHA256 integrity verification**: automatic hash validation of downloaded files -- **Android installer triggering**: `Intent` + `FileProvider` to launch system installer -- **Event notifications**: download progress, completion, failure lifecycle events - -### End-to-end update flow - -```text -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ 1. Validate │ -> │ 2. Download │ -> │ 3. Verify │ -> │ 4. Install │ -│ ValidateAsync│ │ (resumable) │ │ SHA256 check │ │ Intent call │ -└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ -``` - ---- - -## GeneralUpdate.Maui.Android - -### Installation - -Target frameworks: `net10.0; net10.0-android` - -```bash -dotnet add package GeneralUpdate.Maui.Android -``` - -### Dependency Injection - -```csharp -using GeneralUpdate.Maui.Android.Services; -using Microsoft.Extensions.DependencyInjection; - -var services = new ServiceCollection(); -services.AddGeneralUpdateMauiAndroid(); - -using var provider = services.BuildServiceProvider(); -var bootstrap = provider.GetRequiredService(); -``` - -Direct creation is also supported: - -```csharp -var bootstrap = GeneralUpdateBootstrap.CreateDefault(); -``` - -### Quick start - -```csharp -using GeneralUpdate.Maui.Android.Models; -using GeneralUpdate.Maui.Android.Services; - -var bootstrap = GeneralUpdateBootstrap.CreateDefault(); - -// ── Event listeners ─────────────────────────── -bootstrap.AddListenerValidate += (_, e) => -{ - Console.WriteLine($"New version available: {e.PackageInfo.Version}"); -}; - -bootstrap.AddListenerDownloadProgressChanged += (_, e) => -{ - var s = e.Statistics; - Console.WriteLine( - $"{s.ProgressPercentage:F2}% | {s.DownloadedBytes}/{s.TotalBytes} | " + - $"remaining: {s.RemainingBytes} | speed: {s.BytesPerSecond:F0} B/s"); -}; - -bootstrap.AddListenerUpdateCompleted += (_, e) => -{ - Console.WriteLine($"Stage={e.Stage}, File={e.PackagePath}"); -}; - -bootstrap.AddListenerUpdateFailed += (_, e) => -{ - Console.WriteLine($"Failed: {e.Reason}, {e.Message}"); -}; - -// ── Build update package metadata ───────────── -var package = new UpdatePackageInfo -{ - Version = "2.0.0", - VersionName = "2.0", - ReleaseNotes = "Performance and stability improvements", - DownloadUrl = "https://example.com/app-release.apk", - Sha256 = "3A0D2F...F9C2", - PackageSize = 52_428_800 -}; - -// ── Execute update ──────────────────────────── -var options = new UpdateOptions -{ - CurrentVersion = "1.5.0", - InstallOptions = new AndroidInstallOptions - { - FileProviderAuthority = $"{AppInfo.PackageName}.fileprovider" - } -}; - -var check = await bootstrap.ValidateAsync(package, options, CancellationToken.None); -if (check.IsUpdateAvailable) -{ - var result = await bootstrap.ExecuteUpdateAsync(package, options, CancellationToken.None); - Console.WriteLine(result.IsSuccess ? "Update workflow completed." : $"Update failed: {result.Message}"); -} -``` - -### Core API - -#### IAndroidBootstrap - -| Method | Description | -|--------|-------------| -| `ValidateAsync(package, options, ct)` | Validates whether the remote version is higher than current | -| `ExecuteUpdateAsync(package, options, ct)` | Executes the full update flow (download + verify + install) | - -#### Events - -| Event | When fired | -|-------|-----------| -| `AddListenerValidate` | A higher version is detected | -| `AddListenerDownloadProgressChanged` | Periodic download statistics (speed, downloaded, remaining, percentage) | -| `AddListenerUpdateCompleted` | Workflow milestones: `DownloadCompleted`, `VerificationCompleted`, `InstallationTriggered` | -| `AddListenerUpdateFailed` | Update failure with `UpdateFailureReason` and error message | - -#### UpdateOptions - -| Property | Type | Description | -|----------|------|-------------| -| `CurrentVersion` | `string` | Current application version | -| `DownloadDirectory` | `string?` | Download directory (optional, defaults to app cache) | -| `TemporaryFileExtension` | `string` | Temp file extension during download, default `.downloading` | -| `DeleteCorruptedPackageOnFailure` | `bool` | Whether to delete corrupted package on failure, default `true` | -| `ProgressReportInterval` | `TimeSpan` | Progress report interval, default 500ms | -| `InstallOptions` | `AndroidInstallOptions` | Android install options | - -#### UpdatePackageInfo - -| Property | Type | Description | -|----------|------|-------------| -| `Version` | `string` | Remote version (required) | -| `VersionName` | `string?` | Display version name | -| `ReleaseNotes` | `string?` | Release notes | -| `DownloadUrl` | `string` | APK download URL (required) | -| `Sha256` | `string` | APK SHA256 hash (required) | -| `PackageSize` | `long?` | Package size in bytes, used for progress estimation | -| `ForceUpdate` | `bool` | Whether this is a forced update | - ---- - -## GeneralUpdate.Avalonia.Android - -### Installation - -Target framework: `net8.0-android` (compatible with `net9.0-android`+) - -```bash -dotnet add package GeneralUpdate.Avalonia.Android -``` - -### Avalonia UI thread dispatching - -Avalonia requires UI updates on the UI thread. Implement `IUpdateEventDispatcher` to marshal callbacks: - -```csharp -using GeneralUpdate.Avalonia.Android.Abstractions; - -public sealed class AvaloniaUiDispatcher : IUpdateEventDispatcher -{ - public void Dispatch(Action callback) - { - Avalonia.Threading.Dispatcher.UIThread.Post(callback); - } -} -``` - -### Quick start - -```csharp -using GeneralUpdate.Avalonia.Android; -using GeneralUpdate.Avalonia.Android.Abstractions; -using GeneralUpdate.Avalonia.Android.Models; - -// ── Configure options ───────────────────────── -var options = new AndroidUpdateOptions -{ - DownloadDirectoryPath = Path.Combine( - Android.App.Application.Context.CacheDir!.AbsolutePath!, "update"), - FileProviderAuthority = "com.example.app.generalupdate.fileprovider" -}; - -var bootstrap = GeneralUpdateBootstrap.CreateDefault( - options, - eventDispatcher: new AvaloniaUiDispatcher()); - -// ── Event listeners ─────────────────────────── -bootstrap.AddListenerValidate += (_, e) => -{ - Console.WriteLine($"New version: {e.PackageInfo.Version} → current: {e.CurrentVersion}"); -}; - -bootstrap.AddListenerDownloadProgressChanged += (_, e) => -{ - var d = e.Download; - Console.WriteLine($"{d.ProgressPercentage:F1}% | {d.Downloaded}/{d.Total} | speed: {d.Speed:F0} B/s"); -}; - -bootstrap.AddListenerUpdateCompleted += (_, e) => -{ - Console.WriteLine($"Completed: {e.Result.Stage}, file: {e.Result.FilePath}"); -}; - -bootstrap.AddListenerUpdateFailed += (_, e) => -{ - Console.WriteLine($"Failed: {e.Result.Reason}, {e.Result.Message}"); -}; - -// ── Build update package metadata ───────────── -var packageInfo = new UpdatePackageInfo -{ - Version = "2.3.0", - VersionName = "2.3", - Description = "Added dark mode support and bug fixes", - DownloadUrl = "https://example.com/app-release.apk", - Sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - FileSize = 52_428_800, - FileName = "app-release.apk", - IsForced = false -}; - -// ── Three-phase update flow ─────────────────── -var check = await bootstrap.ValidateAsync(packageInfo, "2.2.1", CancellationToken.None); -if (check.UpdateFound) -{ - var prepared = await bootstrap.DownloadAndVerifyAsync(packageInfo, CancellationToken.None); - if (prepared.Success && prepared.FilePath is not null) - { - await bootstrap.LaunchInstallerAsync(packageInfo, prepared.FilePath, CancellationToken.None); - } -} -``` - -### Core API - -#### IAndroidBootstrap - -Avalonia uses a **three-phase API** separating download/verify from install, giving callers more control: - -| Method | Description | -|--------|-------------| -| `ValidateAsync(packageInfo, currentVersion, ct)` | Version check, returns `UpdateCheckResult` | -| `DownloadAndVerifyAsync(packageInfo, ct)` | Download APK and verify SHA256, returns `UpdateOperationResult` | -| `LaunchInstallerAsync(packageInfo, apkFilePath, ct)` | Trigger Android system installer | -| `GetSnapshot()` | Get current update state snapshot | - -#### Events - -| Event | Args | Description | -|-------|------|-------------| -| `AddListenerValidate` | `ValidateEventArgs` | Version validation result | -| `AddListenerDownloadProgressChanged` | `DownloadProgressChangedEventArgs` | Download progress (speed, downloaded, remaining, percentage, status) | -| `AddListenerUpdateCompleted` | `UpdateCompletedEventArgs` | Phase completion notification | -| `AddListenerUpdateFailed` | `UpdateFailedEventArgs` | Failure details | - -#### AndroidUpdateOptions - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `DownloadDirectoryPath` | `string` | Empty (uses app cache) | APK download directory | -| `TemporaryFileExtension` | `string` | `.part` | Download temp file extension | -| `SidecarExtension` | `string` | `.json` | Resume sidecar metadata extension | -| `FileProviderAuthority` | `string` | Empty (required) | Android `FileProvider` authority | -| `DownloadBufferSize` | `int` | `64 * 1024` | Download buffer size | -| `SpeedSmoothingWindowSeconds` | `int` | `4` | Speed smoothing window in seconds | - -#### UpdatePackageInfo - -| Property | Type | Description | -|----------|------|-------------| -| `Version` | `string` | Remote version (required) | -| `VersionName` | `string?` | Display version name | -| `Description` | `string?` | Update description | -| `DownloadUrl` | `string` | APK download URL (required) | -| `FileSize` | `long` | File size in bytes | -| `Sha256` | `string` | APK SHA256 hash (required) | -| `FileName` | `string?` | Download file name | -| `IsForced` | `bool` | Whether this is a forced update | -| `PublishTime` | `DateTimeOffset?` | Publish timestamp | - ---- - -## Android project configuration - -Both components require `FileProvider` configuration in your Android project: - -### AndroidManifest.xml - -```xml - - - - - -``` - -> Tip: if your app already has a `FileProvider`, you can reuse the existing authority. - -### Resources/xml/generalupdate_file_paths.xml - -```xml - - - - - -``` - ---- - -## Maui vs Avalonia comparison - -| Dimension | GeneralUpdate.Maui.Android | GeneralUpdate.Avalonia.Android | -|-----------|---------------------------|-------------------------------| -| Target framework | `net10.0-android` | `net8.0-android` (+ higher) | -| DI support | ✅ built-in `AddGeneralUpdateMauiAndroid()` | Manual creation | -| API style | Two-phase: `ValidateAsync` + `ExecuteUpdateAsync` (combined download+verify+install) | Three-phase: `ValidateAsync` + `DownloadAndVerifyAsync` + `LaunchInstallerAsync` | -| UI thread dispatch | Handled automatically by .NET MAUI | Requires custom `IUpdateEventDispatcher` | -| Resume metadata | Built-in | Sidecar JSON file (`.json` extension) | -| Speed smoothing | Default 500ms report interval | Configurable smoothing window (seconds) | -| State snapshot | — | `GetSnapshot()` returns `UpdateStateSnapshot` | -| Version comparison | Built-in | Replaceable `IVersionComparer` (default `SystemVersionComparer`) | - -### Choosing between the two - -- **.NET MAUI project**: use `GeneralUpdate.Maui.Android` for DI integration and a simpler two-phase API -- **Avalonia project**: use `GeneralUpdate.Avalonia.Android` for finer-grained three-phase control and replaceable strategies -- **Maximum flexibility**: Avalonia's separated API and replaceable components offer more customization - ---- - -## Server requirements - -Both libraries are **client-only** — no mandatory server-side dependencies. You need an HTTP(S) endpoint to serve the APK download and a version-check endpoint (implement it yourself) returning: - -```json -{ - "version": "2.0.0", - "versionName": "2.0", - "releaseNotes": "Performance and stability improvements", - "downloadUrl": "https://cdn.example.com/app-release.apk", - "sha256": "3a0d2f...f9c2", - "packageSize": 52428800, - "forceUpdate": false -} -``` - -> Tip: host version metadata on an OSS static file server and share `versions.json` with GeneralUpdate.Core's OSS mode. - ---- - -## FAQ - -### Can downloads resume after interruption? - -Yes. Both libraries support HTTP Range resume. Maui uses plain Range-request resume; Avalonia additionally uses a sidecar JSON file for resume metadata. - -### What happens when SHA256 validation fails? - -The corrupted APK is automatically deleted. Maui controls this via `DeleteCorruptedPackageOnFailure` (default `true`); Avalonia always discards failed files. An `UpdateFailed` event is raised on failure. - -### How do I implement forced updates? - -Set `ForceUpdate = true` (Maui) or `IsForced = true` (Avalonia) on `UpdatePackageInfo`. Your app decides whether to block users from continuing with the old version on a forced update. - -### What should the FileProvider authority be? - -Format: `{package-name}.{arbitrary-suffix}`, e.g. `com.example.app.generalupdate.fileprovider`. The `authorities` value in `AndroidManifest.xml` must match `FileProviderAuthority` in code. - -### Which Android permissions are required? - -```xml - - - -``` - -> Android 8.0+ requires `REQUEST_INSTALL_PACKAGES` to install APKs from unknown sources. - ---- - -## Related resources - -- [GeneralUpdate.Maui repository](https://github.com/GeneralLibrary/GeneralUpdate.Maui) -- [GeneralUpdate.Avalonia repository](https://github.com/GeneralLibrary/GeneralUpdate.Avalonia) -- [GeneralUpdate.Core component docs](./GeneralUpdate.Core.md) -- [GeneralUpdate.Core component docs](./GeneralUpdate.Core.md) -- [Beginner cookbook](../quickstart/Beginner cookbook.md) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/Android auto-update.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/Android auto-update.md deleted file mode 100644 index 5b8509b..0000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/Android auto-update.md +++ /dev/null @@ -1,423 +0,0 @@ ---- -sidebar_position: 13 -title: Android 自动更新 ---- - -# Android 自动更新 - -## 组件概览 - -GeneralUpdate 为 .NET Android 应用提供了两套独立的自动更新组件: - -| 组件 | NuGet 包 | 适用框架 | 仓库 | -|------|----------|----------|------| -| **GeneralUpdate.Maui.Android** | `GeneralUpdate.Maui.Android` | .NET MAUI(Android) | [GitHub](https://github.com/GeneralLibrary/GeneralUpdate.Maui) | -| **GeneralUpdate.Avalonia.Android** | `GeneralUpdate.Avalonia.Android` | Avalonia 12+(Android) | [GitHub](https://github.com/GeneralLibrary/GeneralUpdate.Avalonia) | - -两个组件都是**无 UI** 的 Android 自动更新核心库,专注于可复用的更新编排。它们不提供任何界面,由调用方自行实现更新提示 UI。 - -### 共同特性 - -- **版本校验**:对比当前版本与远程版本,判断是否需要更新 -- **断点续传下载**:基于 HTTP Range 的 APK 断点续传下载 -- **SHA256 完整性校验**:自动验证下载文件的哈希值 -- **系统安装器触发**:通过 `Intent` + `FileProvider` 调起 Android 系统安装器 -- **事件通知**:下载进度、完成、失败等生命周期事件 - -### 更新流程(端到端) - -```text -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ 1. 版本验证 │ -> │ 2. 断点下载 │ -> │ 3. 哈希校验 │ -> │ 4. 触发安装 │ -│ ValidateAsync │ │ Download │ │ SHA256 Check │ │ Installer │ -└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ -``` - ---- - -## GeneralUpdate.Maui.Android - -### 安装 - -目标框架:`net10.0; net10.0-android` - -```bash -dotnet add package GeneralUpdate.Maui.Android -``` - -### 依赖注入(DI) - -```csharp -using GeneralUpdate.Maui.Android.Services; -using Microsoft.Extensions.DependencyInjection; - -var services = new ServiceCollection(); -services.AddGeneralUpdateMauiAndroid(); - -using var provider = services.BuildServiceProvider(); -var bootstrap = provider.GetRequiredService(); -``` - -也支持直接创建(无需 DI 容器): - -```csharp -var bootstrap = GeneralUpdateBootstrap.CreateDefault(); -``` - -### 快速开始 - -```csharp -using GeneralUpdate.Maui.Android.Models; -using GeneralUpdate.Maui.Android.Services; - -var bootstrap = GeneralUpdateBootstrap.CreateDefault(); - -// ── 事件监听 ────────────────────────────────── -bootstrap.AddListenerValidate += (_, e) => -{ - Console.WriteLine($"发现新版本: {e.PackageInfo.Version}"); -}; - -bootstrap.AddListenerDownloadProgressChanged += (_, e) => -{ - var s = e.Statistics; - Console.WriteLine( - $"{s.ProgressPercentage:F2}% | {s.DownloadedBytes}/{s.TotalBytes} | " + - $"剩余: {s.RemainingBytes} | 速度: {s.BytesPerSecond:F0} B/s"); -}; - -bootstrap.AddListenerUpdateCompleted += (_, e) => -{ - Console.WriteLine($"阶段={e.Stage}, 文件={e.PackagePath}"); -}; - -bootstrap.AddListenerUpdateFailed += (_, e) => -{ - Console.WriteLine($"失败: {e.Reason}, {e.Message}"); -}; - -// ── 构造更新包信息 ──────────────────────────── -var package = new UpdatePackageInfo -{ - Version = "2.0.0", - VersionName = "2.0", - ReleaseNotes = "性能和稳定性改进", - DownloadUrl = "https://example.com/app-release.apk", - Sha256 = "3A0D2F...F9C2", - PackageSize = 52_428_800 -}; - -// ── 执行更新 ────────────────────────────────── -var options = new UpdateOptions -{ - CurrentVersion = "1.5.0", - InstallOptions = new AndroidInstallOptions - { - FileProviderAuthority = $"{AppInfo.PackageName}.fileprovider" - } -}; - -var check = await bootstrap.ValidateAsync(package, options, CancellationToken.None); -if (check.IsUpdateAvailable) -{ - var result = await bootstrap.ExecuteUpdateAsync(package, options, CancellationToken.None); - Console.WriteLine(result.IsSuccess ? "更新流程已完成。" : $"更新失败: {result.Message}"); -} -``` - -### 核心 API - -#### IAndroidBootstrap - -| 方法 | 说明 | -|------|------| -| `ValidateAsync(package, options, ct)` | 校验远程版本是否高于当前版本 | -| `ExecuteUpdateAsync(package, options, ct)` | 执行完整更新流程(下载 + 校验 + 安装) | - -#### 事件 - -| 事件 | 触发时机 | -|------|----------| -| `AddListenerValidate` | 检测到更高版本时触发 | -| `AddListenerDownloadProgressChanged` | 下载进度更新(速度、已下载字节、剩余字节、百分比) | -| `AddListenerUpdateCompleted` | 工作流里程碑:`DownloadCompleted`、`VerificationCompleted`、`InstallationTriggered` | -| `AddListenerUpdateFailed` | 更新失败,附带 `UpdateFailureReason` 和错误消息 | - -#### UpdateOptions - -| 属性 | 类型 | 说明 | -|------|------|------| -| `CurrentVersion` | `string` | 当前应用版本号 | -| `DownloadDirectory` | `string?` | 下载目录(可选,默认使用应用缓存目录) | -| `TemporaryFileExtension` | `string` | 下载中临时文件扩展名,默认 `.downloading` | -| `DeleteCorruptedPackageOnFailure` | `bool` | 失败时是否删除损坏的包,默认 `true` | -| `ProgressReportInterval` | `TimeSpan` | 进度报告间隔,默认 500ms | -| `InstallOptions` | `AndroidInstallOptions` | Android 安装选项 | - -#### UpdatePackageInfo - -| 属性 | 类型 | 说明 | -|------|------|------| -| `Version` | `string` | 远程版本号(必填) | -| `VersionName` | `string?` | 版本名称(展示用) | -| `ReleaseNotes` | `string?` | 更新日志 | -| `DownloadUrl` | `string` | APK 下载地址(必填) | -| `Sha256` | `string` | APK 文件 SHA256 哈希值(必填) | -| `PackageSize` | `long?` | 包大小(字节),用于进度估算 | -| `ForceUpdate` | `bool` | 是否强制更新 | - ---- - -## GeneralUpdate.Avalonia.Android - -### 安装 - -目标框架:`net8.0-android`(兼容 `net9.0-android`+) - -```bash -dotnet add package GeneralUpdate.Avalonia.Android -``` - -### Avalonia UI 线程调度 - -Avalonia 需要在 UI 线程上更新界面。实现 `IUpdateEventDispatcher` 将回调调度到 Avalonia UI 线程: - -```csharp -using GeneralUpdate.Avalonia.Android.Abstractions; - -public sealed class AvaloniaUiDispatcher : IUpdateEventDispatcher -{ - public void Dispatch(Action callback) - { - Avalonia.Threading.Dispatcher.UIThread.Post(callback); - } -} -``` - -### 快速开始 - -```csharp -using GeneralUpdate.Avalonia.Android; -using GeneralUpdate.Avalonia.Android.Abstractions; -using GeneralUpdate.Avalonia.Android.Models; - -// ── 配置选项 ────────────────────────────────── -var options = new AndroidUpdateOptions -{ - DownloadDirectoryPath = Path.Combine( - Android.App.Application.Context.CacheDir!.AbsolutePath!, "update"), - FileProviderAuthority = "com.example.app.generalupdate.fileprovider" -}; - -var bootstrap = GeneralUpdateBootstrap.CreateDefault( - options, - eventDispatcher: new AvaloniaUiDispatcher()); - -// ── 事件监听 ────────────────────────────────── -bootstrap.AddListenerValidate += (_, e) => -{ - Console.WriteLine($"发现新版本: {e.PackageInfo.Version} → 当前: {e.CurrentVersion}"); -}; - -bootstrap.AddListenerDownloadProgressChanged += (_, e) => -{ - var d = e.Download; - Console.WriteLine($"{d.ProgressPercentage:F1}% | {d.Downloaded}/{d.Total} | 速度: {d.Speed:F0} B/s"); -}; - -bootstrap.AddListenerUpdateCompleted += (_, e) => -{ - Console.WriteLine($"完成: {e.Result.Stage}, 文件: {e.Result.FilePath}"); -}; - -bootstrap.AddListenerUpdateFailed += (_, e) => -{ - Console.WriteLine($"失败: {e.Result.Reason}, {e.Result.Message}"); -}; - -// ── 构造更新包信息 ──────────────────────────── -var packageInfo = new UpdatePackageInfo -{ - Version = "2.3.0", - VersionName = "2.3", - Description = "新增暗色模式支持,修复若干问题", - DownloadUrl = "https://example.com/app-release.apk", - Sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - FileSize = 52_428_800, - FileName = "app-release.apk", - IsForced = false -}; - -// ── 三阶段更新流程 ──────────────────────────── -var check = await bootstrap.ValidateAsync(packageInfo, "2.2.1", CancellationToken.None); -if (check.UpdateFound) -{ - var prepared = await bootstrap.DownloadAndVerifyAsync(packageInfo, CancellationToken.None); - if (prepared.Success && prepared.FilePath is not null) - { - await bootstrap.LaunchInstallerAsync(packageInfo, prepared.FilePath, CancellationToken.None); - } -} -``` - -### 核心 API - -#### IAndroidBootstrap - -Avalonia 采用**三阶段 API**,将下载验证与安装触发分离,给予调用方更多控制: - -| 方法 | 说明 | -|------|------| -| `ValidateAsync(packageInfo, currentVersion, ct)` | 版本校验,返回 `UpdateCheckResult` | -| `DownloadAndVerifyAsync(packageInfo, ct)` | 下载 APK 并校验 SHA256,返回 `UpdateOperationResult` | -| `LaunchInstallerAsync(packageInfo, apkFilePath, ct)` | 调起 Android 系统安装器 | -| `GetSnapshot()` | 获取当前更新状态快照 | - -#### 事件 - -| 事件 | 参数 | 说明 | -|------|------|------| -| `AddListenerValidate` | `ValidateEventArgs` | 版本验证完成 | -| `AddListenerDownloadProgressChanged` | `DownloadProgressChangedEventArgs` | 下载进度(速度、已下载、剩余、百分比、状态消息) | -| `AddListenerUpdateCompleted` | `UpdateCompletedEventArgs` | 各阶段完成通知 | -| `AddListenerUpdateFailed` | `UpdateFailedEventArgs` | 更新失败详情 | - -#### AndroidUpdateOptions - -| 属性 | 类型 | 默认值 | 说明 | -|------|------|--------|------| -| `DownloadDirectoryPath` | `string` | 空(使用应用缓存) | APK 下载目录 | -| `TemporaryFileExtension` | `string` | `.part` | 下载临时文件扩展名 | -| `SidecarExtension` | `string` | `.json` | 断点续传 sidecar 元数据扩展名 | -| `FileProviderAuthority` | `string` | 空(必填) | Android `FileProvider` authority | -| `DownloadBufferSize` | `int` | `64 * 1024` | 下载缓冲区大小 | -| `SpeedSmoothingWindowSeconds` | `int` | `4` | 速度平滑窗口(秒) | - -#### UpdatePackageInfo - -| 属性 | 类型 | 说明 | -|------|------|------| -| `Version` | `string` | 远程版本号(必填) | -| `VersionName` | `string?` | 版本名称 | -| `Description` | `string?` | 更新描述 | -| `DownloadUrl` | `string` | APK 下载地址(必填) | -| `FileSize` | `long` | 文件大小(字节) | -| `Sha256` | `string` | APK SHA256 哈希(必填) | -| `FileName` | `string?` | 下载文件名 | -| `IsForced` | `bool` | 是否强制更新 | -| `PublishTime` | `DateTimeOffset?` | 发布时间 | - ---- - -## Android Project 配置 - -两个组件都需要在 Android 项目中配置 `FileProvider`: - -### AndroidManifest.xml - -```xml - - - - - -``` - -> 提示:如果应用已有 `FileProvider`,可复用现有的 authority。 - -### Resources/xml/generalupdate_file_paths.xml - -```xml - - - - - -``` - ---- - -## Maui vs Avalonia 对比 - -| 维度 | GeneralUpdate.Maui.Android | GeneralUpdate.Avalonia.Android | -|------|---------------------------|-------------------------------| -| 目标框架 | `net10.0-android` | `net8.0-android`(+ 更高版本) | -| DI 支持 | ✅ 内置 `AddGeneralUpdateMauiAndroid()` | 手动创建 | -| API 风格 | 两阶段:`ValidateAsync` + `ExecuteUpdateAsync`(合并下载+校验+安装) | 三阶段:`ValidateAsync` + `DownloadAndVerifyAsync` + `LaunchInstallerAsync` | -| UI 线程调度 | 通过 .NET MAUI 自动处理 | 需自行实现 `IUpdateEventDispatcher` | -| 断点续传元数据 | 内置 | Sidecar JSON 文件(`.json` 扩展名) | -| 速度平滑 | 默认 500ms 报告间隔 | 可配置平滑窗口(秒) | -| 状态快照 | — | `GetSnapshot()` 返回 `UpdateStateSnapshot` | -| 版本比较 | 内置 | 可替换 `IVersionComparer`(默认 `SystemVersionComparer`) | - -### 选型建议 - -- **.NET MAUI 项目**:选择 `GeneralUpdate.Maui.Android`,享受 DI 集成和更简洁的两阶段 API -- **Avalonia 项目**:选择 `GeneralUpdate.Avalonia.Android`,具有更细粒度的三阶段控制和可替换策略 -- **需要最大灵活性的场景**:Avalonia 的分离式 API 和可替换组件提供更多定制空间 - ---- - -## 服务端要求 - -两个组件都是**纯客户端库**,对服务端无强制要求。你需要提供一个可下载 APK 的 HTTP(S) 端点和一个返回以下信息的版本检查接口(自行实现): - -```json -{ - "version": "2.0.0", - "versionName": "2.0", - "releaseNotes": "性能和稳定性改进", - "downloadUrl": "https://cdn.example.com/app-release.apk", - "sha256": "3a0d2f...f9c2", - "packageSize": 52428800, - "forceUpdate": false -} -``` - -> 建议:可将版本信息托管在 OSS 静态文件服务器上,与 GeneralUpdate.Core 的 OSS 模式共用 `versions.json`。 - ---- - -## 常见问题 - -### 下载中断后能否恢复? - -可以。两个组件都支持 HTTP Range 断点续传。Maui 使用单纯的 Range 请求恢复;Avalonia 额外使用 sidecar JSON 文件记录断点元数据。 - -### SHA256 校验失败怎么办? - -组件会自动删除损坏的 APK 文件。Maui 由 `DeleteCorruptedPackageOnFailure` 控制(默认 `true`);Avalonia 始终会丢弃校验失败的文件。校验失败后会抛出 `UpdateFailed` 事件。 - -### 如何实现强制更新? - -在 `UpdatePackageInfo` 中设置 `ForceUpdate = true`(Maui)或 `IsForced = true`(Avalonia)。你的应用自行决定在收到强制更新标记后是否阻止用户继续使用旧版本。 - -### FileProvider authority 怎么填? - -格式为 `{应用包名}.{任意后缀}`,例如 `com.example.app.generalupdate.fileprovider`。`AndroidManifest.xml` 中的 `authorities` 必须与代码中的 `FileProviderAuthority` 一致。 - -### 需要哪些 Android 权限? - -```xml - - - -``` - -> Android 8.0+ 需要 `REQUEST_INSTALL_PACKAGES` 才能安装来自未知来源的 APK。 - ---- - -## 相关资源 - -- [GeneralUpdate.Maui 仓库](https://github.com/GeneralLibrary/GeneralUpdate.Maui) -- [GeneralUpdate.Avalonia 仓库](https://github.com/GeneralLibrary/GeneralUpdate.Avalonia) -- [GeneralUpdate.Core 组件文档](./GeneralUpdate.Core.md) -- [GeneralUpdate.Core 组件文档](./GeneralUpdate.Core.md) -- [入门实战手册](../quickstart/Beginner cookbook.md) diff --git a/website/sidebars.js b/website/sidebars.js index 17e9c6e..98dc475 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -37,7 +37,6 @@ const sidebars = { 'doc/GeneralUpdate.Differential', 'doc/GeneralUpdate.Drivelution', 'doc/GeneralUpdate.Extension', - 'doc/Android auto-update', ], },