微信号:frontshow

介绍:InfoQ大前端技术社群:囊括前端、移动、Node全栈一线技术,紧跟业界发展步伐。

Electron应用自动更新方案设计

2018-03-02 18:02 赵沈晶

本文为 Electron 开发系列文章,前面的文章见:
Electron 开发,如何入坑?
Electron 开发跨平台构建流程设计

在发布一个桌面应用之前,必须要考虑的一个问题是:怎么更新(迭代)?一个应用不可能没有更新,除非没人用的或没人维护,从人肉更新到自动更新,再到复杂的更新方案,这里有很多产品和技术的设计...

这是 Electron 系列的第三篇文章,也是 Electron 应用自动更新方案的上篇「从需求到设计」,下篇会讲「从设计到实现」。

为什么要讲从需求到设计?这不是产品的活么?

不要那么仔细地去区分产品、开发,一些技术驱动的功能上,开发需要承担产品的工作,自动更新就是这样的一个功能。而且,前期的功能设计,直接影响了开发的实现方式,在充分理解前期设计的基础上,开发才能做出合理的实现。

我设计和实现了 Electron 应用的一整套自动更新方案,并且已应用于产品上,所以写下这篇文章和大家分享,可以怎么设计和实现一个 Electron 应用的自动更新,我们对于自动更新需求的考虑可能比你想得稍微复杂一些。

本文将首先从需不需要更新开始谈起,接着谈怎么从人肉更新一步步进化到无痛的自动更新。然后,我会向你介绍,一个完善后的自动更新需求可以是怎样的。最后,也是最关键的一部分 —— 我们该采取怎样的更新思路,又该如何设计我们的产品逻辑。

说明 :本文实现的自动更新方案只是众多更新方案中的一种,供参考,你既可以完全参照着这么做,也可以从中获取些许启发,更进一步,如果你已经实现了自动更新,可以比较本文和你所采用方案的异同优劣,欢迎留言交流 。

一、真的需要自动更新么

首先我们抠个字眼,「更新」包括但不等同「自动更新」,在考虑一个应用的自动更新前,要先理清楚你的应用特质和发行策略,这决定着要不要实现自动更新功能,以及「自动的程度」。

1. 更新是一种追求,活着的应用都需要

更新,是为了交付给用户更好的产品,这是追求,活着的应用都是需要的,因为活着需要有一个盼头,当然如果「产品活着」就是你的追求,当我没说。

2. 应用的性质和发行策略决定了要不要自动更新

「更新」是必要的,而「自动更新」未必。你的 Office 套装、Adobe 套装(不使用 Creative Cloud 安装的)的更新大多是一次巨大的体验升级,而它们并没有如另一些应用一样时不时弹出一个通知,让你更新。

这样的应用,一般是一些复杂度高、可离线化运行、发行周期长、老牌的应用。而 Sketch 这样的设计软件,也是同性质的,但它却是采用在线自动更新机制的,所以要不要自动更新,还和应用发行方的策略相关。

3. 自动更新是现代桌面应用的一个基本需求

首先,软件的迭代方式普遍有了改变,现代软件的开发方式强调「快速迭代,不断交付」,这是因为在现代的技术背景下,可以以极小的成本做到快速调整和迭代,像 Electron 应用的迭代甚至可以做到小成本,跨平台的快速迭代,这也就意味着能更快地交付用户更好的产品。而要做到这一点的技术保障就是 —— 自动更新。

再者,自动更新是用户的需求,也是发行方的需求。对用户而言,能方便地在第一时间获取到更好的应用;对于发行方而言,能快速地推动版本升级,及早获得产品反馈,获取更多的用户。

二、从人肉更新到自动更新

「自动更新」有多自动?这是一个好问题,自动更新可以实现「全自动」,但是全自动未必是好的,就像要做一杯完美的意式咖啡,使用全自动咖啡机的结果是差强人意的,只有半自动咖啡机加一位咖啡师才能完成。

