跳过主要内容

有效的应用程序更新通知策略

Implement a robust app update notification for Capacitor & Electron. Learn UX patterns, Capgo, silent/forced updates, and CI/CD strategies.

Martin Donadieu

Martin Donadieu

内容营销

有效的应用程序更新通知策略

你在周五发布了一个热修复。到了周一,支持团队仍然在收到从未接收到更新的用户的反馈,beta测试者卡在了一个陈旧的捆绑包中,而一家企业客户则想知道他们的现场团队正在运行哪个版本。这个时候就显得很清楚了, 应用程序更新通知 不是一个弹出窗口。它是一个用于控制发布的操作系统。

在 Capacitor 和 Electron 项目中,通常的困难不是检测更新的存在。困难的是围绕它的一切:决定谁应该看到它,什么时候看到它,如果他们忽略它,什么应该发生,更新如何通过 CI/CD 流动,以及滚动后收集的遥测数据。 如果您将更新提示视为 UI 装饰,您会得到噪音的提示、脆弱的发布逻辑和困惑的用户。如果您将它们视为产品生命周期的一部分,您会得到更安全的发布和更平静的支持队列。

目录

为什么您的应用程序更新策略很重要

更新不仅影响维护,还影响用户留存

团队经常将更新视为维护任务。修复错误,提示用户,继续。这种思维方式忽略了产品的影响。

推送通知是应用程序生命周期中可以拉回用户的少数几个通道之一。数据由 Invesp的移动推送通知研究 指出推送通知可以提高应用程序参与度 高达88%并且已激活用户的留存率 接近2倍 那些不更新的用户比例。对于更新策略来说,这很重要,因为每个过时的客户端都可能是从未看到您刚刚发布的功能、修复或合规性变化的用户。

弱化的更新流程通常会同时产生三个问题:

  • 产品滞后 指新功能的发布不均匀,导致产品经理从分析数据中读取到混合信号。
  • 支持拖延 指支持人员需要要求截图、版本号和设备信息才能复制问题。
  • 安全漏洞 指旧客户端持续与已经更新的API进行通信。

实践规则: 将更新的交付视为发布管理的一部分,而不是在冲刺结束时发送一个礼貌的消息。

存储更新和实时更新解决不同的问题

App Store和Play Store更新仍然很重要。Native依赖项更改、基于政策的发布、权限更改和二进制级别修复属于此类别。但是,驱动商店更新的更新只是系统的一层,并且由于审查和用户采用超出了您的直接控制,因此它们是慢的。

对于 Capacitor 和 Electron 应用程序,实时更新覆盖了一个不同的工作范畴。它们适用于不需要重新编译二进制文件的 Web 包变化,如 JavaScript、CSS、复制、资产和特性标志。实际上,这意味着您可以分离两个发布问题:

发布问题 最佳匹配
是否此更改需要新本机二进制文件? 商店发布
是否此更改可以安全地作为 Web 包发布? 实时更新
是否用户需要在继续之前知道? 应用内通知决策
是否仅某些用户需要它现在? 通道发布

这就是为什么为客户端应用程序设计的机构应该停止设计单一“更新可用”弹出窗口的原因。专业团队需要柔和提示、静默应用路径、回滚规则、通道目标和日志,以便支持团队可以后续检查。

在信任角度上,用户对更新的态度也很重要。用户不在乎更新本身,反而在乎不可预测的中断。如果应用程序更新顺利,清晰地解释重大变化,并只在出现真正的故障或安全风险时阻止使用,人们会认为这表明了应用程序的能力。

使用Capgo实现更新检测

首要任务很简单:知道用户正在运行的版本,知道用户属于哪个频道,并决定是否需要下载更新。绝大多数DIY更新系统会混乱这些决策。将它们分开是必要的。

截图来自https://capgo.app/blog/building-a-native-mobile-app-with-nextjs-and-capacitor/

从版本意识开始

可靠的更新程序需要在运行时提供三个值:

  1. 安装的应用程序版本
  2. 分配的发布频道
  3. 当前更新状态,例如空闲、检查、可用、下载中、准备就绪、失败

如果您跳过状态模型,通知错误就会迅速出现。应用程序检查太频繁。同一提示每次启动都会显示。后台下载完成,但UI仍然显示“检查中”。

