使用Sparkle框架MacOS程序升级

本地环境

使用CMake编译的QT项目,版本:Sparkle 2

简介

Sparkle是一个非常简单且易用的macOS应用程序更新框架,目前众多知名macOS App都在使用。

Sparkle的原理是根据提前配置好的xml文件地址,每次启动后解析xml,看看有没有比当前版本新的数据,有的话提示更新。 xml文件可以存在任何可以访问xml元数据的服务器,包括 GitHub 仓库。

一、下载编译Sparkle

1.1 下载Sparkle

github地址:https://github.com/sparkle-project/Sparkle

git clone --recursive https://github.com/sparkle-project/Sparkle

1.2 编译Sparkle

使用Xcode打开项目文件

1.可以选择支持两种架构的版本编译

2.点击运行按钮进行编译

1.3 编译运行generate_keys生成公钥和私钥

该脚本目的生成公钥和私钥

根据提示信息将公钥添加到Info.plist

1.4编译generate_appcast

该脚本目的自动生成appcast.xml

./generate_appcast myapp_updates

myapp_updates是一个文件夹

myapp_updates/
├── MyApp_1.2.0.zip
├── MyApp_1.2.0.html
├── appcast.xml
  • MyApp_1.2.0.zip: 这是新版本1.2.0的更新归档文件,用户将通过Sparkle框架下载这个文件来升级到新版本。
  • MyApp_1.2.0.html: 这是1.2.0版本的HTML格式发行说明,可能包含富文本和格式化的更改列表。
  • appcast.xml: 这是由generate_appcast工具生成或更新的文件,包含了所有可用更新的元数据。在运行工具之前,这个文件可能不存在,或者需要更新。

检查最终生成的appcast.xml

1.3 拷贝生成的Sparkle文件

1.3.1 找到编译文件生成目录

1.3.2 拷贝Sparkle.framework目录到你的项目下

二、引入Sparkle到项目中

2.1 修改CMakeLists.txt

# 添加 Sparkle.framework
set(SPARKLE_FRAMEWORK_DIR ${CMAKE_SOURCE_DIR}/你的目录/Sparkle.framework)
set(SPARKLE_INCLUDE_DIR ${SPARKLE_FRAMEWORK_DIR}/Headers)
include_directories(${SPARKLE_INCLUDE_DIR})

add_executable(MyProject
    AutoUpgrade.h
    AutoUpgrade.cpp
    #与C++的桥接文件
    MacosAutoUpgrade.mm
    MacosAutoUpgrade.h
)

add_compile_options(-x objective-c++)
target_link_libraries(Project PRIVATE  ${SPARKLE_FRAMEWORK_DIR})

2.2 代码编写

2.2.1 object-C文件
#ifdef __APPLE__
#include "MacosAutoUpgrade.h"
#import <Sparkle/Sparkle.h>
#include <QPushButton>

@interface AppUpdaterDelegate : NSObject <SPUUpdaterDelegate>

@property(nonatomic) SPUStandardUpdaterController *updaterController;
@property(nonatomic) QObject *updaterObject;

@end

@implementation AppUpdaterDelegate

- (instancetype)initWithUpdaterObject:(QObject *)updaterObject {
  self = [super init];
  if (self) {
    _updaterObject = updaterObject;
  }
  return self;
}

// 检查key至
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey, id> *)change
                       context:(void *)context {
  if ([keyPath isEqualToString:NSStringFromSelector(@selector(canCheckForUpdates))]) {
    bool canCheckForUpdates = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
    QMetaObject::invokeMethod(_updaterObject, "updateButtonState", Q_ARG(bool, canCheckForUpdates));
  } else {
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  }
}

- (void)dealloc {
  @autoreleasepool {
    [_updaterController.updater removeObserver:self
                                    forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates))];
  }
}

@end

Updater::Updater(QPushButton *checkForUpdatesButton) {
  @autoreleasepool {
    _updaterDelegate = [[AppUpdaterDelegate alloc] initWithUpdaterObject:this];
    _updaterDelegate.updaterController =
        [[SPUStandardUpdaterController alloc] initWithStartingUpdater:YES
                                                      updaterDelegate:_updaterDelegate
                                                   userDriverDelegate:nil];

    connect(checkForUpdatesButton, &QPushButton::clicked, this, &Updater::checkForUpdates);

    [_updaterDelegate.updaterController.updater
        addObserver:_updaterDelegate
         forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates))
            options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew)
            context:nil];
  }

  startAutomaticChecks();
}

