5.7

View in English

5.7 移动应用开发

概述与动机

移动应用开发是为手机和平板电脑构建软件的学科。对许多人来说,手机现在是他们拥有的主要甚至唯一的计算机。这使移动应用成为你服务的前门,也常常是用户借以评判你整个组织的界面。

移动端是一个独特的工程环境,而不是网页或桌面的缩小版。设备运行在口袋里,靠电池供电,网络连接时有时无。屏幕很小。操作系统控制着你的应用可以做什么。存在两大主导平台(Apple 的 iOS 和 Google 的 Android),各自拥有自己的语言、设计规则和应用商店。你不能想什么时候发布更新就什么时候发布,因为商店会先进行审核,而用户自己决定何时安装。本章建立在前端工程(第 5.6 章)、用户体验基础(第 5.1 章)和无障碍性(第 5.3 章)的基础之上,并依赖于应用安全(第 4.2 章)以及 CI/CD 与交付(第 8.1 章)。

移动端在企业和政府场景中的相关性很高。企业会为自己的员工发布面向客户的应用和内部应用,后者通常通过移动设备管理(Mobile Device Management,MDM:用于配置和保护公司设备的中央软件)来管理。政府构建面向公民的应用,用于福利、医疗、身份和支付,而且必须服务所有人,包括那些使用旧设备和慢速网络的人,并遵守无障碍法律。在这两种场景下,移动端都是一项严肃、长期存在的投入,因此应当以你对待任何其他生产系统的那种严谨态度来对待它。

关键原则

  • 为设备而设计:小屏幕、电池,以及一个时有时无的网络。
  • 假设连接是间歇性的;优先支持离线工作,并在有条件时再同步。
  • 尊重每个平台自己的设计和交互约定。
  • 你不能掌控发布的时间;商店和用户才能。
  • 碎片化是常态;要支持真实存在的各种设备和操作系统版本范围。
  • 在设备上安全地存储数据,因为设备会丢失和被盗。
  • 无障碍性是一项要求,而不是锦上添花的收尾工作。
  • 为应用的整个生命周期选择构建方式,而不仅仅是为了发布当天。

建议

有意识地选择构建方式

有三种大致的方法,各自适合不同的需求。

原生开发意味着为每个平台使用其自己的工具分别编写代码:iOS 使用 Swift,Android 使用 Kotlin。你能获得最佳性能、对设备功能最全面的访问权限,以及最贴合平台的体验,代价是需要构建和维护两个代码库。

跨平台框架让一个代码库同时面向两个平台。React Native 使用 JavaScript 并渲染真正的原生组件。Flutter 使用 Dart 语言并绘制自己的控件(widget)。这些方案减少了重复的工作量,能够加快交付速度,但也带来了对框架健康状况的依赖,并可能落后于最新的平台功能。

一个渐进式 Web 应用(Progressive Web App,PWA:一个可以被安装、能够离线工作的网站)不需要应用商店,更新也是即时的,但它对某些设备功能的访问权限有限,在主屏幕上的存在感也较弱。

要根据所需的设备功能、性能要求、维护周期、你能招募到的技能,以及你需要触达的范围来做选择。一个高性能的消费级应用可能值得选择原生开发。一个以内容和表单为主、团队规模较小的应用,可能很适合跨平台方案或 PWA。

遵循平台设计规范

每个平台都发布了详尽的约定规范。Apple 提供了人机界面指南(Human Interface Guidelines),Google 提供了 Material Design。这些规范涵盖导航、手势、排版、间距和系统行为。遵循它们会让你的应用显得熟悉,从而降低用户学习它所需的精力。与它们对抗会让应用显得陌生和别扭。一个跨平台代码库仍然需要在各平台约定存在差异的地方尊重各自的约定,而不是把一个平台的外观强加给另一个平台。

为移动端的约束条件而设计