通常情况下,使用托管服务是正确的选择,因为操作工作比code片段中建议的要复杂。您需要签名的包、频道规则、回滚支持、版本历史、设备级日志和交付基础设施。 Capgo provides that for Capacitor and Electron apps through an updater plugin and hosted delivery workflow, which is why most client teams are better off using it than rebuilding the stack internally.

将更新器连接到应用启动

在应用启动时,运行一个轻量级检查。除非应用无法继续运行,否则不要阻塞首次绘制。

一个典型的模式在一个Capacitor应用中看起来像这样:

import { App } from '@capacitor/app'
// import your updater SDK here

type UpdateDecision =
  | { kind: 'none' }
  | { kind: 'soft'; version: string }
  | { kind: 'hard'; version: string }
  | { kind: 'silent'; version: string }

async function checkForUpdate(): Promise<UpdateDecision> {
  try {
    // Replace with your updater SDK call
    const result = await updater.check()

    if (!result || !result.available) {
      return { kind: 'none' }
    }

    if (result.metadata?.mandatory === true) {
      return { kind: 'hard', version: result.version }
    }

    if (result.metadata?.silent === true) {
      return { kind: 'silent', version: result.version }
    }

    return { kind: 'soft', version: result.version }
  } catch {
    return { kind: 'none' }
  }
}

App.addListener('appStateChange', async ({ isActive }) => {
  if (!isActive) return
  const decision = await checkForUpdate()
  handleUpdateDecision(decision)
})

的目的不是“是否有更新”。它是“是否有更新的__CAPGO_KEEP_1____CAPGO_KEEP_2____CAPGO_KEEP_3__,并且应用应该如何对待它”。 check() 的目的不是“是否有更新”。它是“是否有更新的__CAPGO_KEEP_1____CAPGO_KEEP_2____CAPGO_KEEP_3__,并且应用应该如何对待它”。 的目的不是“是否有更新”。它是“是否有更新的__CAPGO_KEEP_1____CAPGO_KEEP_2____CAPGO_KEEP_3__,并且应用应该如何对待它”。 的目的不是“是否有更新”。它是“是否有更新的__CAPGO_KEEP_1____CAPGO_KEEP_2____CAPGO_KEEP_3__,并且应用应该如何对待它”。 一个健康的实现还会存储最后一次成功检查时间和最后一次提示版本。这使得应用更新通知逻辑变得幂等,而不是烦人的。 __CAPGO_KEEP_0__

__CAPGO_KEEP_1__

阅读结果和 branch 早期

分支应该尽可能接近检查结果发生。不要在屏幕上散布更新规则。

以下是实际的分离方式:

  • 无更新 意味着什么也不做,记录一个正常的检查结果。
  • 轻微更新 意味着排队一个弹窗、设置徽章或轻量级的应用内提示。
  • 静默更新 意味着在后台下载并在下一次启动时激活。
  • 强制更新 意味着将应用切换到一个受控的阻塞流程。

在后续的实现中,我喜欢通过一个中央存储来暴露这个决策,以便 React、Vue 或 Ionic UI 可以一致地消费它。

如果您想了解一个 Capacitor 应用程序周围的更广泛设置,这个教程将非常有用:

保持检测层简单。策略中的聪明才智不应出现在启动 code 中。

设计有效的通知模式

大多数更新提示失败,因为团队选择了一个模式并将其用于所有内容。这样做会导致显示阻塞模态来更改复制设置,或者将关键迁移隐藏在没有人注意到的toast中。

环境已经很拥挤了。 Business of Apps 的 Airshipbenchmark摘要 报告称,美国智能手机用户每天接收 46 条推送通知,而平均推送反应和点击率仍然很低,分别为 iOS 上的 3.4% Android 上的 4.6% 一个应用程序更新通知必须赢得注意力而不耗尽用户。

一个显示三种有效移动应用程序更新通知模式的图表:横幅、模态对话框和应用程序消息。

使用仍然有效的最不打扰用户的模式

一个好的更新UI尊严地考虑了中断的成本。如果用户正在输入付款详细信息、记录病人笔记或扫描库存,模态对话框可能比您要修复的错误还要糟糕。

