28、传感器与Jetpack Compose:在Compose中集成光线传感器,使用LaunchedEffect管理生命周期
各位同学,今天我们来聊聊怎么把光线传感器和Jetpack Compose结合起来。说实话,我第一次在Compose里写传感器代码时,也踩了不少坑。最典型的问题就是——生命周期管理。
传统的View系统里,我们在Activity的onResume里注册监听器,在onPause里注销。但Compose是声明式UI,没有这些回调。那怎么办?答案就是LaunchedEffect。
为什么需要LaunchedEffect?
你想想看,Compose的组件随时可能重组。如果我们在重组时反复注册传感器,那内存泄漏就是分分钟的事。LaunchedEffect的作用,说白了就是帮我们管理协程的生命周期。
它有两个关键特性:
- 进入Composition时启动:当组件首次显示时,LaunchedEffect里的代码块开始执行。
- 离开Composition时自动取消:当组件被移除或key发生变化时,协程自动取消,不会留下“野”监听器。
我个人习惯把传感器注册放在LaunchedEffect里,这样既干净又安全。
核心思路:用LaunchedEffect包裹传感器监听器的注册与注销逻辑,利用协程的生命周期来管理传感器资源。
实战:在Compose中集成光线传感器
好,我们直接上代码。下面是一个完整的示例,展示如何在Compose中读取光线强度并实时显示。
@Composable
fun LightSensorScreen() {
// 保存光线强度值
var lux by remember { mutableStateOf(0f) }
// 获取传感器管理器
val context = LocalContext.current
val sensorManager = remember {
context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
val lightSensor = remember {
sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
}
// 使用LaunchedEffect管理传感器生命周期
LaunchedEffect(Unit) {
if (lightSensor == null) {
// 设备不支持光线传感器
return@LaunchedEffect
}
// 创建传感器事件监听器
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
lux = it.values[0] // 光线强度值
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// 精度变化时处理,这里暂时忽略
}
}
// 注册监听器
sensorManager.registerListener(
listener,
lightSensor,
SensorManager.SENSOR_DELAY_NORMAL
)
// 当协程被取消时(组件离开屏幕),注销监听器
awaitCancellation()
// 注意:awaitCancellation之后的代码不会执行
// 但我们可以用try-finally来确保注销
try {
awaitCancellation()
} finally {
sensorManager.unregisterListener(listener)
}
}
// UI展示
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "当前光线强度",
fontSize = 20.sp,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "${lux} lux",
fontSize = 48.sp,
fontWeight = FontWeight.Bold,
color = if (lux > 1000) Color.Yellow else Color.White
)
Spacer(modifier = Modifier.height(32.dp))
Text(
text = if (lightSensor == null) "设备不支持光线传感器" else "传感器正常工作",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
这段代码里,我用了remember来缓存SensorManager和Sensor对象,避免每次重组都重新获取。LaunchedEffect(Unit)表示这个效果只会在组件首次进入Composition时启动一次,不会因为重组而重复执行。
小技巧:如果你想让LaunchedEffect在某个值变化时重新启动,可以把那个值作为key传进去。比如LaunchedEffect(sensorType),当sensorType变化时,旧的协程会被取消,新的协程会启动。
避坑指南:我曾经踩过的坑
我曾经在项目里犯过一个低级错误——忘记在finally块里注销监听器。结果用户退出页面后,传感器还在后台跑,电量哗哗地掉。后来我学乖了,用try-finally确保注销逻辑一定会执行。
另外要注意一点:awaitCancellation()是一个挂起函数,它会一直阻塞直到协程被取消。所以如果你在它后面写代码,是永远不会执行的。正确的做法是用try-finally包裹,或者用withTimeout等机制。
警告:不要在LaunchedEffect里做耗时操作,比如网络请求或大量计算。LaunchedEffect运行在协程中,但它的主要职责是管理生命周期,不是做后台任务。如果需要后台任务,请使用rememberCoroutineScope。
知识体系图
下面这张图展示了本章的核心逻辑:从传感器硬件到Compose UI的完整数据流。
总结
嗯,今天的内容就这些。核心就一句话:用LaunchedEffect管理传感器生命周期,用remember缓存对象,用try-finally确保注销。这三点做到位了,你的传感器集成代码就不会出大问题。
我个人建议你在实际项目中,把传感器相关的逻辑封装成一个单独的Composable函数,这样复用起来更方便。比如写一个LightSensorValue函数,返回一个State
好了,去试试吧。记住,传感器这东西,注册了就要注销,不然电池会哭的。