19、传感器测试工具开发:开发一个简易的传感器测试App,实时显示光照值并记录最大值/最小值。
好,到了这一章,咱们要动真格的了。
前面讲了那么多光线传感器的原理、API、数据格式,都是理论知识。这一章,我们亲手写一个App,把传感器数据抓出来,实时显示在屏幕上,还要记录下最大值和最小值。
说白了,这就是一个「传感器调试工具箱」的雏形。我在项目里经常需要这种小工具,用来快速验证硬件是否正常工作。你想想看,如果每次都要打开完整的工程去调试,那效率得多低?
19.1 我们要做什么?
这个App的功能很纯粹:
- 打开App,自动开始监听光线传感器
- 实时显示当前光照值(单位:lux)
- 记录并显示从启动到现在的最大值和最小值
- 提供一个「重置」按钮,清空记录
嗯,就这么简单。但别小看它,这个结构可以套用到任何传感器上。你学会了这个,加速度计、陀螺仪、气压计……都是一个套路。
19.2 核心架构设计
先画个图,理清思路。我个人习惯在写代码之前,先把模块关系画清楚,这样不容易写乱。
从图上可以看得很清楚: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虽然简单,但框架是通用的。你想想看,如果我想改成测试加速度计,只需要改三处:
- 传感器类型从
TYPE_LIGHT改成TYPE_ACCELEROMETER - 数据显示从单值改成三轴(x, y, z)
- 最大值/最小值分别记录三个轴
其他代码几乎不用动。这就是「传感器测试工具」这个设计模式的好处——一次写好,到处复用。
好了,这一章的内容就到这里。代码不多,但都是干货。你把这个App跑起来,用手电筒照一照,看看最大值是不是真的能记录到几千lux?嗯,动手试试吧。
公众号:蓝海资料掘金营,微信deep3321