欢迎参加我们将于 6 月 3 日举行的 #Android11:Beta 版发布会

优化电池位置

Android 8.0(API 级别 26)中引入的后台位置限制使用户重新开始关注位置服务的使用如何影响电池电量消耗的问题。本页面将介绍一些位置服务最佳实践,以及您现在应如何让您的应用更省电。无论您的应用在哪个版本的平台上运行,应用这些最佳实践都会为其带来益处。

Android 8.0 中的后台位置限制引入了以下更改:

  • 限制后台位置收集并计算位置,并且每小时仅发送几次。
  • WLAN 扫描更加保守;当设备保持连接至同一静态接入点时,不会计算位置更新。
  • 地理围栏响应时间从数十秒更改为大约两分钟。这种更改可显著提升电池性能,在某些设备上最多可提升 10 倍。

本页面假设您正在使用 Google 位置信息服务 API,相较于 框架位置 API,前者的精确度更高,电池负担也更小。特别是,本页面假设您熟悉 Fused Location Provider API,该 API 结合来自 GPS、WLAN、蜂窝网络以及加速度计、陀螺仪、磁力计和其他传感器的信号。此外,您还应熟悉 Geofencing API,该 API 基于 Fused Location Provider API 构建,并针对电池性能进行了优化。

理解电池电量消耗

位置收集和电池电量消耗在以下几个方面直接相关:

  • 精确度:位置数据的精度。一般而言,精确度越高,电池电量消耗越大。
  • 频率:位置的计算频率。位置的计算频率越高,电池电量消耗越大。
  • 延迟时间:位置数据的交付速度。延迟越低,电池电量消耗越大。

精确度

您可使用 setPriority() 方法指定位置的精确度,传递以下任意值作为参数:

  • PRIORITY_HIGH_ACCURACY 提供最高精确度,计算过程使用尽可能多的输入(启用 GPS、WLAN 和蜂窝网络,并使用各种各样的传感器),会产生巨大的电池电量消耗。
  • PRIORITY_BALANCED_POWER_ACCURACY 提供准确的位置,同时进行了耗电优化。极少使用 GPS。通常使用 WLAN 和蜂窝网络信息的组合来计算设备位置。
  • PRIORITY_LOW_POWER 主要依赖移动电话基站,避免 GPS 和 WLAN 输入,从而以最小的电池电量消耗提供粗略的(城市级)精确度。
  • PRIORITY_NO_POWER 被动地从已计算出位置的其他应用接收位置。

大多数应用的定位需求可以通过 Balanced Power 或 Low Power 选项来满足。应仅针对在前台运行且要求实时位置更新的应用(例如地图应用)使用 High Accuracy 选项。

频率

您可以使用以下两种方法指定位置更新频率:

  • 使用 setinterval() 方法指定应用计算位置的时间间隔。
  • 使用 setFastestInterval() 指定其他应用已计算出的位置发送至您的应用的时间间隔。

您应使用 setInterval() 传递最大值。这对于后台位置收集而言尤其如此,因为后台位置收集通常会导致不受欢迎的电池电量消耗。应对前台用例使用几秒钟的时间间隔。Android 8.0 引入的后台位置更新限制将会执行这些策略,但您的应用也应努力在 Android 7.0 或更低版本的设备上执行它们。

延迟时间

您可使用 setMaxWaitTime() 方法指定延迟时间,传递的值通常比 setInterval() 方法中指定的时间间隔大几倍。此设置将延迟位置传递,而且多个位置更新可能分批传递。这两个变化有助于减少电池电量消耗。

如果您的应用无需即时位置更新,则您应将向 setMaxWaitTime() 方法传递最大值,通过牺牲延迟时间来获得更多数据和更高的电池效率。

当使用地理围栏时,应用应向 setNotificationResponsiveness() 方法传递一个较大值来节省耗电量。建议设置五分钟或更大的值。

位置用例

本部分将介绍一些典型的位置收集场景,以及 Geofencing API 和 Fused Location Provider API 的最佳用法建议。

用户可见或前台更新