采用离线优先的方式构建:让核心任务在没有网络连接的情况下也能工作,在本地存储变更,并在网络恢复时再同步。当同一份数据在两个地方发生变更时,要认真处理冲突。要节俭地使用电池和流量:批量处理网络调用,避免持续的定位或后台工作,压缩负载,并尊重用户的省流量设置。要为碎片化做好计划,即屏幕尺寸、设备性能和操作系统版本的广泛分布。根据真实的使用数据来选定支持范围,并在普通硬件上测试,而不仅仅是旗舰机型。为小屏幕设计时要有清晰的层级结构、足够大的触控目标,以及能够适应不同尺寸和方向的内容。

规划分发、版本管理与更新

发布要经过 Apple App Store 和 Google Play,两者都有可能延迟或拒绝发布的审核流程和政策。要把审核时间纳入你的排期,并尽早阅读相关政策。由于用户自行决定何时更新,你会始终同时有多个版本在外面运行。要让你的应用与旧版客户端保持向后兼容,并对你的 API(第 2.3 章)进行版本管理,使旧版应用继续可用。要提供在必要时强制要求更新的方式,例如在某个版本不安全或不再受支持时弹出强制更新提示,并谨慎地使用它。企业也可以通过 MDM 或私有渠道,而不是公开的应用商店,来分发内部应用。

谨慎地使用推送通知和深度链接

推送通知让你能够在应用关闭时依然触达用户。要把它们用于真正的价值,尊重用户的同意和平台权限,避免噪音,因为人们会关闭那些过度使用通知的应用的通知权限。深度链接能从一个链接或一条通知直接把用户带到某个特定屏幕。要正确配置深度链接,使链接打开应用中正确的位置,并在应用未安装时优雅地回退到网页版本。

保护应用及其数据的安全

要把设备当作不可信、且可能已经丢失的对象来对待。将敏感数据存储在平台的安全存储中(iOS 钥匙串(Keychain)或 Android Keystore),绝不要存放在明文文件中。为解锁敏感操作提供生物识别认证(指纹或人脸),并以密码作为后备手段。对高价值的连接考虑使用证书锁定(certificate pinning,即检查服务器呈现的是预期的证书),并为轮换这些证书做好规划。尽量减少在设备上存储的数据,保护好密钥和凭据,并遵循应用安全(第 4.2 章)中更广泛的指导。

建立一条真正的测试和交付流水线

要在真实设备上测试,而不仅仅是在模拟器和仿真器上测试,因为硬件、传感器和性能各不相同。使用一个设备实验室或一个云端设备农场,来覆盖具有代表性的机型和操作系统版本分布。通过持续集成与交付(第 8.1 章)自动化构建、测试、签名和商店提交,包括在公开发布之前向测试人员进行的测试版分发。安全地管理签名密钥和商店凭据是这条流水线的一部分。

把无障碍性当作一项要求

要支持每个平台的无障碍功能:屏幕阅读器(iOS 上的 VoiceOver、Android 上的 TalkBack)、动态文本大小调整、足够的颜色对比度,以及较大的触控目标。为控件加上标签,使辅助技术能够描述它们。要用真实的辅助工具进行测试,而不仅仅依赖自动化检查。对政府而言尤其如此,无障碍性是一项法律强制要求,具体细节在无障碍性(第 5.3 章)中展开。

权衡:优点与缺点

方案优点缺点
原生(Swift、Kotlin)最佳性能、完整的设备访问权限、真正贴合平台的体验两个代码库、成本更高、需要更多人手
React Native单一 JavaScript 代码库、真正的原生组件、迭代速度快依赖框架、桥接复杂性、功能滞后
Flutter单一代码库、界面一致、性能强劲Dart 技能较为少见、应用体积更大、自有的控件模型
渐进式 Web 应用无需应用商店、即时更新、单一 Web 代码库设备功能受限、存在感较弱、受平台限制
强制更新能快速淘汰不安全的旧版本使用过度会惹恼用户;可能阻断访问
证书锁定对中间人拦截提供强有力的防护若证书轮换而应用未更新,会导致连接失败

这里反复出现的权衡是触达范围与交付速度,相对于深度与保真度之间的取舍。原生开发能带来最丰富、最贴合平台的体验,但构建和维护成本最高。跨平台和 PWA 方案节省了工作量、扩大了触达范围,但在平台体验或设备访问能力上会有所牺牲。对于一个交付表单和内容为主的小团队来说,共享一个代码库通常是明智的。对于一个要求苛刻的消费级应用,原生的深度体验可能值得这个代价。要着眼于应用的整个生命周期来做决定,而不仅仅是发布当天。

