JOC.Update 客户端集成指南:WinForms、WPF 与 UniApp APK 在线升级


JOC.Update 跨平台客户端集成封面

JOC.Update 客户端集成指南:WinForms、WPF 与 UniApp APK 在线升级

本文给出三套可直接落地的集成方式:Windows 客户端使用 GZUpdate 的 ZIP 增量更新能力;WPF 增加独立发布配置和统一进度窗口;UniApp Android 使用完整 APK 与系统安装器完成覆盖安装。

1. 后台产品与升级包约定

每种客户端建立独立产品,并固定产品编号、发布通道和升级模式:

客户端版本类型更新模式机器版本
WinForms / WPFreleasebetaincrementalGZUpdate 的 __version__ 数字版本
Android APKandroidfullAPK versionCode

后台版本号必须严格递增。versionTxt 仅用于显示,不参与比较。客户端只能看到已发布且通道、模式均匹配的版本。

Windows 更新包是 ZIP,包内路径相对应用根目录:

update-20401.zip
├── DesktopApp.exe
├── DesktopApp.dll
└── plugins/
    └── Reporting.Plugin.dll

不要把用户数据、数据库、环境配置或 __version__ 放进升级 ZIP。定制更新执行器(例如 __update__.dll)能运行本机代码,必须作为受信任发布物审核。

2. WinForms:启动前检查、进度窗体与手动检查

2.1 引入并注册客户端库

主项目引用:

<ItemGroup>
  <PackageReference Include="GZUpdate.Client" Version="*" />
  <PackageReference Include="GZUpdate.Client.Win" Version="*" />
</ItemGroup>

Program.cs 中集中注册升级服务,清理上一轮备份,并在创建主窗体前完成检查:

using GZUpdate.Client;
using GZUpdate.Client.Win;

internal static class Program
{
    const string UpdateServer = "https://updates.example.com/";
    const string ProductId = "ERP_DESKTOP";
    const string Executor = "__update__.dll";

    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();

        UpdateServices.Regsiter(
            UpdateServer, ProductId,
            EnumVersionType.release, Executor);
        UpdateServices.deleteBak();

        WinUpdateClient.Configure(
            server: UpdateServer,
            productId: ProductId,
            versionType: EnumVersionType.release,
            upgradeExecutorDllPath: Executor);

        var result = WinUpdateClient.CheckAndUpdate();
        if (result.ShouldExitApplication)
            return;

        Application.Run(new MainForm());
    }
}

检查应早于任何主窗体、插件或子进程启动,否则被占用的 EXE/DLL 会导致替换失败。

2.2 自定义 WinForms 进度界面

不要手写“下载后直接覆盖”的逻辑。使用 UpdateProcess 执行更新,并把事件绑定到进度窗体:

var process = new UpdateProcess();
process.OnUpdateMessage += dialog.SetMessage;
process.OnUpdateProcess += dialog.SetPackageProgress;
process.OnUpdateDownProgress += dialog.SetDownloadProgress;
process.OnFail += dialog.SetFailure;
process.OnComplate += dialog.SetComplete;

var updated = await Task.Run(() => process.DoUpdate());

进度窗体至少显示当前消息、下载百分比/速度、更新包数量、“关闭”和“重启”。更新进行时禁止关闭;失败或完成后才启用关闭。所有回调都可能来自后台线程,因此在 SetMessageSetPackageProgress 等方法中通过 BeginInvoke 回到 UI 线程。

2.3 增加手动检查按钮

主窗体按钮与启动检查共用同一套更新流程。检查期间禁用按钮,先找出数字版本最大的候选项,再让用户确认:

