自动更新功能设计与实现

前言

“自动更新”功能通常包含在各类软件中,操作系统类例如 Windows;应用软件类例如 Google Chrome。通过“自动更新”功能,可以使用最新的更新和增强功能来自动使软件保持最新。用户不必再主动搜索重要的更新和信息,软件通常可以识别网络情况,并使用 Internet 连接从指定网站获取更新内容,每当有新的更新可用时,软件可以提示用户进行升级或进行自动升级。

当然也有其他情况,例如在 iOS 中,软件的更新由 App Store 接管,并且对于热更新有着严格的使用限制,详见 知乎:如何看待苹果禁止 JSPatch 等 iOS APP 热更新方案?


参考


设计

简单来说,自动更新功能流程如下:

graph LR
A([启动更新]) --> B[查询新版本]
B --> C{是否有新版本}
C --否--> D([结束更新])
C --是--> E[下载更新内容]
E --> F[应用更新内容]
F --> G([结束更新])

版本定义

参考 iOS,铁路12306 的版本记录有版本号、更新说明及一些其他信息(例如发布日期)。

版本号(Version)

关于版本号的定义,借鉴 .NET的版本控制

遵循语义版本控制 (SemVer),也就是采用 MAJOR.MINOR.PATCH 版本控制。

MAJOR 在下列情况时递增:

  • 产品或新产品方向发生重大更改。
  • 发生了中断性变更。 接受中断性变更存在较大障碍。
  • 旧版本不再受支持。
  • 采用了现有依赖项的较新 MAJOR 版本。

MINOR 在下列情况时递增:

  • 添加了公共 API 外围应用。
  • 添加了新行为。
  • 采用了现有依赖项的较新 MINOR 版本。
  • 引入了新依赖项。

PATCH 在下列情况时递增:

  • 进行了 Bug 修复。
  • 添加了对较新平台的支持。
  • 采用了现有依赖项的较新 PATCH 版本。
  • 任何其他不符合上述情况的更改。

更新说明(Changelog

描述本次更新修改的内容,参考以下格式:

1
2
3
4
5
6
7
8
9
YYYY-MM-DD  John Doe  <johndoe@example.com>

* myfile.ext (myfunction): my changes made
additional changes

* myfile.ext (unrelated_change): my changes made
to myfile.ext but completely unrelated to the above

* anotherfile.ext (somefunction): more changes

安装程序(Installer)

提供 安装程序 的文件名及 Hash,例如 MD5、SHA-1、CRC32。

其他的 Hash 可以参考下图:

版本存档(Archive)

将指定版本添加存档属性后,该版本拒绝提供下载,只能进入服务端查看信息。

强制更新(Mandatory)

将指定版本设置为强制更新,则客户端程序检测到该版本为新版本时,会进行后台自动更新,更新完成后提示用户重启程序后生效。

最新版本(LastestVersion)

将指定版本设置为最新版本,即该版本可以不是实际上最新的版本,则客户端在检测更新时,只会截止到该版本,如果客户端版本高于该版本,可以进行降级更新。

这项设置的意义是,当发布某个新版本后,发现该版本有严重 BUG ,在解决 BUG 之前可以将上个版本设置为最新版本,并同时设置强制更新,让客户端程序自动进行降级处理。

版本资源

版本资源提供查询服务,提供某个版本所有文件的相对路径及 Hash 值。

服务端资源

假设应用程序的名称为APP,则创建如下目录存放资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
C:\USERS\PUBLIC\DOCUMENTS\PUBLISH\APP
├─Installer
APP-Setup-0.1.0.exe
APP-Setup-0.2.0.exe
APP-Setup-0.3.0.exe

└─Version
├─0.1.0
APP.dll
d1.dll

├─0.2.0
│ │ APP.dll
│ │ d1.dll
│ │
│ └─directory1
d2.dll

└─0.3.0
APP.dll

├─directory1
d2.dll

└─directory2
d3.dll

版本信息记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
{
"Name":"APP",
"LastestVersion":"0.3.0",
"Mandatory":{
"IsEnabled":true,
"MinVersion":"0.2.0"
},
"Versions":[
{
"Version":"0.1.0",
"Installer":"APP-Setup-0.1.0.exe",
"CheckSum":{
"SHA-1":"a21701759faa69ac13fd73b8f0c5540f902893dc"
},
"Changelog":"log1",
"Files":[
{
"Filename":"APP.dll",
"Path":"/APP.dll",
"CheckSum":{
"SHA-1":"5e7e3f131ff3e34aa1afb8b896496c3250acf706"
}
},
{
"Filename":"d1.dll",
"Path":"/d1.dll",
"CheckSum":{
"SHA-1":"ec4467dc951ceca5e0c3847e9616d12a5df034c6"
}
}
]
},
{
"Version":"0.2.0",
"Installer":"APP-Setup-0.2.0.exe",
"CheckSum":{
"SHA-1":"1035b0bbf0b5bb3ca293c849bdd7b05fdbcb02c5"
},
"Changelog":"log2",
"Files":[
{
"Filename":"APP.dll",
"Path":"/APP.dll",
"CheckSum":{
"SHA-1":"5e7e3f131ff3e34aa1afb8b896496c3250acf706"
}
},
{
"Filename":"d1.dll",
"Path":"/d1.dll",
"CheckSum":{
"SHA-1":"43e19c5733e776077d61ec77843605871e8a836b"
}
},
{
"Filename":"d2.dll",
"Path":"/directory1/d2.dll",
"CheckSum":{
"SHA-1":"04798252b1562c0d343322b0400603751125eb89"
}
}
]
},
{
"Version":"0.3.0",
"Installer":"APP-Setup-0.3.0.exe",
"CheckSum":{
"SHA-1":"613999ce29210ec6f729177ab082ed3790e3ecf7"
},
"Changelog":"log3",
"Files":[
{
"Filename":"APP.dll",
"Path":"/APP.dll",
"CheckSum":{
"SHA-1":"e23d34dbb69dd1f4d0bc3944fc30ac1b51ab9beb"
}
},
{
"Filename":"d2.dll",
"Path":"/directory1/d2.dll",
"CheckSum":{
"SHA-1":"6325d6305301eeeff4446249e7caaa9c935b2565"
}
},
{
"Filename":"d3.dll",
"Path":"/directory2/d3.dll",
"CheckSum":{
"SHA-1":"f66d766856d94cf117c07f4a1fc7734387915ff7"
}
}
]
}
]
}

客户端资源

1
2
3
4
5
6
C:\Program Files\APP
APP.exe

└─0.1.0
APP.dll
d1.dll

客户端更新逻辑

sequenceDiagram
%% autonumber

客户端->>客户端: 程序启动
客户端->>客户端: 搜索本地最新版本(1),将老版本(2)删除
loop
客户端->>服务端: 定期查询新版本
    alt 有新版本
        服务端->>客户端: 提供新版本(3)及文件列表信息(4)
        客户端->>客户端: 创建文件夹(3),并将(1)中的文件复制到(3)
        客户端->>服务端: 根据(4)的信息,请求下载更新文件
        服务端->>客户端: 传输有差异的文件(新增或替换)
        客户端->>客户端: 删除本地不在(4)中的文件
    else
        服务端-->>客户端: 结束更新
    end    
end


实现

留坑。


后记

本篇内容只粗略介绍了自动更新功能,但在真实的应用场景中,需要考虑更多因素,例如 Ubuntu 中更新源的设置,Windows 中的传递优化。