25、性能优化:避免在onSensorChanged中做耗时操作,使用HandlerThread处理数据
光线传感器看起来简单,但坑其实不少。
尤其是性能问题。我见过不少新手,一上来就在 onSensorChanged 里写一堆逻辑——写文件、网络请求、甚至数据库操作。结果呢?界面卡顿、传感器数据丢失、甚至应用直接 ANR。
为什么会这样?因为 onSensorChanged 是跑在 主线程 上的。你想想看,主线程既要处理 UI 绘制,又要响应触摸事件,还要处理传感器回调。你在这里面做耗时操作,不是添乱吗?
25.1 为什么不能在主线程处理传感器数据
传感器事件的频率很高。光线传感器虽然不像加速度计那么快,但也能达到几十到几百赫兹。每次回调都做耗时操作,主线程根本扛不住。
我举个例子。假设你在 onSensorChanged 里写了个日志文件:
@Override
public void onSensorChanged(SensorEvent event) {
float lux = event.values[0];
// 直接在回调里写文件 —— 大忌!
writeToFile(String.valueOf(lux));
}
这段代码看起来人畜无害。但 writeToFile 涉及 I/O 操作,每次都要打开文件、写入、关闭。传感器一秒钟来几十次,你的主线程就被 I/O 堵死了。
我曾经在一个项目里接手过这样的代码。用户反馈说「打开手电筒后界面卡死」。查了半天,发现就是光线传感器回调里做了文件写入。去掉之后,问题立刻解决。
25.2 HandlerThread 是什么
说白了,HandlerThread 就是一个自带 Looper 的线程。它比普通 Thread 更方便,因为你可以通过 Handler 往里面发消息,让任务排队执行。
普通 Thread 的问题在于:任务执行完线程就结束了。你想再往里面塞任务?得重新创建线程。而 HandlerThread 有一个消息队列,你可以不停地往里面发消息,它会一个一个处理。
这正好适合传感器数据处理的场景——数据源源不断地来,我们需要一个「后台流水线」来处理它们。
25.3 实战:用 HandlerThread 处理光线传感器数据
好,我们直接上代码。我会把整个流程拆开讲。
第一步:创建 HandlerThread 和 Handler
public class LightSensorManager {
private SensorManager sensorManager;
private Sensor lightSensor;
private SensorEventListener listener;
private HandlerThread sensorThread;
private Handler sensorHandler;
public void start() {
// 创建 HandlerThread,名字叫 "SensorThread"
sensorThread = new HandlerThread("SensorThread");
sensorThread.start();
// 获取 HandlerThread 的 Looper,创建 Handler
sensorHandler = new Handler(sensorThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 这里处理数据,跑在后台线程
float lux = (float) msg.obj;
processLightData(lux);
}
};
// 注册传感器监听
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
listener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
// 主线程收到数据,立刻扔给后台
Message msg = sensorHandler.obtainMessage(0, event.values[0]);
sensorHandler.sendMessage(msg);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化一般不需要频繁处理
}
};
sensorManager.registerListener(listener, lightSensor,
SensorManager.SENSOR_DELAY_NORMAL);
}
private void processLightData(float lux) {
// 这里做耗时操作,比如写数据库、网络上传
// 注意:这个方法是跑在后台线程的
Log.d("LightSensor", "后台线程处理光照值: " + lux);
}
}
看到没?onSensorChanged 里只做了一件事:把数据封装成 Message,扔给 Handler。整个过程微秒级完成,主线程几乎没负担。
第二步:记得清理资源
嗯,这里有个容易忽略的点。Activity 销毁时,一定要取消注册并停止线程。否则会内存泄漏。
public void stop() {
if (sensorManager != null && listener != null) {
sensorManager.unregisterListener(listener);
}
// 安全退出 HandlerThread
if (sensorThread != null) {
sensorThread.quitSafely();
try {
sensorThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
quitSafely() 会等消息队列里的任务处理完再退出,比 quit() 更安全。我建议你养成用 quitSafely() 的习惯。
25.4 流程图:数据流转全貌
为了让你更直观地理解,我画了一张流程图。看看数据是怎么从传感器一路跑到后台线程的。
25.5 避坑指南
我踩过的坑,列出来给你参考:
- 不要用 new Thread():每次回调都创建新线程,内存和 CPU 都受不了。HandlerThread 是长驻线程,复用才是正道。
- 注意线程安全:如果你在 HandlerThread 里访问了共享变量,记得加锁或用 Atomic 类。我见过有人忘了加锁,结果数据错乱,排查了一整天。
- 传感器延迟选择:
SENSOR_DELAY_NORMAL大约 200ms 一次,SENSOR_DELAY_FASTEST可能几毫秒一次。不是越快越好,够用就行。光线传感器用 NORMAL 通常就够了。 - 生命周期绑定:在 Activity 的
onPause()里取消注册,onResume()里重新注册。别在onDestroy()里才清理,用户切到后台时传感器还在跑,白白耗电。
总结一下:
HandlerThread 是处理传感器数据的「标准答案」。它把主线程从繁重的数据处理中解放出来,让 UI 保持流畅。你只需要记住三点:
- onSensorChanged 里只转发数据
- 用 HandlerThread 做后台处理
- 别忘了在生命周期结束时清理资源
做到这三点,你的光线传感器代码就稳了。