2025年12月15日·阅读约1分钟

面向移动电池寿命的 API 设计:减少唠叨式请求

针对移动设备电池寿命的 API 设计:学习批量请求、缓存头与裁剪有效载荷,减少无线电唤醒、加速界面并降低电量消耗。

面向移动电池寿命的 API 设计:减少唠叨式请求

为什么“唠叨式”API 会耗电

所谓“唠叨式”API 会让一个应用为构建单个屏幕而发出许多小请求。在纸面上每个请求看起来都很便宜,但在手机上它们累加得很快。

最大的电量开销往往来自网络无线电。蜂窝和 Wi‑Fi 芯片为了收发数据会切换到高功耗状态。当应用在短时间内发出很多请求时,无线电会不断唤醒并保持激活。即便每个响应很小,反复唤醒也会消耗真实的电能。

CPU 也会有额外工作。每个请求都意味着构建头部、做 TLS、解析 JSON、更新缓存以及运行合并结果的应用代码。如果连接中断,准备工作会重复进行。

唠叨式还会让 UI 感觉更慢。屏幕不再是一致的加载流程,而是等待一连串调用完成。你会看到更长的加载等待、零散渲染造成的跳动,以及在网络差时更多的超时。后台刷新也会以相同方式恶化:更多重试、更多唤醒、更多电量消耗。

一个实用的思路是:用更少的往返、更少的字节和更少的后台工作,展示相同的 UI,这就是“针对移动电池寿命的 API 设计”。

你可以用几个具体指标来追踪成效:

  • 每次屏幕加载的 API 调用更少
  • 每次屏幕下载的字节更少
  • 在蜂窝网络上的中位和最差可交互时间更短
  • 为同样结果的后台抓取和重试更少

当这些指标改善时,响应性和电池寿命通常会共同提升。

在改动之前先测量什么

在批量化请求或调整缓存之前,先弄清楚应用当前的行为。最快的改进通常来自修复少数重复犯错的地方,但只有通过测量真实屏幕与流程(而不是一次“顺利路径”的测试)你才能发现它们。

对少数高流量屏幕记录基础数据:每次加载发生多少请求、调用了哪些端点、发送与接收的字节数(头部加上正文)、重试与超时率,以及 UI 何时可用。如果可以,将错误率按网络类型(Wi‑Fi 与蜂窝)拆分。这类拆分常常会揭示在稳定连接下看不出的隐患。

把前台流量与后台流量分开。一块看起来安静的屏幕,手机可能在后台忙于刷新、推送触发的同步、分析数据上传或“以防万一”的预取。分别跟踪这些流量,避免去优化错误的对象。

热点通常集中在几个时刻:应用启动、主页/信息流、展开为多个调用的详情页,以及下拉刷新。挑一个来做端到端测量。

设定基线预算以便团队达成共识。例如:

“订单跟踪的冷启动不应在显示状态前超过 6 次请求且下载不超过 250 KB。”

如果想要一个简单的 KPI 配对来开始,使用(1)每次屏幕加载的请求数 和(2)重试率。减少重试往往比预期带来更多电量节省,因为重复重试会让无线电更长时间保持激活。

逐步减少请求:批量化

唠叨式 API 很容易无意识地产生。每个控件都去加载“它自己的”数据,一个屏幕可能触发几十个小调用。解决办法通常并不复杂:识别哪些数据总是一起加载,把它们合并到更少的调用中返回。

从绘制一个高流量屏幕的调用图开始(首页、收件箱、订单列表)。写下首屏所需内容,然后把每个 UI 元素追溯到驱动它的请求。你常常会发现重复(同一个用户资料被拉取多次)和总是同时发生的调用(例如 profile、permissions、未读计数)。

接下来,将那些可靠一起发生的调用分组。通常有两种选择:

  • 为该屏幕创建专用端点(通常最稳定)
  • 添加一个批量端点,接受一份小而可预测的资源列表