我通常将模式映射如下:

  • 顶部或底部横幅 用于修复小问题、低优先级改进和静默更新确认。
  • Toast 用于背景状态,例如“下次启动时更新准备就绪”,但不是用于重要决策。
  • 设置或个人资料入口点 用于用户想要控制和查看更改日志的用户。
  • 阻塞模态 只有当应用无法在旧版本上安全地继续运行时才会出现。

一个细微的横幅通常比一个戏剧性的模态更有效,因为它不会迫使用户与界面作斗争。

主要模式的快速比较

模式 适合 主要风险 实现注意事项
横幅 可选更新,低紧急性提示 容易忽略 每个版本都可以持久地拒绝
提示 背景状态变化 消失得太快 与一个可靠的设置条目配对
在应用内消息 上下文相关的功能发布 可能不会很快被看到 将其与相关的屏幕绑定
模态窗口 强制性操作 用户不满 只保留为硬门控

最重要的实现细节是 状态持久化如果用户点击“稍后”,则将该版本存储在已提供的版本中。如果他们dismiss一个横幅,不要在每次路由更改时再次显示它。如果您忘记了这一点,用户会认为应用程序已损坏,即使更新器正常工作。

对于已经使用推送作为其生命周期堆栈的一部分的团队来说,比较应用程序更新的用户体验与更广泛的消息设置是值得的。Capgo的指南 ionic和Capacitor使用Firebase的推送通知 是有用的,因为它有助于将运输问题与要求用户采取行动的应用程序表面分开。

推送通知只是故事的一部分

一个常见的错误是假设OS级别的更新徽章和商店通知会为您提供覆盖。实际上,由于设备设置、徽章权限、自动更新行为或电源节省模式,用户经常会错过这些警报。因此,即使商店生态系统正常工作时,在应用程序中仍然需要消息推送。

对于Electron来说,这更为明显。桌面用户通常期望不太突出的状态指示器,而不是在工作流中中断焦点的模态中断。一个小的“更新准备好”的芯片在壳中可能比系统对话更专业。

最佳模式是匹配更新的风险和用户当前任务的模式。其他所有内容都是戏剧。

自动化更新流程和用户选择

一旦检测和模式的UX在位,核心系统就是工作流程。在此之内,团队往往要么过度自动化并失去控制,要么欠自动化并积累支持债务。

一个图表展示了三种自动应用程序更新工作流程:静默、用户选择和强制更新。

Coderio 的应用程序维护指南 建议实用的发布节奏是 每 2 到 4 周的微更新 并且 每 3 到 6 个月的重大发布,硬更新保留用于 关键安全或稳定性问题。这就是正确的思维模型。决定应该来自发布类型,而不是开发人员的焦虑。

静默更新的低风险更改

静默更新是Capacitor应用程序中最不被利用的路径。如果您修复了样式、副本、特性标志连接或非破坏性 JavaScript 错误,那么通常没有理由中断用户。

流程很简单:

  1. App检查是否有新版本。
  2. 如果更新标记为安全的后台应用,则在后台下载。
  3. 下次启动时,应用激活新版本。
  4. 用户可能在重启后看到一个短暂的“更新成功”提示,或者什么都没有。

最后的选择取决于更新的内容。如果更新改变了可见的工作流程,则下次启动时会显示一个小型“新功能”卡片,以帮助用户定位。如果没有改变,则保持沉默是好的。

一个简单的状态处理器可能如下所示:

async function handleUpdateDecision(decision: UpdateDecision) {
  if (decision.kind === 'silent') {
    await updater.download()
    await updater.setNextBundle()
    localStorage.setItem('pendingUpdateVersion', decision.version)
    return
  }

  if (decision.kind === 'soft') {
    showBanner(decision.version)
    return
  }

  if (decision.kind === 'hard') {
    showForcedUpdateScreen(decision.version)
  }
}

可见产品变化的用户选择流程

用户选择流程适用于更新改变行为足够大,以至于人们应该选择中断。新导航、修改的引导程序、改变的审批流程或重大仪表板重设计都属于这一类别。

提示应该保持狭窄:

  • 发生了什么变化
  • 为什么它很重要
  • 如果他们现在更新会发生什么
  • 如果他们等待会发生什么