与团队讨论的问题

  1. 我们支持外部已安装的旧版客户端多长时间,我们的 API 是否已做版本管理以保持它们可用? 由于用户自行决定何时更新,你总是会同时有多个版本的应用在外面运行,而一个假设所有人都是最新版本的后端变更,会破坏那一长串使用旧版客户端的用户。要确定你的向后兼容窗口,对你的 API 进行版本管理以保持旧版应用可用,并为那些确实不安全的版本保留一条很少使用的强制更新路径。这对政府的公民应用和企业的员工应用同样重要,因为使用旧设备的人可能无法、或者不愿按你的时间表升级。请带来你当前的版本分布数据,并追问:对于仍在实际使用中的最旧客户端,会有什么功能被破坏。如果你不知道这个分布情况,就应在发布下一个破坏性变更之前先对其进行埋点统计。

  2. 我们发送推送通知的门槛是什么,谁来决定什么值得打断用户? 推送通知能够在应用关闭时触达用户,这使它既强大又容易被滥用,用户会对过度使用通知的产品关闭通知(或直接删除应用)。要就什么算作真正的价值、用户如何控制频率和渠道,以及你如何尊重平台的同意机制而不是死缠烂打地索要权限达成一致。如果没有一个共同的门槛,每个有指标要完成的团队都会伸手去用推送,整个渠道就会退化成噪音。请带来你上个月发送的通知,并追问用户会为哪些通知感谢你。如果大多数都是促销性质的,就应在退订率替你收紧政策之前先收紧它。

  3. 我们的移动交付流水线是否真实存在,涵盖签名、设备农场和测试版分发,还是发布仍是一场令人紧张的人工冲刺? 移动端带来了网页所没有的风险:商店审核可能延迟或拒绝一次发布,签名密钥和商店凭据必须被妥善处理,硬件和传感器的差异大到模拟器会掩盖真实问题。通过 CI/CD 自动化构建、测试、签名和商店提交,配合向测试人员进行的测试版分发,以及一个覆盖你用户实际使用机型的云端设备农场,正是把发布从一场英雄式的救火行动变成常规工作的关键。要决定谁拥有这条流水线和签名密钥,以及商店审核时间如何被纳入每一个发布计划。请带来你上一次发布的经历,数一数其中的人工步骤。每一个人工步骤,都是在截止日期压力下一次紧张的发布可能出错的地方。

  4. 我们是否已经为这个产品的整个生命周期,而不仅仅是发布当天,选定了原生、跨平台还是渐进式 Web 应用? 构建方式的选择,是多年来对移动应用成本和能力影响最大的单一杠杆,一个为了快速发布而做出的选择可能会成为陷阱:原生开发以两个代码库和两套技能为代价,换来最丰富的设备访问能力和平台体验;而跨平台和 PWA 共享代码,但带来了对框架的依赖,或者失去了对某些设备功能的访问权限。对于一个大团队而言,这个决定会驱动招聘、维护预算,以及你能多快采用每年发布的操作系统新版本,因此它应该有一个明确的负责人,而不是由写出第一个原型的人随手定下的默认选择。请带来所需的设备功能、性能要求、维护周期,以及你实际能招募到的技能,并诚实地说明在每种选项下你会放弃哪些平台功能。在企业和政府场景中,要权衡这个应用是否是一项必须经受住人员流动和十年平台变迁考验的长期投入,并记录这个决定及其理由,使未来的团队不必去猜测代码库为何是这个样子。

  5. 我们真实用户需要的设备和操作系统版本支持范围是什么,我们测试用的是他们实际使用的硬件,而不是团队桌上的手机吗? 碎片化是移动端的正常状态:用户所使用的屏幕尺寸、设备性能和操作系统版本分布极广,一个只在团队的旗舰机型上调优过的应用,在你大部分受众所拥有的普通硬件上,会运行迟缓甚至崩溃。设定一个支持范围,是在触达范围和工作量之间做的权衡,因为你承诺支持的每一个更旧的机型和操作系统版本,都会扩大测试矩阵和维护负担,因此这个范围必须来自真实的使用数据,而不是假设。请带来你的设备和操作系统版本分布数据、一个云端设备农场或实验室目前覆盖的机型,以及你在低端硬件(而不仅仅是模拟器)上实测的性能。对政府的公民应用而言,这几乎是不可协商的,因为你必须在无障碍义务下服务所有人,包括那些使用旧设备和慢速网络的人;对企业车队而言,你应该测试员工实际携带的那些坚固耐用机型,而不是一个通用样本。

  6. 设备上存有哪些敏感数据,每一项数据是否都防范了手机丢失、被盗或落入他人之手的情况? 移动设备装在口袋里到处走,会丢失或被盗,因此任何存储在明文文件中的数据或密钥,只需一部放错地方的手机就会暴露,而且随着用户数量的增加,其影响范围也会扩大。这里的考量彼此对立:在设备上缓存数据正是让离线优先得以运作、并保持应用快速响应的原因,然而每一项被缓存的内容都是一项负债,必须存放在平台的安全存储中(iOS 钥匙串或 Android Keystore),被最小化,并且最好由生物识别或密码来把关。请带来一份清单,列出应用在本地究竟持久化保存了什么、每一项存放在哪里、由什么来解锁它,以及高价值的连接是否使用了带有可行轮换计划的证书锁定。在企业场景中,要把这一点与移动设备管理策略和远程擦除关联起来;在政府场景中,要把设备上的个人数据当作一项必须被证明合理、被记录在案、并能在审计中站得住脚的隐私和法律风险来对待。