示例:需要频繁的精确更新且延迟极低的地图应用。所有更新都发生在前台:用户启动某个 Activity,使用位置数据,然后在短时间内停止该 Activity。

使用 setPriority() 方法,并使用 PRIORITY_HIGH_ACCURACYPRIORITY_BALANCED_POWER_ACCURACY 值。

setInterval() 方法中指定的时间间隔取决于具体用例:在实时用例中,应将该值设置为几秒;在其他用例中,将该值限制为几分钟(建议设置为大约两分钟或更久,以尽量减少电池用量)。

获知设备的位置

示例:天气应用想获知设备的位置。

使用 getLastLocation() 方法,该方法将会返回最近的可用位置(在极少数情况下可能为 null)。此方法提供了一种获取位置的简单方法,并且不会产生主动请求位置更新的相关费用。结合使用 isLocationAvailable() 方法,该方法在 getLastLocation() 返回的位置合理更新时返回 true

在用户位于特定位置时启动更新

示例:在用户身处在到公司、家或其他位置的一定距离内时请求更新。

结合使用 Geofencing 更新和 Fused Location Provider 更新。在应用收到 Geofence Entrance Trigger 时请求更新,并且在应用收到 Geofence Exit Trigger 时移除更新。这可确保只有在用户进入到定义区域时,应用才会获得更细粒度的位置更新。

此场景的典型工作流包括:在地理围栏发生变换时显示通知,以及在用户点按该通知时启动包含请求更新代码的 Activity。

基于用户的 Activity 状态启动更新

示例:仅在用户开车或骑自行车时请求更新。

结合使用 Activity Recognition API 更新和 Fused Location Provider 更新。在检测到目标 Activity 时请求更新,并且在用户停止执行该 Activity 时移除更新。

适用于此用例的典型工作流包括:在检测到目标 Activity 时显示通知,以及在用户点按该通知时启动包含请求更新代码的 Activity。

长期运行的后台位置更新与地理区域绑定

示例:用户想在设备附近有零售商时获得通知。

这是一个很好的地理围栏用例。此用例几乎肯定会涉及后台位置服务,因此应使用 addGeofences(GeofencingRequest, PendingIntent) 方法。

您应设置以下配置选项:

  • 如果您正在跟踪 Dwell Transition,则应使用 setLoiteringDelay() 方法,传递一个大约五分钟或更小的值。

  • 使用 setNotificationResponsiveness(),传递一个大约五分钟的值。但是,如果您的应用能够达到更久的响应延迟,则可考虑传递一个大约 10 分钟的值。

一个应用可能一次只能最多注册 100 个地理围栏。如果在一个用例中,应用希望跟踪较多的零售商选项,那么该应用可能希望注册更多地理围栏(城市级),并且在大型地理围栏内注册较小的地理围栏(城市内的地点)。当用户进入大型地理围栏时,可添加较小的地理围栏;当用户离开大型地理围栏时,可将较小的地理围栏移除,并重新注册新区域的地理围栏。

长期运行的后台位置更新(无可见应用组件)

示例:被动追踪位置的应用

尽可能使用 setPriority() 方法以及 PRIORITY_NO_POWER 选项,因为它几乎不会消耗电池。如果无法使用 PRIORITY_NO_POWER,则使用 PRIORITY_BALANCED_POWER_ACCURACYPRIORITY_LOW_POWER,但请避免将 PRIORITY_HIGH_ACCURACY 用于持续的后台工作,因为此选项会消耗大量电量。

如果您需要更多位置数据,请使用被动定位:调用 setFastestInterval() 方法,传递一个比传递给 setInterval() 的值更小的值。当与 PRIORITY_NO_POWER 选项结合使用时,被动定位可适时地传递其他应用计算出的位置,不会产生任何费用。