不要在对话框中写入发布说明诗。通常情况下,一句清晰的句子和两个按钮比一大堆复杂的文本更有效

我喜欢这个模式

新版本已可用。它包含更新的报告工作流程和修复了一个导出问题。现在更新或继续并稍后安装

API迁移后旧客户端会失效,请勿假装它是可选的

对于考虑到应用交付之外的治理团队来说,这个逻辑在安全运营中也同样适用。良好的自动化处理日常变化,仅在风险合理化时才会升级。这就是为什么这个安全自动化的概述对SOC团队来说有用的原因 安全自动化的概述 这也表明了更广泛的设计原则:分类事件、自动化安全路径并使人类干预成为有意图的

您还可以通过使用逻辑来加紧这个。Capgo的文章 应用更新的使用频率分段 对于狭窄的关键案例来说,强制更新

对于狭窄的关键案例来说,强制更新

强制更新是合法的。它们也容易被滥用。

在以下情况下使用硬门控:

条件 强制更新
已知暴露的安全补丁
严重破坏的稳定性问题
后端协议的破坏
小的UI细节调整
可选功能发布

实现应该明确。启动时检查已安装的版本,比较它与您的最低支持版本,仅当用户低于该阈值时才将其阻止。不要从“新版本存在”中推断“强制”。

强制更新屏幕需要三个属性:

  • 无死路. 给用户一个明确的重试路径。
  • 清晰的说明. 告诉他们为什么需要更新。
  • 离线处理. 如果网络不可用,说明也要。

什么不起作用的是一个只有一个“更新”按钮的模态窗口,失败时没有任何提示,网络信号不稳定时。 如果应用程序被阻止,恢复路径必须比正常路径更精致。

高级发布与通道和遥测

大多数更新事件并不是因为检测失败而发生的。它们发生的原因是团队在发布更新之前没有了解更新在野外的行为。

频道减少了爆炸半径

基于频道的发布是客户端应用程序中安全发布实时更新的最安全方式。相反,发布给特定的人群,如内部、QA、beta、staging、生产或甚至客户特定的流程。

这给了你一个看起来更像操作控制而不是二进制发布的发布形状。一个构建可以通过一系列人群的顺序移动,各个人群的信任可以让下一个群体看到它之前。

以下是关于更新工作流程的计划结构的商业侧面截图。

截图来自 https://capgo.app/pricing

这对通知策略也很重要。 Adapty 的推送通知最佳实践 报告指出 优化的发送时间可以增加反应率 40% 并且 高级目标可以三倍地提高反应率. 在更新系统中,这意味着通道感知的发布和版本特定的消息,而不是对整个安装基数的广泛提示。

Telemetry 告诉你用户是否实际移动了

一个专业的更新系统应该回答这些问题,而不需要工程师通过 ad hoc 日志进行挖掘:

  • 每个设备正在使用哪个捆绑包版本?
  • 更新是否下载成功?
  • 是否在下一次启动时成功应用?
  • 是否在发布后启动失败率增加?
  • 哪些用户仍然卡在过时的版本上?

这就是为什么在没有它的情况下,你只知道你发布了什么,而在有它的情况下,你知道用户采用了什么。

如果支持团队无法看到更新状态,支持团队会将产品问题升级为发布问题。

我强烈地倾向于每个设备的时间线,而不是仅仅聚合的仪表板。聚合的采用曲线是有用的,但它们不会解释为什么一个企业客户在一周后仍然在旧捆绑包上打开应用。设备级日志会。

当你可以隔离特定人群时,版本目标发布也变得更加实际。这篇指南在 向用户发送特定版本 这是企业团队通常在支持多个客户环境后需要的控制权的例子。

CI/CD 应该发布和观察,而不仅仅是构建

现代管道不应仅仅是“构建成功”。它应该:

  1. 构建捆绑包
  2. 签署并发布到正确的频道
  3. 附加发布元数据
  4. 监控采用和故障
  5. 回滚如果健康状况恶化

回滚部分是demo更新器和生产更新器之间的界限。如果捆绑包导致启动崩溃或启动死锁,团队需要快速停止爆炸半径。这种情况是为什么大多数机构选择使用管理工具而不是DIY的主要原因。交付、防护栏、可观察性和回滚不是次要功能。它们是系统。

