19、传感器测试工具开发:开发一个简易的传感器测试App,实时显示光照值并记录最大值/最小值。

好,到了这一章,咱们要动真格的了。

前面讲了那么多光线传感器的原理、API、数据格式,都是理论知识。这一章,我们亲手写一个App,把传感器数据抓出来,实时显示在屏幕上,还要记录下最大值和最小值。

说白了,这就是一个「传感器调试工具箱」的雏形。我在项目里经常需要这种小工具,用来快速验证硬件是否正常工作。你想想看,如果每次都要打开完整的工程去调试,那效率得多低?

19.1 我们要做什么?

这个App的功能很纯粹:

  • 打开App,自动开始监听光线传感器
  • 实时显示当前光照值(单位:lux)
  • 记录并显示从启动到现在的最大值和最小值
  • 提供一个「重置」按钮,清空记录

嗯,就这么简单。但别小看它,这个结构可以套用到任何传感器上。你学会了这个,加速度计、陀螺仪、气压计……都是一个套路。

19.2 核心架构设计

先画个图,理清思路。我个人习惯在写代码之前,先把模块关系画清楚,这样不容易写乱。

传感器测试App 核心架构 SensorManager 注册/注销监听器 SensorEventListener onSensorChanged() UI更新 TextView实时刷新 数据流 更新UI 数据记录器 最大值/最小值追踪 重置按钮 清空记录 重置触发

从图上可以看得很清楚:SensorManager负责跟硬件打交道,监听器拿到数据后,一方面更新UI,一方面交给数据记录器去比较大小。重置按钮则负责把记录器里的值清空。

19.3 开始写代码

好,打开Android Studio,新建一个项目,包名就叫 com.example.sensortester。Activity我习惯用 MainActivity,布局文件用 activity_main.xml

19.3.1 布局文件

先写布局。界面不需要花哨,清晰就好。我一般会放三个大号的TextView来显示当前值、最大值、最小值,再加一个重置按钮。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:gravity="center">

    <TextView
        android:id="@+id/tv_current"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="当前光照:-- lux"
        android:textSize="28sp"
        android:textColor="#2c3e50"
        android:layout_marginBottom="32dp"/>

    <TextView
        android:id="@+id/tv_max"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="最大值:-- lux"
        android:textSize="20sp"
        android:textColor="#e74c3c"
        android:layout_marginBottom="16dp"/>

    <TextView
        android:id="@+id/tv_min"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="最小值:-- lux"
        android:textSize="20sp"
        android:textColor="#3498db"
        android:layout_marginBottom="40dp"/>

    <Button
        android:id="@+id/btn_reset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="重置记录"
        android:textSize="18sp"
        android:paddingLeft="32dp"
        android:paddingRight="32dp"/>

</LinearLayout>

布局很简单,就是垂直排列。当前值用大号字体突出显示,最大值用红色,最小值用蓝色,一目了然。

19.3.2 Java代码实现

接下来是核心代码。嗯,这里要注意几个关键点:

  • 传感器监听器必须在 onResume() 中注册,在 onPause() 中注销。这是为了省电,避免App退到后台还在耗电。
  • 最大值和最小值的初始值要设置成极端值:最大值初始为 Float.MIN_VALUE,最小值初始为 Float.MAX_VALUE。这样第一个数据进来后,一定会被记录。
  • UI更新必须在主线程。如果 onSensorChanged() 回调频率很高,可以考虑用 Handler 做节流,但光线传感器一般没那么快,直接更新就行。
public class MainActivity extends AppCompatActivity 
        implements SensorEventListener {

    private SensorManager sensorManager;
    private Sensor lightSensor;

    private TextView tvCurrent, tvMax, tvMin;
    private Button btnReset;

    private float maxLux = Float.MIN_VALUE;
    private float minLux = Float.MAX_VALUE;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化视图
        tvCurrent = findViewById(R.id.tv_current);
        tvMax = findViewById(R.id.tv_max);
        tvMin = findViewById(R.id.tv_min);
        btnReset = findViewById(R.id.btn_reset);

        // 获取传感器服务
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        if (sensorManager != null) {
            lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
        }

        // 检查传感器是否存在
        if (lightSensor == null) {
            tvCurrent.setText("当前设备不支持光线传感器");
            return;
        }

        // 重置按钮点击事件
        btnReset.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                maxLux = Float.MIN_VALUE;
                minLux = Float.MAX_VALUE;
                tvMax.setText("最大值:-- lux");
                tvMin.setText("最小值:-- lux");
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (lightSensor != null) {
            // 注册监听器,延迟设为 SENSOR_DELAY_NORMAL
            sensorManager.registerListener(this, lightSensor, 
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 一定要注销,否则会一直耗电
        if (sensorManager != null) {
            sensorManager.unregisterListener(this);
        }
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_LIGHT) {
            float lux = event.values[0];
            
            // 更新当前值
            tvCurrent.setText(String.format("当前光照:%.1f lux", lux));

            // 更新最大值
            if (lux > maxLux) {
                maxLux = lux;
                tvMax.setText(String.format("最大值:%.1f lux", maxLux));
            }

            // 更新最小值
            if (lux < minLux) {
                minLux = lux;
                tvMin.setText(String.format("最小值:%.1f lux", minLux));
            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // 光线传感器一般不需要处理精度变化
        // 但你可以在这里打印日志,方便调试
    }
}

核心逻辑总结:

  • onSensorChanged() 是数据入口,每次传感器有新数据都会回调这里
  • 最大值和最小值通过简单的比较逻辑维护
  • 重置按钮只是把两个变量恢复成初始值,UI也恢复成占位符

19.4 避坑指南

写这个App的时候,有几个坑我踩过,分享给你:

我曾经犯过的错:

  • 忘记注销监听器:有一次我把 unregisterListener 写在了 onDestroy() 里,结果App切到后台再切回来,注册了两次,回调也重复了。后来改成 onPause() 注销,onResume() 注册,问题解决。
  • 初始值设置错误:一开始我把最大值初始设为0,结果如果光线很暗(比如0.5 lux),最大值永远更新不了。后来才意识到要用 Float.MIN_VALUE
  • UI更新频率过高:虽然光线传感器不快,但如果你同时处理多个传感器,建议用 Handler 做一下限流,比如每100ms更新一次UI,避免界面卡顿。

小技巧:

如果你想测试这个App,可以用手电筒照一下手机的光线传感器(一般在听筒附近),光照值会瞬间飙升到几百甚至几千lux。用手捂住传感器,值会降到接近0。这样就能验证最大值和最小值记录是否正常工作了。

19.5 扩展思考

这个App虽然简单,但框架是通用的。你想想看,如果我想改成测试加速度计,只需要改三处:

  1. 传感器类型从 TYPE_LIGHT 改成 TYPE_ACCELEROMETER
  2. 数据显示从单值改成三轴(x, y, z)
  3. 最大值/最小值分别记录三个轴

其他代码几乎不用动。这就是「传感器测试工具」这个设计模式的好处——一次写好,到处复用。

好了,这一章的内容就到这里。代码不多,但都是干货。你把这个App跑起来,用手电筒照一照,看看最大值是不是真的能记录到几千lux?嗯,动手试试吧。


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