1. 最牛逼的更新方式

最牛逼的更新方式是开一个发布会,或者发一个新版软件的通告,然后就有大把大把的用户去竞相下载新版本应用,还会奔走相告,引得更多人去下载和更新,这是最牛逼的更新方式,也是牛逼的人肉更新方式,比如 Adobe 系列产品。

而如果你的产品没有这样的煽动性,你也打算让用户去人肉更新,让用户天天关注你的下载页或邮件,期望着在发布了新版之后,用户们都能很自觉地下载和更新,那么到头来你或许会很失望,而且你会面对「版本分布散乱」的问题,这个问题又会带出一系列其他的问题。

所以,不要期待着用户会时刻关注你的产品更新,让用户主动去发现新版本(尤其是在应用外的地方,比如网站)然后人肉更新,这对于大部分产品来说都是一个梦想。

2. 假自动更新:应用内通知,应用外下载,手动安装

那么稍稍地进一步,稍稍地自动化一点,不用用户主动去发现新版本,而是在应用内告诉用户,这相比于上面的人肉更新,已经进步一大截了。

这种更新方式下,一旦发行方有新版的应用发布,用户 会在应用内收到新版可用的更新消息推送 ,然后可能需要你点开一个链接,这时会用浏览器打开下载页,你需要手动地下载新版应用,完了后手动地安装。

但注意,这只是实现一个通知功能,虽然有很大进步,但并没有到可以称为「自动更新」的程度,所以我称之为「假自动更新」。

3. 自动更新:应用内通知、下载后,唤起安装过程

自然地再进一步,我们会期望在应用内可以通知用户,并且应用本身还能帮助我们下载新版应用安装包,下载后,应用还能帮助我们唤起安装过程(和你的应用具体的安装方式有强关系)。

这样的更新方式下,我们才算是自动更新,应用本身可以通知、下载和唤起安装程序,这一套逻辑是应用本身具备的。在用户角度来看,每次有更新就会收到通知,只要轻轻点一下「立即更新」,应用就会下载读条,下载完了后就可以自动安装,没有多余操作。

4. 无痛的自动更新:下次打开即新版

人啊,往往会得寸进尺,我能不能在用户都没有感知的情况下,就「偷偷」地更新我的应用呢?答案是可以的。

一次更新无非是,检查更新、通知用户、下载文件、安装,如果你把这些步骤都藏在后台,那么用户自然是无感知的了。有了更新不通知,直接下载好,至于安装,覆盖的安装必然需要退出应用,那么就在用户退出应用的时候唤起安装,并且使安装过程不可见,那么就实现了,等用户下次打开的时候就是新版了。

不过不告知用户,完全透明地去更新,这种做法一般不推荐,最好还是通知一下用户,但是可以提供用户选择「退出应用后更新」,我们下面会讲到的更新方案里就有这一项。

三、一个完善的自动更新需求

在这一节,我将描述一个完整的自动更新需求,我们目前在使用的更新方案就是基于此建立的。

1. 自动更新的基本需求

不管每个应用的自动更新有多少的细节差别,有一些基本的需求(也可以说环节)是必须要实现的。

  • 检查更新 :检查是否有新版本可用,什么时候检查,有哪些方式触发检查,可以自定

  • 通知用户 :当有更新可用的时候需要通知用户,可以检查到有更新就通知,也可以在更新文件下载好了后再通知,前者用户可以见到下载过程,后者用户感受不到下载过程

  • 下载更新 :应用有能力下载更新,并且保证下载的完整性,可以后台下载,也可以前台下载

  • 安装更新 :应用能唤起下载好的新版应用安装程序,然后自身退出,至于什么时候唤起安装,可以根据需要做设计

2. 区分强弱更新

如果实现了以上的基本需求,自动更新已经完整了,但是上面每一步发生的时机、和用户的交互、视觉等方面都可以做一些设计,概括地说,就是自动更新这个需求可以再拆分。

