跳转至正文

为什么 iOS 会停止后台定位更新

为什么 iOS 会停止 Capacitor 应用中的后台位置更新:挂起、后台模式、授权、自动暂停、精度以及如何修复每个原因。

iPhone 应用在后台运行任务并追踪位置的示意图

iOS 停止后台位置更新是因为它挂起了你的应用,而不是因为 Core Location 失败。除非应用声明了位置后台模式,并在其 CLLocationManager中启用后台更新,且在应用仍处于前台时启动这些更新,否则 iOS 会在用户离开后几秒内冻结进程,并排队或丢弃定位数据。本指南列出了我们在 Capacitor 应用中看到的每一个原因及其修复方法,并展示了 @capgo/background-geolocation 如何处理这些问题。

iOS 如何处理进入后台的应用

当用户滑动到主屏幕或锁定手机时,iOS 会给应用一个短暂的窗口期,然后将其挂起。被挂起的应用无法获得 CPU 时间。对于 Capacitor 应用而言,这意味着原生端和 WebView 都会停止运行,因此你的 JavaScript 回调也会停止触发。

Apple 为正在积极使用位置的应用提供了例外。如果应用配置了后台位置,并且在前台启动了活跃的位置会话,iOS 会保持其运行并继续提供更新。以下内容均关于如何满足该契约,以及系统故意破坏该契约的情况。

原因 1:缺少后台定位模式

如果不使用 location 中的 UIBackgroundModes,无论使用多少 code 都无法保持更新流动。请将其添加到 ios/App/App/Info.plist:

<key>UIBackgroundModes</key>
<array>
  <string>location</string>
</array>

你也可以在 Xcode 的 Signing &amp; Capabilities > Background Modes > Location updates 中启用它。这是一个 plist 键,而非 entitlement,因此无需更改 provisioning profile。

其背后的原生属性是 allowsBackgroundLocationUpdates。将其设置为 true 如果 plist 中缺少后台模式,应用会崩溃。当你传入 Capgo 时,插件会自动为你设置该属性 backgroundMessage 改为 start(),因此发布前必须确保 plist 中包含该键。

App Review 会检查后台模式是否有正当理由。请在审核备注中说明哪个功能使用了该模式,并确保该功能对审核人员可见。

原因 2:更新是在后台启动的

Apple 的规则:如果会话是在应用处于前台时启动的,后台更新将继续进行。尝试从静默推送、后台 fetch 或 BGTaskScheduler 任务启动会话,对于标准位置更新是无效的。

从用户操作或应用恢复时开始跟踪:

import { App } from '@capacitor/app';
import { BackgroundGeolocation } from '@capgo/background-geolocation';

let tracking = false;

export async function startTracking() {
  if (tracking) return;
  tracking = true;
  await BackgroundGeolocation.start(
    {
      backgroundMessage: 'Recording your route',
      backgroundTitle: 'Route recording',
      requestPermissions: true,
      distanceFilter: 10,
      url: 'https://api.example.com/v1/points',
    },
    (location, error) => {
      if (error) return console.error(error);
      if (location) console.log(location.latitude, location.longitude);
    },
  );
}

// Re-arm when the user comes back, if the session should be active
App.addListener('appStateChange', ({ isActive }) => {
  if (isActive && shouldBeTracking()) startTracking();
});

declare function shouldBeTracking(): boolean;

原因 3: backgroundMessage 未传入

这是 Capgo 插件特有的问题,也是最常见的 bug 报告。如果 backgroundMessage 未定义,插件将仅配置位置管理器用于前台使用: allowsBackgroundLocationUpdates 保持 false ,当应用挂起时更新就会停止。请在两个平台上都传递消息。在 Android 上,它还会成为前台服务通知的文本。

原因 4:授权级别错误,或选择了“仅一次”

许多开发者认为需要“始终”权限才能在后台追踪。其实不然。Apple 文档指出,拥有“使用期间”授权的应用,只要启用了后台更新,在拥有活跃位置服务时就会继续在后台运行。

“始终”权限对另一件事很重要:重启已终止的应用。只有区域监控、显著位置变化和访问记录才能重启应用,且必须拥有“始终”权限。

两个用户选择会悄悄破坏后台追踪:

  • “仅一次”。 这是一种临时的“使用期间”授权,当应用停止使用时即过期。下一次会话将从“未确定”状态重新开始。
  • 先请求“使用期间”,然后“始终”提示从未显示。 当你请求“始终”权限时,iOS 可能会稍后显示升级提示,时机由系统决定,通常是在应用不在屏幕上时。

在启动会话前,请先检查实际状态:

import { BackgroundGeolocation } from '@capgo/background-geolocation';

const status = await BackgroundGeolocation.checkPermissions();

if (status.location !== 'granted') {
  await BackgroundGeolocation.requestPermissions({ permissions: ['location'] });
}

if (status.backgroundLocation === 'when_in_use') {
  // Background tracking still works for standard updates.
  // Explain why Always helps if you also rely on geofences.
}

在 iOS 上, backgroundLocation 返回 when_in_use 或 granted / always,这样你就能显示正确的说明,而不是靠猜。

原因 5:Core Location 自动暂停更新

CLLocationManager.pausesLocationUpdatesAutomatically 默认为 true。当 iOS 判断用户没有移动时,会暂停更新以省电。对于 When In Use 应用,暂停会中断位置访问,直到应用再次启动,而且 Core Location 不会自行恢复。