行业视角

初创企业。 团队规模很小、资金跑道有限,你很少能负担得起两个原生代码库或两套技能,因此一个跨平台框架、甚至一个能用单一代码库同时触达两个应用商店的 PWA,通常是更好的选择。为那唯一重要的核心任务提供离线优先的支持,把任何令牌都存放在安全存储中而不是明文文件里,并把商店审核时间纳入每一次发布计划,这样一次被拒不会毁掉一个发布日期。在真实使用量证明有必要之前,跳过强制更新、证书锁定和设备农场。

小型企业。 没有专职的移动端专家,预算也很紧张,应大力倾向于购买而非自建:一个无代码应用构建工具、一个来自你的收银或预订供应商的白标应用,或者一个基于你现有网站打造得体的 PWA,往往胜过一个你无法维护的定制应用。如果你确实委托开发一个应用,要自己掌握签名密钥和商店账号,这样承包商就无法拿你的产品存在感作为筹码,并且要在合同中坚持要求无障碍性和安全的设备端存储。把范围控制在客户实际会在手机上完成的一两项任务上。

企业。 在规模化的场景下,这个应用是跨越众多团队的长期投入,因此应当把构建方式、安全存储模式、CI/CD 流水线和 API 版本管理策略标准化,而不是让每个产品各自重新发明。内部员工应用通常通过移动设备管理来完成安装、配置、远程擦除和策略执行,而面向客户的应用则需要一个覆盖真实使用情况的设备农场,以及经过审计的无障碍性和安全性。要集中治理签名密钥、商店凭据和发布时间,使一次破坏性的后端变更永远不会让使用旧版客户端的那一长串用户陷入困境。

政府。 采购规则、透明度和公共问责制塑造着每一个选择。你必须服务所有人,包括那些使用旧设备和慢速网络的人,因此无障碍性是一项需要用真实辅助工具验证的法律强制要求,一个广泛的设备支持范围也几乎是不可协商的。要倾向于那些能避免供应商锁定、保持数据可移植、并让公众能够查验应用如何处理其数据的方案和合同,并把设备端的个人数据当作一项必须在审计中说明理由、记录在案的风险来对待。

示例

初创企业。 一家开发习惯养成应用的三人初创企业,必须同时覆盖 iOS 和 Android,但负担不起两个原生代码库或两套技能。他们选择了一个跨平台框架,使一个小团队能够同时发布到两个应用商店,并从一开始就采用离线优先的设计,使用户即使在地铁里没有信号也能记录一个习惯,之后再同步。他们把登录令牌存放在平台的安全存储中,而不是明文文件里,把商店审核时间纳入每一个发布计划,并在自己的手机之外,还在几部便宜的旧手机上进行测试,这让他们发现了原本会被直接发布出去的性能迟缓问题。