2.1 为什么要划分出强弱更新方式

我们把自动更新划分成了强弱两种更新方式,背后的出发点是,我们对某次更新的定性。

如果某次更新只是功能特性的改进、小功能的上线、普通 BUG 的修复,总之是一次让你「 情绪保持稳定 」的更新,那么这被定性为一次弱更新,我们对于这次更新的期待是,希望用户更新,但是不强制。

如果某次更新包含了杀手级功能的上线、重大 BUG 的修复、大的改版,总之是一次让你「 无法保持平静 」的更新,那么这被定性为一次强更新,我们对于强更新的期待是,强烈推荐用户更新,具有强制性。

我们把「弱更新」称为「自动更新」,对应英文「Update」,把「强更新」称为「自动升级」,对应英文「Upgrade」。

2.2 弱更新和强升级的区别在哪
  • 通知形式不同

    • 弱更新以轻量的形式进行提醒,下载后提醒,不打断用户操作,依附于已有窗口

    • 强升级以重量的形式进行提醒,下载前提醒,打断用户操作,是一个新的窗口

  • 用户可选余地不同

    • 弱更新下,用户每次都可以选择下次安装

    • 强升级下,用户除了选择「立即升级」外,只能选择「退出应用再升级」,一般无法跳过

  • 下载方式不同

    • 弱更新下,下载是在后台的,对用户是透明的,下载完成后才唤起通知

    • 强升级下,下载是在前台的,用户可见(下载进度条),下载前就唤起通知

  • 安装时机不同

    • 弱更新下,用户点「立即更新」,即开始运行安装程序

    • 强升级下,选择「立即升级」,那么下载完成后自动开始安装;选择「退出应用再升级」,那么等退出应用后再自动运行安装程序(并且是静默的)

3. 异常处理

一次更新必然要保证异常不会引起更新有问题,而在一次更新过程中,主要就 2 类异常。

3.1 下载过程中的异常

这会引起下载不完全的问题,所以在应用下载时一定要保障应用是下载完全的,如果因为用户的原因,比如下载过程过,用户退出了应用,那么就应该在下次检查到更新时,重新下载。

但如果检查到更新后发现用户本地已经有完整的可用的最新更新包,那就不用下载。

3.2 安装过程中的异常

安装过程一般交给安装程序去保证,如果安装过程中退出,安装程序应该要保证安装的原子性。如果安装方式没有采用安装程序,比如 Mac 下使用 dmg 的形式分发的应用,那么把安装过程交给用户,一般可以避免风险,如针对 Mac 下以 dmg 形式分发的应用,把「拖拽新应用到应用文件夹覆盖原应用的动作」交由用户自己去操作。

四、更新的思路

这一节简单谈一下更新的本质是什么,做了什么,以及我们曾尝试过的方式。

1. 不完全更新:在线替换部分文件

之前在张鑫旭的博客上看到过一种更新方式,是在线请求核心的 js 文件,然后替换旧的文件。这样做的出发点是很好的,因为一次更新,大部分仅仅是部分文件的改动,那么请求改动的文件,替换掉就可以了。

但是这里有两个问题,第一个问题是这样做是一次不完全的更新,只能替换部分的文件,这也有办法解决,请求一个压缩包,然后起一个子进程去解压覆盖原应用文件夹同时退出应用防止文件占用,但是你怎么保证其可靠性;第二个问题,用户如果安装在某些文件夹下,写入是有权限问题的,应用本身的运行是不在管理员权限下的,唤起的进程默认是继承父进程的令牌的,那么也没有管理员权限,对于某些文件夹,需要提权,这又是一个难点。

2. 完全更新

也是基于自动更新实现考虑,我们重新设计了我们应用的安装方式,从「解压」变成「构建安装程序」(仅针对 Windows),如此我们分发应用就变成了分发安装包,我们采用的安装程序是 Inno Setup。