private async void CheckUpdateButton_Click(object? sender, EventArgs e)
{
    CheckUpdateButton.Enabled = false;
    try
    {
        var versions = await Task.Run(() =>
            UpdateServices.doCheckVersion() ?? []);

        if (versions.Count == 0)
        {
            MessageBox.Show("当前已经是最新版本。", "在线升级");
            return;
        }

        var latest = versions.OrderByDescending(item => item.version).First();
        if (MessageBox.Show(
                $"发现新版本 {latest.versionTxt},是否立即升级?",
                "在线升级",
                MessageBoxButtons.YesNo) == DialogResult.Yes)
        {
            await RunUpdateWithDialogAsync(this);
        }
    }
    finally
    {
        CheckUpdateButton.Enabled = true;
    }
}

3. WPF:可开关配置、升级协调器和统一主题进度窗

WPF 项目引用 GZUpdate.ClientGZUpdate.Client.WPF,并将升级配置放进发布目录的 update.json。默认关闭:

{
  "enabled": false,
  "serverBaseUrl": "https://updates.example.com/",
  "productCode": "ERP_WPF",
  "versionType": "release",
  "upgradeExecutorDllPath": "__update__.dll"
}

文件不存在或 enabled=false 时,不检查更新。启用后若缺少地址、产品编号、通道或执行器文件名,必须报告配置错误,不能将其伪装成“无更新”。

3.1 建立协调器

将配置转换为 WpfUpdateOptions,并把底层结果归一为 DisabledNoUpdateUpdatedUpdatedAndRestartedFailed,供启动与手动检查复用:

var options = new WpfUpdateOptions
{
    Server = config.ServerBaseUrl,
    ProductId = config.ProductCode,
    VersionType = config.VersionType,
    UpgradeExecutorDllPath = config.UpgradeExecutorDllPath,
    PromptBeforeManualUpdate = false,
    CloseDialogOnStartupComplete = true,
    RestartAfterStartupUpdate = true,
    RestartAfterManualUpdate = true,
    DialogTitle = "程序升级",
    CheckFailedTitle = "检查更新失败",
    ProgressDialogFactory = new ApplicationUpdateProgressDialogFactory()
};

WpfUpdateClient.Configure(options);
var result = WpfUpdateClient.CheckAndUpdate(owner, WpfUpdateMode.Startup);

3.2 在显示主窗口前处理结果

强制升级策略应在创建 MainWindow 前执行:

var result = updates.CheckForUpdate(owner: null);

if (result.Status == ApplicationUpdateStatus.Failed)
{
    MessageBox.Show("启动更新检查失败,请检查网络或升级配置。",
        "检查更新失败", MessageBoxButton.OK, MessageBoxImage.Error);
    Shutdown(-1);
    return;
}

if (result.Status is ApplicationUpdateStatus.Updated
    or ApplicationUpdateStatus.UpdatedAndRestarted)
{
    Shutdown();
    return;
}

MainWindow = new MainWindow(viewModel);
MainWindow.Show();

只有 DisabledNoUpdate 才能进入主窗口。若产品要求离线可用,可以将 Failed 改为风险提示后继续,但这是产品策略,不能静默处理。

3.3 进度窗口接口

实现 IWpfUpdateProgressDialogFactory,每次升级创建一个 IWpfUpdateProgressDialog。该窗口应实现 ShownSetMessageSetDownloadProgressSetPackageProgressSetCompleteSetFailureEnableClose

升级中在 Closing 事件取消关闭;失败或结束后才允许关闭。事件更新 UI 时使用 Dispatcher.CheckAccess()Dispatcher.BeginInvoke()。主界面“检查更新”菜单只调用协调器,并按五种状态显示相应结果。

4. UniApp Android:完整 APK、原生进度层和系统安装

Android 不能以 ZIP 覆盖正在运行的应用,后台必须发布 full APK,后台版本号必须等于 manifest.json 中递增的 versionCode

4.1 配置和启动检查

将配置独立为模块,并只使用 HTTPS:

export const appUpdateConfig = Object.freeze({
  serverUrl: 'https://updates.example.com',
  productId: 'ERP_ANDROID',
  versionType: 'android',
  updateMode: 'full',
})
import { checkAppUpdate } from './utils/app-updater'

