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_ACCURACY
或 PRIORITY_BALANCED_POWER_ACCURACY
。
setInterval()
方法中指定的时间间隔取决于具体用例:在实时用例中,应将该值设置为几秒;在其他用例中,将该值限制为几分钟(建议设置为大约两分钟或更久,以尽量减少电池用量)。
获知设备的位置信息
示例:天气应用想获知设备的位置。
使用 getLastLocation()
方法,该方法将会返回最近的可用位置信息(在极少数情况下可能为 null)。此方法提供了一种获取位置信息的简单方法,并且不会产生主动请求位置信息更新的相关消耗。结合使用 isLocationAvailable()
方法,该方法在 getLastLocation()
返回的位置信息合理更新时返回 true
。
在用户位于特定位置时启动更新
示例:在用户距公司、家或其他位置一定范围内时请求更新。
结合使用地理围栏更新和一体化位置信息提供程序更新。在应用收到地理围栏进入触发器时请求更新,并且在应用收到地理围栏离开触发器时移除更新。这可确保只有在用户进入到定义区域时,应用才会获得更精细的位置信息更新。
此场景的典型工作流包括:在发生地理围栏进入 transition 事件时显示通知,以及在用户点按该通知时启动包含请求更新的代码的 Activity。
基于用户的 Activity 状态启动更新
示例:仅在用户开车或骑自行车时请求更新。
结合使用 Activity Recognition API 更新和一体化位置信息提供程序更新。在检测到目标 activity 时请求更新,并且在用户停止执行该 activity 时移除更新。
此用例的典型工作流包括:在检测到目标 activity 时显示通知,以及在用户点按该通知时启动包含请求更新的代码的 activity。
长期运行的后台位置信息更新与地理区域绑定
示例:用户想在设备附近有零售商时获得通知。
这是一个很好的地理围栏用例。此用例几乎肯定会涉及后台位置信息服务,因此应使用 addGeofences(GeofencingRequest, PendingIntent)
方法。
您应设置以下配置选项:
如果您在跟踪停留 transition 事件,应使用
setLoiteringDelay()
方法,传递一个大约五分钟或更小的值。使用
setNotificationResponsiveness()
,传递一个大约五分钟的值。但是,如果您的应用可管理更久的响应延迟,您可以考虑传递一个大约 10 分钟的值。
一个应用一次最多只能注册 100 个地理围栏。如果在一个用例中,应用希望跟踪较多的零售商选项,那么该应用可能需要注册更多地理围栏(城市级),并为大型地理围栏内的商店动态注册较小的地理围栏(城市内的地点)。当用户进入大型地理围栏时,可添加较小的地理围栏;当用户离开大型地理围栏时,可将较小的地理围栏移除,并重新注册新区域的地理围栏。
长期运行的后台位置信息更新(无可见应用组件)
示例:被动追踪位置的应用
尽可能使用 setPriority()
方法以及 PRIORITY_NO_POWER
选项,因为它几乎不会消耗电池电量。如果无法使用 PRIORITY_NO_POWER
,请使用 PRIORITY_BALANCED_POWER_ACCURACY
或 PRIORITY_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 的生命周期。如需了解详情,请参阅使用生命周期感知型组件处理生命周期。
设置超时
为了防止电池电量消耗,应设置一个停止位置信息更新的合理超时。通过设置超时,可确保更新不会无限期地继续,并在请求更新后未移除更新的情况下(例如由于代码错误),对应用起到保护作用。
对于一体化位置信息提供程序请求,可通过调用 setExpirationDuration()
(将接收一个表示自该方法上一次被调用后的时长 [以毫秒计] 的参数)添加超时。此外,您还可以通过调用 setExpirationTime()
(将接收一个表示自系统上一次启动后的有效期 [以毫秒计] 的参数)添加超时。
如需为地理围栏位置信息请求添加超时,请调用 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()
中指定的时间间隔不应过小。
通过实践本页中的建议,可显著提高用户设备的电池性能。如果您的应用不耗电,用户就会更愿意留下它们。