把批量保持在以屏幕为单位并设限,这样便于缓存、监控和调试。

一些规则能避免批量变得混乱。只返回屏幕当前需要的内容,不要“以防万一”返回完整对象。如果某些部分是可选的,要显式表明,这样 UI 能先渲染重要部分。还要设计响应以便局部失败不会强制进行整批重试。重试失败子项比重发整个批量便宜得多。

节省电量的缓存头(不只是省带宽)

缓存其实是一个省电功能。每个请求都会唤醒无线电、占用 CPU 并触发解析与应用逻辑。好的缓存头能把很多刷新变成轻量级的检查。

最大的收益来自条件请求。应用不再重新下载相同数据,而是问“这有变更吗?”,如果没有,服务端返回一个很小的 304 Not Modified

在代表资源版本的响应上使用 ETag,客户端下次拉取时发送 If-None-Match。如果生成 ETag 有困难,使用 Last-Modified 并配合 If-Modified-Since 对于基于“更新时间”的资源也是不错的选择。

对很少变更的数据要明确缓存决策。Cache-Control 应与现实相符。用户资料可以设置较短的 max-age,而应用配置、参考列表和特性开关通常可以更长一些。

在客户端,把 304 当作快速通道处理。不要解析 JSON(因为没有正文),不要重建模型,也不要让 UI 闪烁。保留屏幕上已有内容,只更新必要的指示器。

举例:订单跟踪页面每 15 秒轮询一次订单状态。如果响应包含 ETag,大多数检查都可以返回 304。应用保留上一次状态,仅在状态变更时(例如从“已打包”到“已发货”)才做真实的更新工作。

对敏感数据要有意图地处理。缓存并非天然不安全,但需要明确规则。除非产品需求允许,否则避免缓存带有个人明细或令牌的响应。对用户特定数据使用较短的生命周期,并防止共享缓存存储私有响应(必要时使用 Cache-Control: private)。

更聪明的载荷:少发送、少解析

面向移动预算构建
为每个屏幕设置请求预算,并在蜂窝网络上按预算设计端点。
开始使用

每额外的字节在移动端的成本往往不仅仅是带宽。它会让无线电保持更久、并让应用消耗 CPU 来解码 JSON 并更新模型。如果你的目标是减少 API 的“唠叨”,裁剪有效载荷通常是最快的收益点。

从逐屏审计载荷开始。选一个屏幕(比如首页),记录响应大小,并逐字段审查:客户端是否渲染此字段,或仅用来决定展示逻辑?如果不使用,就从端点中移除。

对于列表,通常只需要一个小的“摘要”形状。一行列表常见需要 id、标题、状态与时间戳,而不需要完整描述、长注释或深层嵌套对象。只有当用户打开某项时再拉取详情。

一些常见改动能迅速缩小载荷:

  • 用 id 或短枚举码替代重复的长字符串
  • 不要重复发送客户端可安全假定的默认值
  • 避免在每个列表项中重复相同的嵌套对象
  • 保持字段名与类型稳定,减少客户端分支与重复解析

压缩在慢网下有帮助,但要在真实设备上测试 CPU 的权衡。Gzip 或 Brotli 常常能大幅压缩传输,但较旧手机在解压非常大响应时可能会花费可观时间。最好的收益仍然是从源头上少传数据。

把响应形状当作契约。字段名与类型保持一致时,应用需要的兜底逻辑更少、防御性代码更少,这有助于 UI 平滑并降低电量消耗。

为糟糕网络与更少重试设计

移动应用不仅在发送请求时消耗电量,也在失败后重试、再次唤醒无线电并重复工作时消耗电量。如果 API 假设总是有良好的 Wi‑Fi,真实的蜂窝用户就会为此买单。

让请求少量多次变得可行。优先服务器端过滤与分页,而不是“把所有东西下载到手机再过滤”。如果某屏幕只需要某用户的最近 20 条事件,就支持精确查询,这样应用就不会拉取数百行再丢掉大部分。

