17、省电策略:定位功耗分析、自适应定位、WorkManager与定位任务调度

做定位开发,绕不开一个话题——

我见过太多App,功能没问题,一开定位手机就发烫。用户骂两句还是小事,后台被系统干掉才是真麻烦。今天这篇,咱们就把定位功耗这件事彻底聊透。

17.1 定位功耗到底耗在哪?

很多人以为定位耗电就是GPS模块在跑。其实没那么简单。

我拆解过几次功耗数据,发现定位的耗电大头其实是这三块:

  • 射频模块唤醒:GPS芯片从休眠到锁定卫星,这个过程最费电。大概需要30秒到2分钟不等,期间电流能到100mA以上。
  • CPU持续唤醒:定位回调来了,你的App得处理吧?如果处理逻辑写得烂,CPU就一直在高频跑。
  • 网络请求:很多App拿到位置后立刻上传服务器。WiFi还好,移动网络下每次上传都是一次心跳唤醒。

核心结论:定位功耗 = 定位方式 + 定位频率 + 后续处理。三者缺一不可。

我曾经接手过一个项目,定位间隔设成了1秒。产品经理说“要实时”。结果呢?用户手机半天就没电了。后来改成10秒一次,配合低功耗模式,续航直接翻倍。嗯,有时候产品需求和技术实现之间,需要有人站出来说“不”。

17.2 自适应定位:别傻傻地一直高精度

你想想看,用户走在室外开阔地,GPS信号好得很,用高精度没问题。但进了地铁、进了商场,GPS根本锁不住星,你还开着高精度模式,那不是白费电吗?

自适应定位的思路很简单:根据场景动态切换定位模式

我习惯的做法是这样:

  1. 先尝试高精度定位(GPS + 网络辅助)。
  2. 如果5秒内没拿到有效位置,降级为网络定位。
  3. 网络定位拿到位置后,每隔30秒再尝试一次高精度。
  4. 如果连续3次高精度都失败,就彻底切到网络定位,直到检测到环境变化(比如WiFi列表变了)。
// 自适应定位的核心逻辑
fun adaptiveLocationRequest(context: Context) {
    val locationRequest = LocationRequest.Builder(
        Priority.PRIORITY_HIGH_ACCURACY,  // 先高精度
        10000L  // 10秒间隔
    ).apply {
        setMinUpdateIntervalMillis(5000L)
        setMaxUpdateDelayMillis(30000L)
    }.build()

    // 监听定位结果
    val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            val location = result.lastLocation
            if (location != null && location.accuracy < 50f) {
                // 精度够好,保持高精度
                return
            }
            // 精度不够,降级
            downgradeToNetworkProvider()
        }
    }
}

我的经验:自适应定位的关键不是切换本身,而是切换的阈值。精度阈值设得太严(比如10米),用户大部分时间都在降级模式;设得太松(比如100米),高精度模式又白开了。我个人建议50米是个不错的平衡点。

17.3 WorkManager:定位任务的正确打开方式

很多开发者还在用Service做后台定位。说实话,这年头Service已经不太靠谱了。Android 8以后后台限制越来越严,到了Android 12,连前台Service都得加通知。

那怎么办?WorkManager

WorkManager是Google官方推荐的异步任务调度方案。它最大的好处是:系统会帮你决定什么时候跑

举个例子:你的App需要每隔15分钟上报一次位置。如果用AlarmManager,系统可能在你设定的时间点强制唤醒CPU。但WorkManager会等一个合适的时机——比如用户正在充电、或者网络空闲时——再执行任务。

// 使用WorkManager调度定位任务
class LocationWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        return try {
            // 获取一次位置
            val location = getCurrentLocation()
            // 上报服务器
            uploadLocation(location)
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }

    private fun getCurrentLocation(): Location {
        // 这里用FusedLocationProviderClient获取一次位置
        // 注意:不要持续监听,只取一次
        val fusedClient = LocationServices.getFusedLocationProviderClient(applicationContext)
        // ... 同步获取位置
    }
}

// 调度任务:每15分钟执行一次
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)  // 有网才执行
    .setRequiresBatteryNotLow(true)                 // 电量不低于低电量阈值
    .build()

val request = PeriodicWorkRequestBuilder<LocationWorker>(
    15, TimeUnit.MINUTES
).setConstraints(constraints).build()

WorkManager.getInstance(context).enqueue(request)

注意:WorkManager的最小间隔是15分钟。如果你需要更频繁的定位,那就别用WorkManager了,老老实实用前台Service吧。但说实话,15分钟一次对大多数场景已经足够了。

17.4 定位任务调度:别让定位成为电老虎

调度定位任务,说白了就是回答三个问题:

  • 什么时候定位?——用户正在使用App时?还是后台静默采集?
  • 定位多久?——持续定位还是单次定位?
  • 定位完做什么?——立即上报?还是缓存一批再上报?

我见过最离谱的做法:后台Service每5秒定位一次,每次定位完立刻上传服务器。结果一个月下来,用户的流量费比App本身还贵。

正确的做法应该是:

  1. 批量上报:把多次定位结果缓存起来,凑够10条或者间隔5分钟再统一上报。
  2. 条件触发:只有在WiFi环境下才上传,移动网络下只缓存。
  3. 电量感知:电量低于20%时,降低定位频率;低于10%时,停止定位。
// 电量感知的定位调度
class BatteryAwareLocationScheduler(private val context: Context) {

    fun scheduleLocationTask() {
        val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)

        val interval = when {
            batteryLevel > 50 -> 60_000L   // 电量充足,1分钟一次
            batteryLevel > 20 -> 300_000L  // 电量一般,5分钟一次
            else -> 600_000L               // 电量低,10分钟一次
        }

        // 使用Handler或WorkManager调度
        scheduleWithInterval(interval)
    }
}

17.5 一张图看懂省电策略

说了这么多,咱们用一张图把整个知识体系串起来:

Android 定位省电策略知识体系 定位功耗分析 自适应定位 WorkManager调度 定位任务调度 高精度 → 网络定位 动态切换 精度阈值:50米为平衡点 最小间隔15分钟 约束条件:网络、电量、充电状态 批量上报:缓存10条或5分钟 电量感知:动态调整频率 核心原则:能不定位就不定位,能少定位就少定位

17.6 避坑指南

最后分享几个我踩过的坑:

  • 别用Timer做定时定位:Timer不会休眠,手机待机时它还在跑。用WorkManager或者AlarmManager的setExactAndAllowWhileIdle。
  • 定位回调里别做耗时操作:我见过有人在onLocationResult里直接写数据库、发网络请求。结果定位回调阻塞,GPS芯片一直不释放,电耗飙升。
  • 别忘了注销监听:Activity销毁时没注销LocationCallback?嗯,那你的App就成电老虎了。我曾经排查过一个线上问题,用户反馈手机发烫,最后发现就是定位监听没注销。
  • 慎用PRIORITY_NO_POWER:这个模式说是“不耗电”,但它的定位结果完全依赖其他App的定位请求。如果系统里没有其他App在定位,你就永远拿不到位置。说白了,这是个“蹭车”模式,蹭不蹭得到全看运气。

我的习惯:每次上线前,我都会用Battery Historian跑一遍功耗分析。如果定位相关的wakelock占比超过5%,我就知道代码还有优化空间。这个工具Google官方就有,强烈推荐大家用起来。

省电这件事,说到底就是尊重用户的电量。你想想看,用户装你的App是为了方便,不是为了给手机找个电老虎。把定位频率降下来、把上报策略优化好、把WorkManager用起来,用户的手机续航会感谢你的。


公众号:蓝海资料掘金营,微信deep3321