使用 setMaxWaitTime() 方法,增加一个延迟时间来调节频率。例如,如果您使用 setinterval() 方法以及一个大约 10 分钟的值,则应考虑调用 setMaxWaitTime() 并传递一个 30 至 60 分钟之间的值。通过使用上述选项,您的应用将每 10 分钟左右计算一次位置,但它每 30 至 60 分钟才会被唤醒一次,对一些位置数据进行批量更新。此方法通过牺牲延迟时间来获得更多的可用数据和更好的电池性能。

在用户与其他应用交互时频繁地进行高精度更新

示例:在用户关闭屏幕或打开其他应用时继续工作的导航或健身应用。

使用前台服务。如果您的应用可能要为用户完成一些成本高昂的工作,建议最好让用户知晓这些工作。前台服务要求持久性通知。如需了解详细信息,请参阅通知概览

定位最佳做法

实现本部分中的最佳做法有助于减少应用的电池用量。

移除位置更新

造成不必要的电池电量消耗的一个常见原因是,当不再需要位置更新时,没有移除它们。例如,当某个 Activity 的 onStart()onResume() 生命周期方法中包含 requestlocationUpdates() 调用,但在 onPause()onStop() 生命周期方法中却没有相应的 removeLocationUpdates() 调用时,就会发生这种情况。

您可使用具有生命周期感知能力的组件更好地管理应用中的 Activity 的生命周期。如需了解详细信息,请参阅使用具有生命周期感知能力的组件处理生命周期

设置超时

为了防止电池电量消耗,应设置一个停止位置更新的合理超时。通过设置超时,可确保更新不会无限期地继续,并且在请求更新后未移除更新的情况下(例如由于代码错误),对应用起到保护作用。

对于 Fused Location Provider 请求,可通过调用 setExpirationDuration()(将接收一个表示自该方法上一次被调用后的时长 [以毫秒计] 的参数)添加超时。此外,您还可以通过调用 setExpirationTime()(将接收一个表示自系统上一次启动后的时长 [以毫秒计] 的参数)添加超时。

如需为 Geofence Location 请求添加超时,则调用 setExpirationDuration() 方法。

批处理请求

对于所有非前台用例,将多个请求一起进行批处理。您可使用 setInterval() 方法指定计算位置的时间间隔。然后使用 setMaxWaitTime() 方法设置位置传递给应用的时间间隔。传递给 setMaxWaitTime() 方法的值应是传递给 setInterval() 方法的值的倍数。例如,请考虑以下位置请求:

Kotlin

val request = LocationRequest()
request.setInterval(10 * 60 * 1000)
request.setMaxWaitTime(60 * 60 * 1000)

Java

LocationRequest request = new LocationRequest();
request.setInterval(10 * 60 * 1000);
request.setMaxWaitTime(60 * 60 * 1000);

在本例中,大约每十分钟计算一次位置,并且大约每小时批量传递六个左右位置数据点。虽然您仍然每十分钟左右就会获得位置更新,但您节省了电池用量,因为您的设备大约每小时才会被唤醒一次。

使用被动位置更新

在后台用例中,限制位置更新是一个好方法。Android 8.0 中的后台位置更新限制将执行此做法,但在旧版设备上运行的应用也应尽量限制后台位置更新。

可能当您的应用在后台运行时,另一个应用会频繁地请求前台位置更新。位置服务为您的应用实现了此类更新。考虑以下位置请求,它会适当地使用位置数据:

Kotlin

val request = LocationRequest()
request.setInterval(15 * 60 * 1000)
request.setFastestInterval(2 * 60 * 1000)

Java

LocationRequest request = new LocationRequest();
request.setInterval(15 * 60 * 1000);
request.setFastestInterval(2 * 60 * 1000);

在上一个例子中,您的应用大约每 15 分钟计算一次位置。如果其他应用请求位置,则您的应用可在最多两分钟后获得这些信息。

虽然被动地使用位置不会导致电池电量消耗,但当接收位置数据会触发昂贵的 CPU 或 I/O 操作时,应格外小心。为了最大限度地降低电池电量消耗,setFastestInterval() 中指定的时间间隔不应过小。

通过实践本页中的建议,可显著提高用户设备的电池性能。如果您的应用不损耗电池,用户就会更愿意留下它们。