企业。 一家物流公司为其司机和仓库员工构建了一个内部应用。由于仓库和配送路线的信号时有时无,团队选择了离线优先的设计:扫描和状态更新先保存在本地,等连接恢复后再同步。他们使用一个跨平台框架,用一个小团队服务两个平台的单一代码库。这个应用通过移动设备管理来分发,而不是通过公开的应用商店,因此 IT 部门能够控制公司设备上的安装、配置和安全策略。敏感凭据存放在平台的安全存储中,生物识别用于解锁应用。一个云端设备农场对员工实际携带的那些坚固耐用机型进行具有代表性的测试。

政府。 一个国家机构发布了一个面向公民的身份与福利应用。从第一天起,无障碍性就是一项硬性要求:完整的屏幕阅读器支持、动态文本大小调整和高对比度,并用真实的辅助工具进行测试以满足法律要求。由于公民使用的设备种类极其广泛,团队支持了范围很宽的旧机型和慢速连接,并让核心任务保持离线可用。敏感数据保存在安全的设备存储中,生物识别保护访问权限,高价值连接使用带有计划轮换流程的证书锁定。API 版本管理让已安装的旧版应用继续可用,并为安全修复保留了一条很少使用的强制更新路径。商店审核时间被纳入每一个发布计划。

商业案例:动机、投资回报率与总体拥有成本

移动端是许多用户接触你服务的地方,因此这个应用会影响采用率、满意度,以及对你组织而言重要的那些任务的完成情况。一个快速、可靠、设计良好的应用会提升使用率并降低支持负担。对企业而言,一个内部移动应用能让移动办公的员工队伍在可衡量的程度上更有生产力,并减少纸面工作。对政府而言,一个可用的公民应用能拓宽服务覆盖范围,并减少呼叫中心和线下办理的需求。

在总体拥有成本(TCO)方面,构建方式的选择是最大的杠杆。原生开发意味着要在应用的整个生命周期中为两个代码库和两套技能付费。跨平台方案用一部分这种成本换来了一项你必须持续保持更新的依赖。除代码之外,还要为商店费用和审核周期、一个设备测试实验室或云端农场、随着平台每年发布新版本而需要的持续操作系统版本支持,以及移动端所要求的安全工作做预算。投入不足所带来的代价,会以不受支持设备上的崩溃、未受保护的设备端数据引发的安全事件、被拒绝或延迟的发布,以及放弃使用一个缓慢或别扭的应用的用户等形式显现出来。

要向领导层论证这一点,应把这个应用与具体成果关联起来:任务完成率、留存率、员工生产力,或支持成本的降低。要为整个应用生命周期而不仅仅是第一次发布,为整体的构建方式决策定价,并指出一项严肃的移动端实践能够降低的那些风险(安全、无障碍法律、商店拒绝)。

反模式与陷阱

  • 把移动端当作一个缩小版网站来对待: 忽视触控、手势和平台约定。
  • 假设网络永远完美: 没有离线处理,一旦信号中断应用就会崩溃。
  • 只在最新旗舰机型上测试: 掩盖了在真实用户所使用设备上的糟糕性能。
  • 把密钥存放在明文文件中: 一旦设备丢失或被盗,敏感数据便会暴露。
  • 通知过载: 推送过多,导致用户静音或删除应用。
  • 忽视商店审核时间: 发布计划假设能够即时发布,结果不断延误。
  • 没有强制更新路径: 不安全的旧版本长期存在,无法被淘汰。
  • 耗尽电池和流量: 持续的后台工作和多话的网络行为让用户察觉到异常。
  • 把无障碍性当作事后补救: 排斥了部分用户,对政府而言还违反了法律。
  • 强迫单一代码库在所有地方看起来一模一样: 导致应用在两个平台上都显得格格不入。

成熟度模型

