25、性能优化:避免在onSensorChanged中做耗时操作,使用HandlerThread处理数据

光线传感器看起来简单,但坑其实不少。

尤其是性能问题。我见过不少新手,一上来就在 onSensorChanged 里写一堆逻辑——写文件、网络请求、甚至数据库操作。结果呢?界面卡顿、传感器数据丢失、甚至应用直接 ANR。

为什么会这样?因为 onSensorChanged 是跑在 主线程 上的。你想想看,主线程既要处理 UI 绘制,又要响应触摸事件,还要处理传感器回调。你在这里面做耗时操作,不是添乱吗?

核心原则:onSensorChanged 里只做最轻量级的数据采集,所有数据处理都扔到后台线程去。

25.1 为什么不能在主线程处理传感器数据

传感器事件的频率很高。光线传感器虽然不像加速度计那么快,但也能达到几十到几百赫兹。每次回调都做耗时操作,主线程根本扛不住。

我举个例子。假设你在 onSensorChanged 里写了个日志文件:

@Override
public void onSensorChanged(SensorEvent event) {
    float lux = event.values[0];
    // 直接在回调里写文件 —— 大忌!
    writeToFile(String.valueOf(lux));
}

这段代码看起来人畜无害。但 writeToFile 涉及 I/O 操作,每次都要打开文件、写入、关闭。传感器一秒钟来几十次,你的主线程就被 I/O 堵死了。

我曾经在一个项目里接手过这样的代码。用户反馈说「打开手电筒后界面卡死」。查了半天,发现就是光线传感器回调里做了文件写入。去掉之后,问题立刻解决。

注意:主线程被阻塞超过 5 秒就会触发 ANR(Application Not Responding)。传感器高频回调 + 耗时操作 = ANR 的完美配方。

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() 的习惯。

小技巧:如果你只是做简单的计算,不想引入 HandlerThread,也可以用 AsyncTask 或者 Kotlin 协程。但 HandlerThread 的优势在于:它是一条「持久化」的后台线程,适合高频、持续的数据流。

25.4 流程图:数据流转全貌

为了让你更直观地理解,我画了一张流程图。看看数据是怎么从传感器一路跑到后台线程的。

光线传感器数据流转图 物理传感器 硬件层 中断 onSensorChanged 主线程 sendMessage HandlerThread 后台线程 排队处理 数据处理 写文件/数据库/网络 关键点: • onSensorChanged 只做数据转发,不做任何耗时操作 • HandlerThread 内部维护消息队列,保证数据按顺序处理

25.5 避坑指南

我踩过的坑,列出来给你参考:

  • 不要用 new Thread():每次回调都创建新线程,内存和 CPU 都受不了。HandlerThread 是长驻线程,复用才是正道。
  • 注意线程安全:如果你在 HandlerThread 里访问了共享变量,记得加锁或用 Atomic 类。我见过有人忘了加锁,结果数据错乱,排查了一整天。
  • 传感器延迟选择SENSOR_DELAY_NORMAL 大约 200ms 一次,SENSOR_DELAY_FASTEST 可能几毫秒一次。不是越快越好,够用就行。光线传感器用 NORMAL 通常就够了。
  • 生命周期绑定:在 Activity 的 onPause() 里取消注册,onResume() 里重新注册。别在 onDestroy() 里才清理,用户切到后台时传感器还在跑,白白耗电。

总结一下:

HandlerThread 是处理传感器数据的「标准答案」。它把主线程从繁重的数据处理中解放出来,让 UI 保持流畅。你只需要记住三点:

  1. onSensorChanged 里只转发数据
  2. 用 HandlerThread 做后台处理
  3. 别忘了在生命周期结束时清理资源

做到这三点,你的光线传感器代码就稳了。


公众号:蓝海资料掘金营,微信deep3321