受启发于 Electron 官方推荐的更新方式以及 @Qquanwei 的 github(感谢这位开发者),再结合 Inno Setup 的官方资料,最终我们考虑清楚了更新的思路。

  • 我们分发的是使用 Inno Setup 构建的应用安装包,运行即可使用 Inno Setup 提供的安装过程来安装我们的应用

  • 我们在服务器部署了一个 json 文件,包含了我们发布的应用信息,包括每个版本的版本号、发行日期、更新概要、新版安装包下载链接

  • 用户每次打开应用,应用会去请求这个 json 文件,对比最新的版本号和当前的版本号以检查是否有更新

  • 下载更新就是下载最新版的应用安装包

  • 安装更新就是唤起一个子进程运行安装包,并退出自身,安装包会处理提权的事

  • 用户安装后,一次更新就完成了

五、设计自动更新的产品逻辑

在产品逻辑(流程设计)这一阶段,可以分为 3 大块:弱更新逻辑、强升级逻辑和检查更新以决定进入强升级 / 弱更新流程的逻辑。

1. 检查更新

检查更新有两个问题,第一个问题是我们检查什么?这里我们可以不依赖后端接口去实现检查,上一节也说了,我们把各次版本发行记录在一个 json 文件中,然后部署这个 json 文件到服务器,检查更新就是去服务器请求这个 json 文件,然后解析,对比里面的最新版和当前使用的客户端版本,知道有没有更新。

第二个问题是,依据什么判断是强升级还是弱更新?基于上面的需求拆分,我们需要划分出强弱更新,强弱更新的逻辑是不一样的,那么在检查更新阶段如何判定该进入哪个更新逻辑呢?

第一个办法,在 json 文件中对于每个版本标明相比于上一个版本是一次强升级还是弱更新,比如有一个 updateType 字段。这个方式的优点是简单明了,就算用户当前的版本和最新版相隔了好几个版本, 只要满足这其中跨越的有任何一个版本是强升级,那么这次的更新就是一次强升级 ,否则是一次弱更新。

第二个办法,一切根据版本号而来,「X.Y.Z」 这样的版本号,对比当前客户端的版本和最新版,我们可以把 X、Y 不变,仅仅 Z (修订版本号)改变的当做一次弱更新,如果对比发现,X 或 Y 有任何一个比当前的高了,那么就定义为一次强升级。这种方式不用额外的字段,也不用比对中间跨越的版本,仅仅需要对比当前的版本和最新的版本。

假设我们采用第二种方式,那么检查更新的流程是以下这样的:

2. 强升级逻辑

根据之前的需求分析,我们的强升级逻辑是以下这样的:

强升级下,一旦发行有可用的更新,在下载前就直接通知用户,而通知的方式是新建一个通知窗口,在 Electron 中也就是新建一个渲染进程。

用户无法选择「下次提醒」或「跳过」,而是只有立即升级和退出应用再升级两种选项。值得注意的是, 我们在对于退出应用再升级流程下的安装更新采用的是「静默的安装方式」 ,在用户看来,退出应用时,会出现一个 Windows 的 UAC 弹窗(系统级别,避免不了,至于 UAC 是什么,这里不展开),过了 UAC 之后, 之后的安装过程对用户来说是透明的,用户下次打开就是新版 。这是因为既然用户选择了退出,那么我们就认为用户不再需要看到应用安装后再启动,然后用户再关一次,而是在下一次打开再看到新版。

而非静默的安装方式,安装过程和进度对用户来说是可见的,而且安装好,应用会自动打开。

那这是怎么做到的呢?这是 Inno Setup 提供的一种特性,在运行安装包的时候可以给命令行参数以控制安装程序的表现,具体实现我们下一篇再说。

3. 弱更新逻辑

我们的弱更新逻辑相比强升级就比较简单了,而且是使用频率更高的更新方式:

可以从流程里看到,弱更新就像我们经常能看到的更新一样,首先它不应该打扰用户的操作,所以我们选择轻量的提醒方式,实际应用中我们采用的是如 VS Code 那样的更新提醒方式,是依附于主应用窗口的浮条形式来提醒。

而且我们隐去了下载过程,下载完了(即本地有下载完全的安装包)才提醒用户,用户点击更新,实际上仅仅是运行已经下载好了的安装程序,安装好了后自动打开新版应用。而下次提醒功能,就是本次应用打开期间不再提醒。

4. 扩展

上面的更新中异常怎么解决?审视一下最可能的异常(指由用户操作引起的,由程序编写引起的叫 BUG),就是下载过程中止和安装过程中止。这都好解决,下载中止,我们为了保险起见和简单,没有采用继续下载的方式,而是如果下载中止,那么就等于下载不完全,我们把这种情况看做「没有下载」处理,那就是重新下载。而安装中止,安装程序会帮你的。

还有一些应用中我们能看到「跳过这个版本」的选项,这其实也好实现。我们上面的「下次提醒」,本次使用期间不提醒,其实是给程序一个全局变量的标志来实现,而对于「跳过这个版本」来说,是每次打开都不再显示这个版本的更新提醒,自然我们想到数据的本地持久化。说白了,在用户的本地(或浏览器存储)中放一个标记,每次更新提醒前,去本地取出来再检查下就可以了。

六、小结

本次的「Electron 自动更新方案:从需求到设计」就到此为止,希望以上的设计方案能给大家一些参考,一般的应用更新,实现上面的弱更新逻辑就可以了,强升级的逻辑其实是为我们自己快速推动大版本的更新和迭代留了一个口子。此外,还是那句话,每个应用都有其特殊性,本文的更新方式只是众多方案中的一种,供参考,下一篇文章将讲基于这样的设计,我们是怎么实现的呢。

作者介绍

摘星,真名赵沈晶,毕业于浙江大学,是一个「不安分」的人,在校期间搞过咖啡店,做过平面设计,折腾过产品经理,最后「跌入」前端开发这个碗里,毕业后进入了酷家乐,目前在做客户端的开发。爱看书,类目广泛,从人文社科到和自己饭碗相关的都看,爱折腾新玩意、新技能、新应用,一切能提高工作效率或生活品质的东西都会去接触。

原文地址:

https://webfe.kujiale.com/electron-update-design/

前端之巅

「前端之巅」是 InfoQ 旗下关注前端技术的垂直社群,加入前端之巅学习群请关注「前端之巅」公众号后回复 “ 加群 ”。投稿请发邮件到 editors@cn.infoq.com,注明 “ 前端之巅投稿 ”。

专栏推荐:

推荐一个技术专栏,左耳朵耗子(陈皓)维护的《左耳听风》,每篇文章都是陈皓对自己多年“堵过的枪眼儿”“填过的坑儿”的深入思考和凝练,希望能够轻松应对团队管理、项目管理以及做好技术选型的技术领导者,希望能够快速成长进步的职场新人,广大在工作中遇到各种棘手问题的一线技术人,以及立志成为架构师的人,都能在其中获得帮助和启发。扫描下图二维码订阅专栏,现在注册极客时间,还可享受 30 元新人优惠。点击阅读原文查看专栏介绍。

 
前端之巅 更多文章 这里有25个React Native免费教程,请您查收~ RedMonk发布编程语言排行榜:Swift成为全球增长最快的编程语言 滴滴出行跨地域 iOS 构建优化与持续集成 Swift 4.2进入最后开发阶段,为Swift 5铺平道路 致移动开发前线读者
猜您喜欢 The Delicious Evils of PHP OpenStack白皮书 新机遇:容器与OpenStack(三) 程序员不可不知的职业风险——蹲大牢 BloomFilter的原理,实现及优化 python的ggplot库