28、结合WorkManager:后台定时采集传感器数据
霍尔传感器数据采集,如果只在App在前台时做,那其实没什么挑战。真正的难点在于——用户把App切到后台,甚至锁屏了,你还能不能稳定地拿到数据?
我早年做过一个智能门磁项目,霍尔传感器检测门窗开关状态。用户反馈说“明明门开了,App却没记录”。查了半天,原来是App被系统杀进程了。嗯,从那以后,我再也不敢只用Service来做后台采集了。
今天我们就聊聊WorkManager。它是Android官方推荐的“后台任务之王”。说白了,就是帮你搞定“即使App被杀了,也能在指定时间醒来干活”这件事。
为什么选WorkManager?
你可能会问:我直接用Handler+Service不行吗?行,但坑很多。
- 省电策略:系统对后台Service限制越来越严,Android 8.0以后后台Service几乎跑不起来
- 生命周期:App被杀死,Service就没了,数据采集中断
- 重试机制:网络不好、传感器临时不可用,你得自己写重试逻辑
WorkManager把这些都封装好了。它保证任务一定会执行,哪怕设备重启过。我个人习惯,凡是需要“定时、持久、可靠”的后台工作,一律用WorkManager。
整体架构
我们先看一张图,理清WorkManager采集霍尔传感器数据的完整流程:
流程其实不复杂。你只需要定义好“做什么”(Worker)和“多久做一次”(PeriodicWorkRequest),剩下的交给WorkManager调度。它会在合适的时机唤醒你的Worker,执行完就继续休眠。
实战:定时采集霍尔传感器数据
下面我们一步步实现。先加依赖:
// build.gradle (app级别)
dependencies {
implementation "androidx.work:work-runtime-ktx:2.9.0"
}
第一步:定义Worker
Worker就是你的“干活单元”。每次WorkManager唤醒它,就会执行doWork()方法。
class HallSensorWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
override fun doWork(): Result {
return try {
// 1. 获取霍尔传感器
val sensorManager = applicationContext
.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val hallSensor = sensorManager
.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
if (hallSensor == null) {
Log.w("HallWorker", "设备不支持霍尔传感器")
return Result.success()
}
// 2. 注册一次性监听(注意:这里用回调方式)
val latch = CountDownLatch(1)
var hallValue = 0f
val listener = SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
// 磁场强度,单位微特斯拉(μT)
hallValue = event.values[0]
latch.countDown()
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
sensorManager.registerListener(
listener, hallSensor,
SensorManager.SENSOR_DELAY_NORMAL
)
// 等待最多2秒获取数据
latch.await(2, TimeUnit.SECONDS)
sensorManager.unregisterListener(listener)
// 3. 存储数据(这里用SharedPreferences演示)
val prefs = applicationContext
.getSharedPreferences("hall_data", Context.MODE_PRIVATE)
prefs.edit()
.putFloat("last_value", hallValue)
.putLong("timestamp", System.currentTimeMillis())
.apply()
Log.d("HallWorker", "采集到霍尔值: $hallValue μT")
Result.success()
} catch (e: Exception) {
Log.e("HallWorker", "采集失败", e)
Result.retry() // 失败后重试
}
}
}
第二步:调度WorkManager
在Application或者Activity启动时,把任务注册进去:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
scheduleHallSensorCollection()
}
private fun scheduleHallSensorCollection() {
// 每15分钟采集一次(最小间隔15分钟,系统强制)
val collectRequest = PeriodicWorkRequestBuilder<HallSensorWorker>(
15, TimeUnit.MINUTES
)
.setConstraints(
Constraints.Builder()
.setRequiresBatteryNotLow(true) // 电量不低时执行
.build()
)
.addTag("hall_sensor_collection")
.build()
WorkManager.getInstance(this)
.enqueueUniquePeriodicWork(
"hall_sensor_work",
ExistingPeriodicWorkPolicy.KEEP, // 如果已存在,保留旧的
collectRequest
)
}
}
数据存储与查看
采集到的数据存在SharedPreferences里。我们可以加一个简单的读取方法:
fun getLastHallValue(context: Context): Pair<Float, Long>? {
val prefs = context.getSharedPreferences("hall_data", Context.MODE_PRIVATE)
val value = prefs.getFloat("last_value", Float.NaN)
val timestamp = prefs.getLong("timestamp", 0L)
return if (timestamp > 0) Pair(value, timestamp) else null
}
在UI上展示时,我建议用RecyclerView展示历史记录。不过那是UI层面的东西了,今天我们聚焦后台采集。
避坑指南
我踩过的坑,列出来给你参考:
- 传感器注册/注销要成对:Worker执行完一定要unregisterListener,否则传感器一直开着,耗电极快。我曾经有个版本忘记注销,用户反馈手机半天就没电了。
- Result.retry()有退避策略:默认是10秒后重试,指数退避。如果你不想重试太多次,可以自定义BackoffPolicy。
- UniquePeriodicWork的KEEP vs REPLACE:如果用户重新打开App,你希望保留旧任务还是替换?我建议用KEEP,避免重复创建多个任务。
- 调试时手动触发:可以用adb命令立即执行一次Worker,不用等15分钟:
adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS"
扩展:采集频率自适应
如果你觉得固定15分钟太死板,可以结合电量做自适应:
| 电量状态 | 采集间隔 | 说明 |
|---|---|---|
| 电量 > 50% | 15分钟 | 正常采集 |
| 电量 20% ~ 50% | 30分钟 | 降低频率 |
| 电量 < 20% | 60分钟 | 保底采集 |
实现方式很简单:在Worker里读取当前电量,动态决定是否跳过本次采集。比如电量低于20%时,直接return Result.success(),什么都不做。
// 在doWork()开头加判断
val batteryManager = applicationContext
.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val battery = batteryManager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
)
if (battery < 20) {
Log.d("HallWorker", "电量不足,跳过本次采集")
return Result.success()
}
这样做的好处是:既保证了数据连续性,又不会在低电量时雪上加霜。
小结
WorkManager + 霍尔传感器,说白了就是“定时醒来,干活,睡觉”。你不需要关心系统什么时候杀进程,也不需要写复杂的重试逻辑。WorkManager全帮你兜底了。
我个人建议,只要不是秒级采集需求,优先用WorkManager。它省心、省电、稳定。如果你真需要高频采集,那就得考虑前台Service + 前台通知了——那是另一个话题。
嗯,今天就到这里。代码可以直接拿去用,记得把依赖版本换成最新的。