CI/CD 集成本身不需要复杂。重要的是发布是确定性的和可追踪的。发布应该能追溯到一个提交、环境、操作者和频道。如果你不能快速回答这四个问题,事故响应会变得混乱。

常见通知问题的故障排除

The problems below show up repeatedly in Capacitor and Electron update work. Most of them come from state drift, not from the network.

提示在每次启动时都会出现。

症状: 用户会关闭应用更新通知,但每次打开应用时都会重新出现。

可能原因: 您成功检查了,但没有持久化提示状态,根据提供的版本。

解决方案: 存储用户拒绝或延迟的版本,并在显示UI之前进行比较。

function shouldPrompt(version: string): boolean {
  const dismissed = localStorage.getItem('dismissedUpdateVersion')
  return dismissed !== version
}

function dismissPrompt(version: string) {
  localStorage.setItem('dismissedUpdateVersion', version)
}

这也是团队混淆“可用”和“应该中断”的地方。它们是不同的决定。

静默更新下载但永远不会激活。

症状: 日志显示已下载一个包,但旧UI仍在加载。

可能原因: 应用程序下载了更新,但从未标记为下一次启动,或您的启动路径仍指向最后一个活动的捆绑包。

解决方法: 使激活明确并在启动时验证它。将“下载”和“活动”视为code和分析中的两个独立状态。

当您将生命周期建模为 available -> downloading -> ready -> active 而不是一个布尔值时,很多bug会消失。

检查在开发和生产环境下表现不同

症状: 更新检测在发布版本中正常工作,但在本地开发环境中不正常,或者反之亦然。

可能原因: 环境特定的配置。不同的频道名称、调试时禁用的插件或启动code包裹在错误的守卫中。

解决方法: 让环境行为可见。记录启动时的日志通道、应用版本和构建模式。不要依赖内存。

  • 开发构建 通常应该绕过实时更新检查或指向专门的测试通道。
  • 测试构建 应该像生产环境一样运行,但在隔离的发布流中。
  • 生产构建 应该永远不与内部QA流量共享通道。

用户在检查时处于离线状态

症状: 当用户在无网络连接时打开应用时,应用会显示一个断裂的更新状态。

可能原因: 检查路径假设网络成功并将失败映射到错误UI而不是中立状态。

修复: 在应用程序处于非活动状态时,优雅地降级。保持当前版本运行,记录失败的检查,并在应用程序再次激活时重试。

离线是正常的运行时条件,而不是异常情况。

对于强制更新,离线路径需要额外的关注。如果最低支持版本已经过期,应用程序可能需要保持阻塞状态。在这种情况下,请清晰地解释原因,并在网络连接恢复时提供重试操作。如果更新是可选的,永远不要因为暂时的网络丢失而惩罚用户。

所有这些情况的重复原则是简单的:分离 检测, 策略, UI,和 激活。当这些关注点合并成一个钩子或一个屏幕组件时,调试就变成了猜测。


如果您的团队正在发布 Capacitor 或 Electron 应用程序,并且需要一个受控的更新系统,具有通道,签名包传递,回滚保护和设备级观察性,请考虑使用Capacitor。 Capgo 值得评估。它适合那些希望实时更新表现得像发布基础设施而不是手工搭建的侧项目的团队。

继续阅读《有效的应用程序更新通知策略》

如果您正在使用 《有效的应用程序更新通知策略》 来规划CI/CD自动化,连接它与 Capgo CI/CD 在Capgo CI/CD中 Capgo 原生构建 在Capgo 原生构建中 Capgo 集成 在Capgo 集成中 CI/CD 集成 CI/CD 集成的实现细节在这里 GitHub 动作集成 for the implementation detail in GitHub Actions Integration.

实时更新Capacitor应用

当一个 web 层 bug 活跃时,通过 Capgo 将修复推送到应用程序,而不是等待应用商店批准几天。用户在后台接收更新,而本机更改保持在正常的审查路径中。

来自马丁的专业支持

立即开始

最新博客文章

Capgo 给您创建真正专业的移动应用所需的最佳见解。