第 1 级:启动(Initiate)。 移动端工作是临时性、被动式的。应用被当作网站来构建,只在团队自己的手机上测试,离线时经常崩溃。对安全存储、无障碍性或商店审核时间几乎没有考虑。发布是一场令人紧张的人工冲刺,没有人负责构建方式或签名密钥。

第 2 级:发展(Develop)。 基本实践开始出现,但在各团队和产品之间并不一致。为特定应用选定了一种构建方式,遵循了平台的基本规范,并在少数几台真实设备上进行了测试,也存在一些离线处理和安全存储机制。构建过程部分自动化,有人负责商店提交,但另一个团队的应用仍可能完全不同地处理这一切,甚至根本不处理。

第 3 级:标准化(Standardize)。 良好实践已被记录下来,并在整个组织范围内强制执行。离线优先是默认做法,一个已记录的设备支持范围在设备实验室或云端农场上进行测试,平台设计规范和无障碍性得到遵循,并用真实的辅助工具进行了验证。安全存储、生物识别和 API 版本管理是标准做法,CI/CD 自动化了构建、测试、签名和测试版分发,商店审核时间被纳入每一次发布计划。

第 4 级:管理(Manage)。 移动端质量被对照基线进行度量和控制。崩溃率、冷启动和屏幕渲染性能、电池和流量使用,以及任务完成率被持续从真实设备上采集,并对照目标进行跟踪,还按机型和操作系统版本进行细分,使低端硬件上的回归能够被及时发现,而不是被直接发布出去。无障碍性和安全性经过审计,而不是被想当然地假设,通知退订率和更新采纳率被监控,支持范围和构建方式根据这些证据被审查。一个发布是否应当被叫停或修复,取决于这些指标,而不是应用在负责人手机上感觉如何。

第 5 级:编排(Orchestrate)。 移动端在整个组织中被持续改进和整合,并随着设备格局的变化而调整。证书轮换、强制更新路径和回滚都是常规操作,支持范围和构建方式随着平台每年发布新版本而根据证据被重新界定,用户和设备的全部分布范围被作为一等公民来对待。移动端规划与安全、无障碍性、API 和交付实践相互衔接,因此一次操作系统变更、一个新的设备档次,或一次策略调整,都被作为常规工作来吸收,而不是当作紧急事件。

讨论思路

  • 对于某个给定的产品,你如何在原生、跨平台和渐进式 Web 应用之间做选择?
  • 什么样的设备和操作系统版本支持范围适合你真实的用户数据,你如何让它保持最新?
  • 在你的应用中,哪些地方必须做到离线优先?你将如何处理同步冲突?
  • 什么时候强制更新才是合理的?你如何避免不公平地阻挡用户?
  • 你将如何在能够反映你用户实际情况的规模上进行真机测试?
  • 设备上存有哪些敏感数据?每一项数据是如何被保护的?
  • 你如何在一个共享代码库中尊重每个平台各自的约定?

关键要点

  • 为应用的整个生命周期选择构建方式(原生、跨平台或 PWA)。
  • 遵循平台设计规范,使应用显得熟悉,从而降低用户的使用成本。
  • 为移动端的约束条件而设计:离线优先、节俭使用电池和流量、应对碎片化、适配小屏幕。
  • 你无法掌控发布时间;要为商店审核、版本管理和强制更新做好规划。
  • 克制地使用推送通知和深度链接,并尊重用户同意。
  • 用安全存储、生物识别,以及在必要时的证书锁定,来保护设备端数据。
  • 在真实设备上测试,并通过 CI/CD 自动化移动端流水线。
  • 把无障碍性当作一项要求,对政府而言,这是一项法律强制要求。

参考文献与延伸阅读

  • Apple, Human Interface Guidelines
  • Google, Material Design guidelines
  • Apple, App Store Review Guidelines
  • Google, Google Play developer policies and Android developer documentation
  • OWASP, Mobile Application Security Verification Standard (MASVS) and Mobile Security Testing Guide
  • React Native project documentation
  • Flutter project documentation
  • Google, web.dev guidance on progressive web apps
  • U.S. Section 508 and WCAG (Web Content Accessibility Guidelines) references for mobile accessibility
  • NIST, Guidelines on mobile device security and management