支持增量同步,使得客户端可以问“自我上次检查后有什么变化?”。这可以简单到返回时间戳或递增的版本号,然后客户端只请求自那时起的更新与删除。

重试是不可避免的,所以让更新操作幂等。重试不应导致重复计费或重复提交。为创建操作使用幂等键,并采用“设置状态”的更新语义(而不是“再加一”)会有很大帮助。

发生重试时,避免重试风暴。采用带抖动的指数退避,避免数千台设备同时打击你的服务,同时也避免手机每秒钟唤醒一次。

返回清晰的错误码以帮助客户端决定下一步。401 应触发重新认证,404 通常应停止重试,409 可能需要刷新状态,429 或 503 应触发退避策略。

客户端行为如何放大 API 成本

避免在 API 上积累技术债务
随需求变化重新生成干净的源代码,避免积累难看的补丁。
生成代码

即使 API 设计良好,客户端也可能悄悄放大网络工作。在移动端,这些额外唤醒和无线电在线时间的开销常常比字节本身更耗电。

缓存很少变化的数据。头像、特性开关和参考数据(国家、状态、分类)不应该在每次访问时都拉取。给它们合理的寿命,持久化到磁盘,只在必要时刷新。如果 API 支持验证(ETag 或 Last-Modified),一次快速的重验证通常比完整下载便宜得多。

另一个常见问题是同时发起重复的并行请求。应用的两个部分同时请求同一资源(例如头部与设置页都请求 profile),如果不做合并,会发送两次调用、解析两次响应并更新两次状态。把一次网络调用视为单一的真相来源,让多个消费者等待该调用完成。

后台刷新要有意图。如果它不会很快改变用户看到的内容、或不会触发通知,通常可以推迟。避免在每次应用恢复时都运行刷新逻辑。加入短暂冷却时间并检查数据上次更新时间。

如果你使用 AppMaster 构建后端,就更容易支持这些客户端行为:围绕屏幕来塑造端点、保持响应模式一致,并以可控方式添加缓存头,让客户端大部分时间保持安静。

批量、缓存与载荷常见错误

设计一个批量屏幕端点
将屏幕所需内容合并为一个有界的响应,便于客户端缓存。
创建端点

目标是更少的无线电唤醒、更少的 CPU 工作以及更少的重试。有些模式会抵消这些节省,甚至让屏幕更慢。

什么时候批量会适得其反

批量有用,直到它变成一个“给我一切”的调用。如果服务端要做大量表连接、复杂权限检查并构建很大的响应,这单次请求可能比几个小请求更慢。在移动端,单次慢请求会让应用一直等待、让网络保持激活并增加超时风险。

更健康的批量是以屏幕为形状:只包含一个视图所需且有明确限制的内容。如果你不能用一句话描述响应,说明该端点可能太宽泛。

会导致屏幕变旧的缓存策略

没有清晰失效策略的缓存会陷入恶性循环:用户看到旧数据,下拉刷新,应用执行额外的全量重载。使用带有更新来源(创建/更新动作、服务器事件或短时鲜度窗口)计划的缓存头,让客户端可以信任缓存响应。

轮询也是电量陷阱。紧密的 5 秒定时器即便没有变化也会让设备持续忙碌。优先采用服务器驱动的更新,或当必须轮询时积极退避(在空响应后延长间隔、在后台暂停)。

过大的载荷是隐性成本。为了便利返回巨量嵌套对象,会导致更多字节、更多 JSON 解析和更多内存波动。只发送屏幕所需内容,按需拉取详情。

最后,别忽视批量中的部分失败。如果一个子结果失败而你重试整个批量,就会重复流量。设计响应以便客户端只重试失败部分。

一个快速的心智清单:保持批量有界、提前定义缓存鲜度、避免紧密轮询、裁剪载荷并支持部分成功。