// 主动检查更新,只要有最新版本无论是否点击忽略版本都展示升级界面
void Updater::checkForUpdates() {
  @autoreleasepool {
    [_updaterDelegate.updaterController checkForUpdates:nil];
  }
}

// 自动检测更新时,如果待升级的版本已点击忽略,则不展示升级界面
void Updater::startAutomaticChecks() {
  @autoreleasepool {
    // 开启自动检查更新
    _updaterDelegate.updaterController.updater.automaticallyChecksForUpdates = YES;
    // 每天检查一次
    _updaterDelegate.updaterController.updater.updateCheckInterval = 86400;
    // 已忽略的版本不展示升级界面 
    [_updaterDelegate.updaterController.updater checkForUpdatesInBackground];
  }
}

void Updater::updateButtonState(bool canCheckForUpdates) {
  Q_EMIT buttonStateUpdated(canCheckForUpdates);
}

#endif
2.2.2 头文件
#ifndef MACOS_AUTO_UPGRADE_H
#define MACOS_AUTO_UPGRADE_H

#include <QObject>

class QPushButton;

#ifdef __OBJC__
@class AppUpdaterDelegate;
#endif

class Updater : public QObject {
  Q_OBJECT
 public:
#ifdef __APPLE__
  Updater(QPushButton *checkForUpdatesButton);
  void startAutomaticChecks();

 signals:
  void buttonStateUpdated(bool canCheckForUpdates);

 private slots:
  void checkForUpdates();
  void updateButtonState(bool canCheckForUpdates);

 private:
#ifdef __OBJC__
  AppUpdaterDelegate *_updaterDelegate;
#else
  void *_updaterDelegate;
#endif
#endif
};

#endif  // MACOS_AUTO_UPGRADE_H
2.2.3调用代码
  std::unique_ptr<Updater> auto_upgrade_ = nullptr;  
  auto_upgrade_ = std::make_unique<Updater>(ui->pushButton);
  auto_upgrade_->startAutomaticChecks();
  QObject::connect(auto_upgrade_.get(), &Updater::buttonStateUpdated,
                   ui->pushButton, &QPushButton::setEnabled);

2.3 修改info.plist

    <!-- 用于比较版本 --> 
    <key>CFBundleVersion</key>
    <string>1.0.2</string>

    <!-- 应用程序的最低系统要求 --> 
    <key>LSMinimumSystemVersion</key>
    <string>12.5</string>

    <!-- 这个键用于启用或禁用自动更新检查。如果设置为 true,Sparkle 将自动检查更新 -->    
    <key>SUEnableAutomaticChecks</key>
    <true/>

    <!-- 增加自动更新,无需交互选项 --> 
    <key>SUAllowsAutomaticUpdates</key>
    <true/>
    <!-- 自动更新检查间隔时间(秒) --> 
    <key>SUScheduledCheckInterval</key>
    <integer>86400</integer>

    <!-- 这是Sparkle用来检查更新的URL。指向一个XML文件,该文件包含应用程序的最新版本信息 -->  
    <key>SUFeedURL</key>
    <string>https://yourwebsite.com/updates/appcast.xml</string>

    <!-- generate_keys生成的公钥 --> 
    <key>SUPublicEDKey</key>
    <string>your public key</string>

三、制作升级包流程

  • 编译程序生成.app文件
  • .app文件签名
  • .app文件压缩为zip/tar
  • 新建文件夹./upgrade
  • 移动压缩包到./upgrade目录下
  • 新建说明文件.txt
  • 移动说明文件.txt到./upgrade目录下
  • 执行./generate_appcast ./upgrade生成appcast.xml
  • 修改xml内部信息适配项目
  • 上传.xml 压缩文件和 .txt文件到对应位置即可使用(可以上传github测试)

四、问题排查

如果升级失败,可以使用控制台查看具体报错信息。

 

1.查看用户日志

2.点击开始(抓取日志)

3.运行更新程序(等待更新失败)

4.点击暂停,停止抓取新日志

5.搜索你的项目名,查看对应升级模块日志查看错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值