本地环境
使用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.搜索你的项目名,查看对应升级模块日志查看错误