发布前的快速检查清单

在发布前,做一遍只关注网络行为的检查。大多数电量节省来自消除意外行为:更少唤醒、更少解析、以及后台更少重试。

在你的前三个热度最高的屏幕上运行以下检查:

  • 冷启动在一个小且可预期的请求数内完成(注意隐藏的后续调用,如每项的额外请求)。
  • 响应包含明确的缓存规则(适用时提供 ETagLast-Modified),当没有变化时返回 304 Not Modified
  • 列表端点有界:稳定排序、分页,并且默认不返回未使用字段。
  • 重试逻辑带抖动的退避并在不会自愈的错误上停止重试;总重试时长有上限。
  • 后台更新有充分理由;除非确实改变用户可见内容,否则避免持续轮询。

一个简单的现实检查:加载一个屏幕,打开飞行模式后再打开它。如果仍能显示有用内容(缓存的内容、上次状态、友好的占位),说明你很可能已经减少了不必要的调用并提升了感知速度。

示例:让订单跟踪页面更省流量更省电

几天内证明影响
从一个高流量屏幕开始并在数日内验证请求量下降。
试用 AppMaster

顾客在蜂窝网络且电量剩 20% 的情况下打开订单跟踪应用。他们只想知道“我的包裹现在在哪里?”。界面看似简单,但背后的 API 流量可能很昂贵。

之前,应用通过一连串请求加载页面。UI 等待期间无线电多次唤醒,网络较差时有些调用超时。

典型的“之前”模式可能是:

  • GET /orders/{id} 获取订单概要
  • GET /orders/{id}/items 获取商品明细
  • GET /orders/{id}/history 获取状态事件
  • GET /me 获取用户资料与偏好
  • GET /settings 获取展示规则(货币、日期格式)

现在应用三步改造而不改变 UI。

首先,新增一个屏幕专用端点,在一个往返内返回视图需要的内容:订单概要、最新状态、近期历史与商品标题。其次,对 profile 做缓存:GET /me 返回 ETagCache-Control: private, max-age=86400,所以大多数打开会变为快速的 304(或如果缓存仍然新鲜则根本不发请求)。第三,精简载荷:商品列表只返回 idnameqtythumbnail_url,不返回完整商品描述或未用的元数据。

结果是更少的往返、更少的字节,并且在网络抖动时更少重试。手机的无线电更多时间保持休眠,这正是节省电量的关键。

对用户而言,界面没有什么“新”变化,但加载更快、更灵敏,并且在连接较差时仍能正常工作。

下一步:实用部署计划(以及 AppMaster 的帮助点)

如果想要快速见效,就从小处开始并证明影响。电量节省多半来源于减少无线电唤醒与解析工作,而不是大规模重写。

三个安全、可测且易回滚的改动:

  • 对一屏做端到端度量(请求数、总字节、可交互时间、错误与重试率)
  • 将该屏的请求批量化为一到两个调用(保留旧端点以兼容旧客户端)
  • 在最高流量的 GET 端点上添加 ETag 支持,让客户端使用 If-None-Match 并接收 304 Not Modified

挑一个使用稳定的功能(例如订单列表或消息收件箱)。如果可以,在服务端标志下发布变更,然后在几天内比较新旧路径的 KPI。关注每会话的请求数下降、重试减少和每活跃用户下载字节的减少。

协调 API 与应用发布以免旧客户端崩溃。一个实用规则是:先添加新行为、再迁移客户端、最后移除旧行为。如果你改变了缓存策略,对个性化数据要格外小心,确保共享缓存不会混淆不同用户的数据。

如果你想更快地原型并发布后端改动,AppMaster (appmaster.io) 可以帮助你可视化建模数据、用拖放编辑器构建业务逻辑,并在需求变化时重新生成可投入生产的源代码。

