如何在设备移动时覆盖Doze模式?后台定位轮询技术需求咨询
嘿,你已经搞定了充电时持续轮询的需求,现在要解决设备静止时停止定位轮询的问题对吧?我给你梳理几个可行的方案,都是基于你目前用的Firebase JobDispatcher来实现的:
解决方案:设备静止时停止Firebase JobDispatcher的定位轮询
一、先搞定设备静止状态的检测
判断设备是否静止有两种靠谱的方式,你可以根据需求选:
1. 基于位置位移判断
利用Fused Location Provider获取最近的位置记录,计算相邻位置的距离,如果在一段时间内(比如5分钟)位移小于某个阈值(比如10米),就判定为设备静止。
// 示例:判断设备是否静止的工具方法 fun isDeviceStill(lastLocation: Location?, currentLocation: Location?): Boolean { if (lastLocation == null || currentLocation == null) return false // 计算两个位置的直线距离(米) val distance = lastLocation.distanceTo(currentLocation) // 位移小于10米,且时间间隔超过5分钟,判定为静止 val timeDiff = currentLocation.time - lastLocation.time return distance < 10 && timeDiff > 5 * 60 * 1000 }
2. 利用Activity Recognition API(更省心)
Google的Activity Recognition API可以直接返回设备的活动状态(静止、行走、驾车等),这种方式准确率更高,还更省电。
首先请求活动状态更新:
private fun requestActivityUpdates(context: Context) { val task = ActivityRecognition.getClient(context) .requestActivityUpdates( 60 * 1000, // 每分钟更新一次状态 PendingIntent.getBroadcast( context, 0, Intent(context, ActivityRecognitionReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) ) task.addOnSuccessListener { /* 订阅成功,后续会收到状态回调 */ } }
然后写个BroadcastReceiver接收状态,把是否静止的标记存在SharedPreferences里:
class ActivityRecognitionReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { intent?.let { val activities = ActivityRecognitionResult.extractResult(it) val mostProbableActivity = activities.mostProbableActivity // 置信度超过70%且状态为静止时,标记设备静止 val isStill = mostProbableActivity.type == DetectedActivity.STILL && mostProbableActivity.confidence >= 70 // 保存标记到SharedPreferences,供JobService使用 context?.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)?.edit() ?.putBoolean("is_device_still", isStill)?.apply() } } }
二、在JobService中加入静止判断逻辑
每次Job触发时,先检查设备是否静止,如果是就直接结束任务,不执行定位:
class LocationPollJobService : JobService() { override fun onStartJob(job: JobParameters?): Boolean { // 从SharedPreferences读取当前设备是否静止的标记 val isStill = getSharedPreferences("AppPrefs", MODE_PRIVATE) .getBoolean("is_device_still", false) if (isStill) { // 设备静止,直接结束Job,不执行定位 jobFinished(job, false) return false } // 设备非静止,执行你的定位轮询逻辑 performLocationPoll() // 如果定位是异步操作,返回true,完成后记得调用jobFinished return true } override fun onStopJob(job: JobParameters?): Boolean { // 可选:取消正在进行的定位操作 return true // 返回true表示需要重新调度Job } private fun performLocationPoll() { // 这里写你的定位逻辑代码 } }
三、进阶优化:动态启停Job(更省电)
如果想进一步节省电量,可以在检测到设备静止时直接取消Job调度,当设备恢复移动时再重新调度:
// 取消已调度的后台Job fun cancelBackgroundJob(context: Context) { val dispatcher = FirebaseJobDispatcher(GooglePlayDriver(context)) dispatcher.cancel(backgroundJobKey) } // 重新调度后台Job(和你原来的调度逻辑一致) fun scheduleBackgroundJob(context: Context) { val dispatcher = FirebaseJobDispatcher(GooglePlayDriver(context)) val job = dispatcher.newJobBuilder() .setService(LocationPollJobService::class.java) .setTag(backgroundJobKey) .setRecurring(true) .setTrigger(Trigger.executionWindow(60, 60)) // 1分钟轮询周期 .setLifetime(Lifetime.FOREVER) .setConstraints(Constraint.DEVICE_CHARGING) // 保留充电时执行的约束 .setReplaceCurrent(true) .build() dispatcher.schedule(job) }
然后修改ActivityRecognitionReceiver,根据状态切换Job的启停:
override fun onReceive(context: Context?, intent: Intent?) { // ... 前面的代码获取isStill ... val prefs = context?.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE) val wasStill = prefs?.getBoolean("is_device_still", false) ?: false if (isStill && !wasStill) { // 设备从移动变为静止,取消Job调度 cancelBackgroundJob(context!!) } else if (!isStill && wasStill) { // 设备从静止变为移动,重新调度Job scheduleBackgroundJob(context!!) } // 更新SharedPreferences里的静止标记 prefs?.edit()?.putBoolean("is_device_still", isStill)?.apply() }
注意事项
- 别忘了在AndroidManifest.xml中注册
LocationPollJobService和ActivityRecognitionReceiver,还要添加必要的权限:ACCESS_FINE_LOCATION、ACTIVITY_RECOGNITION(Android 12+需要运行时请求这个权限)。 - Activity Recognition的更新间隔别设太频繁,1-5分钟一次就足够,避免不必要的电量消耗。
内容的提问来源于stack exchange,提问作者Thanatos
相关产品推荐
相关产品推荐