export default {
  onLaunch() {
    checkAppUpdate()
  },
}

使用 plus.runtime.getProperty 获取已安装 APK 的真实 versionCode;请求 /checkversion 后,只接受 HTTP 200、status=0、更高的整数版本和 HTTPS 下载地址。

4.2 下载与安装

下载到应用私有目录,先比对服务端 fileSizeBytes,通过才交给系统安装器:

const task = plus.downloader.createDownload(
  downloadUrl,
  { filename: `_doc/updates/app-${versionCode}.apk` },
  (download, status) => {
    if (status !== 200 || !download.filename) {
      showUpdateError('APK 下载失败,请稍后重试。')
      return
    }

    if (expectedFileSize > 0 &&
        download.downloadedSize !== expectedFileSize) {
      showUpdateError('APK 文件大小校验失败,已取消安装。')
      return
    }

    plus.runtime.install(download.filename, {}, onInstalled, onInstallFailed)
  },
)
task.start()

通过 statechanged 读取 downloadedSizetotalSize 和时间差,计算百分比和速度。原生 WebView 会遮挡普通 Vue 弹层时,使用透明原生 Webview 显示全屏下载进度,并在成功、失败或取消后关闭。

服务端返回的显示版本字段是 versionTxt,目标版本文本应优先读取它:

function getTargetVersionText(version, versionCode) {
  const text = version.versionTxt ?? version.VersionTxt
  const value = String(text ?? versionCode).trim()
  return /^v/i.test(value) ? `目标版本:${value}` : `目标版本:v${value}`
}

Android 最终会校验包名、签名证书和 versionCode。清单应声明网络、网络状态和安装包请求权限,用户仍需在系统中允许未知来源安装,应用不能绕过系统确认。

该链路已覆盖 HTTPS 与文件大小。若要使用服务端 fileSha256,应接入可审计的原生哈希能力,并在散列匹配前禁止调用 plus.runtime.install

4.3 APK 发布步骤

  1. 递增 manifest.jsonversionCode,填写 versionName
  2. 使用相同包名和相同签名证书构建 APK。
  3. 在后台上传 APK:版本号填 versionCode,显示版本填 versionName,类型填 android,模式填 full
  4. 保存草稿,在真机验证检查、下载、进度、系统安装和重启。
  5. 验证后发布。回滚也必须发布更高 versionCode 的 APK,Android 不允许低版本覆盖安装。

5. 发布前检查

  • Windows ZIP 保持相对目录,不携带用户数据、环境配置或 __version__
  • 检查前关闭占用安装目录的插件和子进程。
  • 后台产品编号、通道、版本号和模式与客户端一致。
  • WPF 发布包包含 update.json,启用状态可审计。
  • Android 使用 HTTPS、完整 APK、同一签名证书和递增 versionCode
  • 坚持“草稿 → 真机验证 → 发布”;异常版本应下架。
版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
MCP自动发表文章
下一篇:没有了
评论列表

发表评论

评论内容
昵称:
验证码:
验证码
关联文章

JOC.Update 客户集成指南WinFormsWPF UniApp APK 线升级
GZUpdate自动升级服务 .NET C/S Winform客户程序自动升级演示
GZUpdate自动升级程序客户演示
2.客户服务连接
20260610升级指南
客户发送数据
YES-WIN Winform开发框架 日志管理升级指南
客户接收文件
客户的实现
服务获取客户连接
客户发送,服务接收并输出
Windows 11 运行安卓子系统安装教程 安装apk
ABP VNext框架中Winform终端的开发和客户授权信息的处理
Epicor客户安装
服务回发,客户接收并输出
SAP S/4HANA MM模块培训 60 - 制造企业流程:PP、MM、QM、PM、SDCS集成
Winform中使用HttpClientapi服务进行交互
FTP客户工具 FileZilla
openVPN客户windows开机自动启动
如何新版outlook客户配置腾讯企业邮箱