先对一个高流量屏幕实现一个批量端点并为其关键 GET 添加 ETag 支持。如果数据指标改善,你就能明确知道下一步该投入哪里。

常见问题

多少次 API 调用算是在移动端“过多”?

一个好的默认做法是为每个屏幕设定预算,然后基于真实会话去衡量。许多团队会以冷启动时 4–8 次请求作为起点,然后先修复最耗时的接口再逐步收紧。合适的数字是能稳定达到你的可交互时间目标、且不会触发大量重试或长时间网络活跃期的那个数字。

批量请求总是优于多个小端点吗?

当多个请求总是一起发生时,批量请求通常有帮助;但如果批量变得庞大或缓慢,反而会带来负面影响。保持批量响应“以屏幕为形状”且有界,这样单次请求不会变成单点故障。如果一个批量端点经常超时或返回大量未使用数据,最好拆分成少量更专注的调用。

哪些缓存头能带来最大的省电效果?

从电量角度看,先从 ETag 与条件请求(客户端发送 If-None-Match)开始,因为这可以把很多刷新变为很小的 304 Not Modified 响应。再配合与数据实际变更频率匹配的 Cache-Control,客户端就能避免不必要的网络工作。如果不能实现 ETag,对“有更新时会带时间戳”的资源,Last-Modified + If-Modified-Since 是稳妥的替代方案。

我应该使用 ETag 还是 Last-Modified?

当你需要可靠的版本检查时,优先使用 ETag,它对资源内容变化的检测更准确;当服务端有明确的更新时间并且对时间粒度没问题时,Last-Modified 足够。如果只能实现一种,ETag 往往更能避免不必要的下载。

在更改 API 之前我应该测量什么?

按屏幕和会话来打点,而不仅仅按端点。记录每次加载的请求数、传输字节(包含头和主体)、重试与超时率、以及可交互时间;并把前台请求与后台请求区分开来,这样你就不会去优化错误的流量。通常你会发现少数几个屏幕或流程制造了大多数重复唤醒。

如何处理批量响应中的部分失败?

让批量响应能对每个子结果独立成功或失败,并包含足够的错误信息,客户端只需重试失败的部分即可。避免因为一项子操作失败就重发整个批量,这会产生重复流量并在不稳定网络下多次唤醒无线电。

在不破坏应用的前提下,最快减少有效载荷大小的方法是什么?

把响应裁剪到当前屏幕确实渲染的字段,列表使用摘要形态。把冗长或很少使用的字段移到详情端点,只有在用户打开项时再获取。这样能在不破坏应用的情况下快速减少传输字节,并减少 JSON 解析与模型更新带来的 CPU 和电量开销。

如何调整重试逻辑以节省电量?

使用指数退避并加入抖动(jitter),并对总重试时间设上限,避免设备每隔几秒就唤醒一次。让写操作尽可能幂等(如使用幂等键),以免重试造成重复提交或双重计费。还要返回明确的状态码,让客户端在错误无法自行修复时停止重试。

如何避免因轮询更新导致的电量消耗?

紧密的轮询间隔会在没有更新时也频繁唤醒无线电和 CPU。如果必须轮询,应在连续未变更时延长间隔,并在后台暂停轮询。尽可能采用事件驱动的更新,这样应用只在有新内容时才唤醒。

AppMaster 如何帮助我更快交付这些 API 更改?

在 AppMaster 中,你可以以屏幕为导向创建端点并保持响应模式一致,这使得批量与负载裁剪更容易管理。你还可以添加版本化逻辑并返回支持条件请求的头,让客户端得到快速的“无变更”响应。一个实用方法是先对一个高流量屏幕实现批量端点和 ETag,然后衡量请求数和重试的下降。

容易上手
创造一些 惊人的东西

使用免费计划试用 AppMaster。
准备就绪后,您可以选择合适的订阅。

开始吧
面向移动电池寿命的 API 设计:减少唠叨式请求 | AppMaster