Capgo 插件会设置 pausesLocationUpdatesAutomatically = false 其原生 code 中的该项,因此这一项已经处理好了。如果你自己编写原生 code,或使用其他插件,请先检查这个属性。

原因 6:设备静止且过滤范围过大

使用 distanceFilter 50 米的过滤精度,放在桌上的手机不会产生任何更新。这在你 logs 里看起来像“追踪已停止”,但其实是按设计工作的。

  • 如果你需要在静止时也有心跳,请使用较小或为零的 distanceFilter.
  • 使用 minIntervalMs 限制原生 POST 请求的发送频率,而不是设置很大的距离过滤器。
await BackgroundGeolocation.start(
  {
    backgroundMessage: 'Vehicle tracking active',
    distanceFilter: 0,
    minIntervalMs: 120_000, // at most one native POST every 2 minutes
    url: 'https://api.example.com/v1/vehicle-points',
  },
  () => {},
);

原因 7:精确位置已关闭

自 iOS 14 起,用户可以关闭应用的“精确位置”。你仍能收到更新,但位置是近似的(误差数公里),且频率低得多。可通过数据检测到这一点(数值很大 accuracy )并向用户解释精确位置对你的功能为何重要。如果你开发的是配送或健身应用,请在 onboarding 阶段说明,而不是等行程出问题后才提示。

原因 8:用户终止了应用

如果用户在应用切换器中划掉应用,iOS 会停止标准位置更新。没有任何 plugin 能让它继续运行。对于从 Android 转过来的团队来说,这通常很令人意外,因为在 Android 上,即使任务被移除,前台服务仍可继续运行。

选项:

  • 告知用户在会话期间不要强制退出应用。大多数车队应用都会在 onboarding 中提示这一点。
  • 在重要地点周围添加带 Always 授权的地理围栏 (geofences),以便 iOS 能在用户进入或离开时在后台重新启动应用。
  • 当你的服务器停止接收某个会话的点位时,发送 push 提醒用户重新打开应用。

原因 9:原生层在运行,但 JavaScript 未运行

这个问题比较隐蔽。当 WebView 处于挂起状态,或者虽然还活着但处理定时器或网络请求不够及时时,原生 code 仍能收到位置。如果你用 fetch 来自回调的数据可能延迟到达,甚至完全不到达。

使用插件的 url 选项。每个点位都由原生 code 以与以下相同的 JSON POST: Location 类型加上 "source": "native",且独立于 WebView。回调仅用于 UI 更新。投递采用尽力而为机制且没有磁盘队列,所以如果某个点位不能丢,请在回调触发时也存到本地,之后再对账。

如需了解位置以外的后台工作,请阅读 后台任务在 Capacitor 中如何工作 并查看 @capgo/capacitor-background-task.

原因 10:低电量模式与热状态

低电量模式会降低整个系统的后台活动。定位仍然可用,但你的会话依赖的其他环节(网络重试、后台刷新)会变慢。在发布前,请务必开启低电量模式测试。

蓝色指示器

当获得“使用 App 期间”授权的 App 在后台使用定位时,iOS 会在状态栏显示蓝色胶囊。Capgo 插件会设置 showsBackgroundLocationIndicator 改为 true 对于后台会话,这是用户期望的,也是审核人员会关注的。不要试图隐藏它,用户信任它。

能够捕获这些 Bug 的测试方案

对于后台定位,仅靠模拟器测试是不够的。请使用真机 iPhone 并执行以下操作:

  1. 构建 Release 配置,而不只是 Debug。挂到 Xcode 上的 Debug 构建,生命周期处理方式不同。
  2. 在应用处于前台时启动会话,然后锁定手机,步行或驾车 20 分钟。
  3. 分别在“允许一次”、关闭“精确位置”、开启“低电量模式”的情况下重复测试。
  4. 检查你的服务器日志中的 source: "native" 数据点,而不只是应用内日志。
  5. 在会话期间强制退出应用,并确认应用在下次启动时能解释发生了什么。

如果你需要设备 UDID 用于 ad hoc 构建, iOS UDID 查询工具 从 Safari 获取。如果你没有配备 Xcode 26 的 Mac 用于构建, Capgo 构建 可以在云端生成已签名的 iOS 构建。

故障排查清单

现象 可能原因 修复
锁屏后几秒即停止 否 location 后台模式,开还是不开? backgroundMessage 添加 plist 键并传入 backgroundMessage
启动时崩溃 allowsBackgroundLocationUpdates 未开启后台模式 添加 UIBackgroundModes → location
仅生效一次,随后失效 用户选择了“允许一次” 解释原因并再次请求,或发送至 openSettings()
停车时停止 较大 distanceFilter 调低它,然后使用 minIntervalMs
坐标点间距过大,精度低 未开启“精确位置” 提示用户启用
划掉应用后停止 用户终止了应用 iOS 上属预期行为,请用地理围栏重新启动
原生端能获取数据点,服务端不能 JavaScript fetch 在回调中 使用 url 选项

发布修复

这些修复大多涉及 Info.plist 或原生配置,因此需要新的 App Store 构建。JavaScript 部分,如权限说明、onboarding 文案和重试逻辑,可以通过 Capgo 实时更新 在原生构建发布后推送给现有用户。

如果你正在比较插件,我们的 Transistorsoft 与免费 Capgo 插件对比 涵盖了其中的权衡。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。