18、传感器事件与线程管理:传感器事件队列、Handler与Looper使用、异步处理模式、避免ANR
传感器开发里有个很常见的坑——数据来得太快,UI 来不及反应。我早期做计步器项目时就吃过这个亏:传感器回调里直接更新 TextView,结果界面卡成 PPT,用户走了一百步,屏幕上数字才跳到三十。嗯,今天我们就来聊聊怎么优雅地处理传感器事件,以及如何避免 ANR 这个“应用杀手”。
传感器事件的“脾气”
传感器事件是通过 onSensorChanged() 回调传递给我们的。这个回调跑在哪个线程?答案是:取决于你注册传感器时用的 Looper。
如果你用 SensorManager.registerListener(listener, sensor, rate, handler) 传了一个 Handler,那么回调就发生在该 Handler 绑定的线程上。如果你没传 Handler,默认就在主线程。你想想看,主线程上每秒几十上百次的回调,UI 还能流畅吗?
传感器事件队列:数据洪流的“缓冲池”
传感器硬件产生数据的频率远高于我们能处理的频率。系统内部维护了一个事件队列,把传感器数据先缓存起来。我们通过 onSensorChanged() 一个个取出来处理。
但这里有个关键点:事件队列是共享的。如果你处理得太慢,队列会积压,新事件会覆盖旧事件吗?不会。系统会一直往队列里塞,直到队列满。满了之后怎么办?丢掉最旧的事件。这就是为什么有时候你会感觉传感器数据“跳变”——因为中间的一些事件被丢弃了。
我个人习惯的做法是:在 onSensorChanged() 里只做最轻量的操作——把数据塞进一个自定义的线程安全队列,然后立刻返回。真正的处理逻辑交给工作线程。
// 线程安全的传感器事件队列
private final ConcurrentLinkedQueue<SensorEvent> eventQueue = new ConcurrentLinkedQueue<>();
@Override
public void onSensorChanged(SensorEvent event) {
// 只做入队操作,绝不在这里处理数据
eventQueue.offer(event);
}
Handler 与 Looper:线程间通信的“信使”
Handler 和 Looper 是 Android 线程通信的核心。说白了,Looper 就是一个消息循环,不断从消息队列里取消息,交给 Handler 处理。
主线程默认就有 Looper,所以我们可以直接在主线程创建 Handler。但工作线程需要手动调用 Looper.prepare() 和 Looper.loop()。
在传感器场景下,我建议你创建一个专用的工作线程,让它持有自己的 Looper 和 Handler。这样传感器事件就在工作线程处理,不阻塞主线程。
// 创建一个带 Looper 的工作线程
HandlerThread sensorThread = new HandlerThread("sensor-thread");
sensorThread.start();
// 获取该线程的 Looper,创建 Handler
Handler sensorHandler = new Handler(sensorThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 在这里处理传感器数据
// 可以做滤波、计算、存储等操作
}
};
// 注册传感器时传入这个 Handler
sensorManager.registerListener(listener, accelerometer,
SensorManager.SENSOR_DELAY_NORMAL, sensorHandler);
异步处理模式:让传感器数据“流水线”起来
传感器数据处理的理想模式是生产者-消费者模型。传感器是生产者,不断产生事件;工作线程是消费者,不断消费事件。中间用队列解耦。
我常用的异步处理模式是这样的:
- 采集层:onSensorChanged() 里只做入队操作
- 缓冲层:线程安全的队列,暂存事件
- 处理层:工作线程从队列取数据,做滤波、特征提取等
- 输出层:处理结果通过 Handler 发回主线程更新 UI
这样做的好处很明显:每一层各司其职,互不阻塞。即使传感器数据爆发式增长,也只是队列变长,不会卡死 UI。
避免 ANR:传感器开发的“生死线”
ANR(Application Not Responding)是 Android 系统对主线程阻塞的警告。主线程超过 5 秒没处理完输入事件,或者 BroadcastReceiver 超过 10 秒没返回,系统就会弹出 ANR 对话框。
传感器场景下,最容易触发 ANR 的操作有:
- 在 onSensorChanged() 里做网络请求
- 在 onSensorChanged() 里写数据库
- 在 onSensorChanged() 里做复杂计算(如 FFT)
- 在 onSensorChanged() 里更新大量 UI 元素
我曾经接手过一个项目,传感器回调里直接调用了 Retrofit 的网络接口。结果用户一走路,应用就 ANR。排查了半天才发现问题。
避免 ANR 的几条铁律:
| 操作类型 | 允许在传感器回调中执行? | 建议做法 |
|---|---|---|
| 变量赋值 | ✅ 可以 | 直接赋值,但要考虑线程安全 |
| 入队操作 | ✅ 可以 | 使用 ConcurrentLinkedQueue 等线程安全集合 |
| UI 更新 | ❌ 不可以 | 通过 Handler 发到主线程 |
| 文件 I/O | ❌ 不可以 | 在工作线程中异步执行 |
| 网络请求 | ❌ 绝对不可以 | 使用协程或 RxJava 异步处理 |
| 数据库操作 | ❌ 不可以 | 使用 Room 的异步查询 |
实战:一个完整的传感器异步处理框架
下面是我在项目中常用的传感器处理框架,你可以直接拿来用:
public class SensorManagerWrapper {
private HandlerThread sensorThread;
private Handler sensorHandler;
private Handler mainHandler;
private ConcurrentLinkedQueue<float[]> dataQueue;
public SensorManagerWrapper() {
sensorThread = new HandlerThread("sensor-worker");
sensorThread.start();
sensorHandler = new Handler(sensorThread.getLooper());
mainHandler = new Handler(Looper.getMainLooper());
dataQueue = new ConcurrentLinkedQueue<>();
}
// 传感器回调——只做入队
private SensorEventListener listener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
float[] values = event.values.clone();
dataQueue.offer(values);
// 触发工作线程处理
sensorHandler.post(processingRunnable);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化一般不需要频繁处理
}
};
// 工作线程的处理逻辑
private Runnable processingRunnable = new Runnable() {
@Override
public void run() {
float[] data = dataQueue.poll();
while (data != null) {
// 在这里做滤波、计算等耗时操作
float result = processData(data);
// 把结果发回主线程
mainHandler.post(() -> updateUI(result));
data = dataQueue.poll();
}
}
};
private float processData(float[] values) {
// 示例:简单的低通滤波
// 实际项目中可能是步数检测、姿态解算等
return values[0] * 0.8f + values[1] * 0.2f;
}
private void updateUI(float value) {
// 更新 UI 控件
// textView.setText(String.valueOf(value));
}
public void release() {
sensorThread.quitSafely();
}
}
SVG:传感器事件处理流程
这张图展示了传感器事件从硬件到 UI 的完整流转路径。核心思想就是:每一层只做一件事,层与层之间用队列或 Handler 解耦。
总结
传感器事件管理说白了就三件事:别在主线程处理、用队列缓冲、异步回传结果。做到这三点,你的传感器应用基本不会出现 ANR 问题。
我记得刚入行时,带我的前辈说过一句话:“传感器数据就像流水,你堵不住它,只能引导它。” 嗯,现在想想,确实如此。
公众号:蓝海资料掘金营,微信deep3321