15、传感器测试工具开发:自己写一个SensorTest App,实时显示所有传感器数据。
好,到了这一章,我们终于要动手写点真正能跑的东西了。
前面讲了那么多距离传感器的原理、校准、调试,但说实话,你总不能每次测试都连adb敲命令吧?我个人的习惯是,手边必须有一个趁手的传感器调试工具。今天我们就自己写一个——SensorTest App,实时显示所有传感器数据。
15.1 为什么非要自己写?
你可能会问:Google Play上不是有Sensor Test、Sensor Box之类的现成App吗?
嗯,确实有。但我遇到过好几次坑——有些App只显示加速度计和陀螺仪,距离传感器和光线传感器的数据根本不展示。还有的App刷新率太低,根本抓不到距离传感器的近/远跳变。
说白了,自己写的工具,你最清楚它怎么工作。想加什么字段就加什么字段,想调采样率就调采样率。而且,你还能把它集成到你的自动化测试框架里。
15.2 核心思路:监听所有传感器
Android的传感器框架其实很简单。核心就三个步骤:
- 拿到
SensorManager - 遍历
getSensorList(Sensor.TYPE_ALL) - 为每个传感器注册
SensorEventListener
听起来简单?但这里有个坑——不是所有传感器都能同时监听。我曾经在项目里一次性注册了十几个传感器,结果发现某些低端手机直接卡死,甚至崩溃。为什么?因为有些芯片的传感器总线是共享的,同时高频读取会导致冲突。
15.3 架构设计:一个轻量级的MVP
我们不需要搞太复杂的架构。但也不能把代码全塞进Activity里。我个人习惯用这种分层:
- Model层:传感器数据实体类,包含传感器名称、类型、数值、精度、时间戳
- Presenter层:管理传感器注册、注销、数据回调
- View层:RecyclerView展示所有传感器列表,每个item显示实时数据
下面这张图可以帮你快速理解整体流程:
15.4 关键代码实现
我们直接看核心部分。首先是传感器数据实体类:
public class SensorData {
public String sensorName; // 传感器名称
public int sensorType; // 传感器类型
public float[] values; // 数据值(不同传感器长度不同)
public float accuracy; // 精度
public long timestamp; // 时间戳(纳秒)
public boolean isRegistered; // 是否已注册监听
}
然后是Presenter的核心逻辑。这里我特别强调一点:注册传感器时一定要指定采样率。很多人直接用 SENSOR_DELAY_NORMAL,结果距离传感器的响应慢得像蜗牛。
public class SensorPresenter implements SensorEventListener {
private SensorManager sensorManager;
private List<Sensor> sensorList;
private Map<Integer, SensorData> dataMap = new HashMap<>();
public void init(Context context) {
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
// 遍历所有传感器,逐个注册
for (Sensor sensor : sensorList) {
// 距离传感器建议用 SENSOR_DELAY_FASTEST 或 UI
int delay = (sensor.getType() == Sensor.TYPE_PROXIMITY)
? SensorManager.SENSOR_DELAY_UI
: SensorManager.SENSOR_DELAY_NORMAL;
sensorManager.registerListener(this, sensor, delay);
SensorData data = new SensorData();
data.sensorName = sensor.getName();
data.sensorType = sensor.getType();
data.isRegistered = true;
dataMap.put(sensor.getType(), data);
}
}
@Override
public void onSensorChanged(SensorEvent event) {
SensorData data = dataMap.get(event.sensor.getType());
if (data != null) {
data.values = event.values.clone();
data.accuracy = event.accuracy;
data.timestamp = event.timestamp;
// 通知UI更新
notifyDataChanged(data);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
SensorData data = dataMap.get(sensor.getType());
if (data != null) {
data.accuracy = accuracy;
}
}
public void release() {
if (sensorManager != null) {
sensorManager.unregisterListener(this);
}
}
}
15.5 UI展示:实时刷新列表
UI部分我用RecyclerView来实现。每个item显示传感器名称、类型、数值、精度。这里有个小技巧——用DiffUtil来更新列表,避免整个列表闪烁。
public class SensorAdapter extends RecyclerView.Adapter<SensorAdapter.ViewHolder> {
private List<SensorData> dataList;
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
SensorData data = dataList.get(position);
holder.nameText.setText(data.sensorName);
// 距离传感器只显示第一个值(厘米)
if (data.sensorType == Sensor.TYPE_PROXIMITY) {
holder.valueText.setText(String.format("%.1f cm", data.values[0]));
} else {
// 其他传感器显示所有值
StringBuilder sb = new StringBuilder();
for (float v : data.values) {
sb.append(String.format("%.2f, ", v));
}
holder.valueText.setText(sb.toString());
}
// 精度显示:0=不可靠, 1=低, 2=中, 3=高
String[] accLabels = {"不可靠", "低", "中", "高"};
int accIndex = (int) data.accuracy;
if (accIndex >= 0 && accIndex < accLabels.length) {
holder.accText.setText("精度: " + accLabels[accIndex]);
}
}
}
15.6 实测效果与调试技巧
写完之后,我建议你拿真机跑一下。重点关注这几点:
| 传感器类型 | 预期行为 | 常见问题 |
|---|---|---|
| 距离传感器 | 遮挡时显示0cm或1cm,远离时显示5cm或更大 | 部分手机只有0和1两个值,属于正常 |
| 光线传感器 | 手电筒照射时数值飙升,遮挡时接近0 | 数值范围因厂商而异,不必纠结绝对值 |
| 加速度计 | 平放时Z轴约9.8,翻转时变化 | 静止时数值有微小波动,属于正常噪声 |
| 陀螺仪 | 静止时接近0,旋转时有数值 | 部分低端手机陀螺仪漂移严重 |
15.7 性能优化:别让App卡死
嗯,这里要特别提醒一下。如果你同时注册了十几个传感器,并且都用 SENSOR_DELAY_FASTEST,那你的UI线程可能会被回调淹没。我遇到过最夸张的情况——每秒收到上千次回调,RecyclerView根本来不及刷新,直接ANR。
解决方案有两个:
- 限流:在Presenter层加一个时间过滤器,比如每100ms只通知UI一次
- 异步刷新:用
HandlerThread处理传感器回调,只在主线程做UI更新
// 限流示例:每100ms刷新一次UI
private long lastUpdateTime = 0;
private static final long UPDATE_INTERVAL = 100; // 毫秒
@Override
public void onSensorChanged(SensorEvent event) {
long now = System.currentTimeMillis();
if (now - lastUpdateTime < UPDATE_INTERVAL) {
return; // 跳过这次更新
}
lastUpdateTime = now;
// 继续处理数据...
}
15.8 扩展思路:还能怎么玩?
这个SensorTest App写完之后,你可以继续加功能:
- 数据记录:把传感器数据保存到CSV文件,方便后续分析
- 波形图:用Canvas或者MPAndroidChart画实时曲线
- 阈值报警:比如距离传感器小于某个值时,触发震动或声音
- 多设备对比:同时连接多台手机,对比同一传感器的数据差异
我个人最喜欢的是波形图功能。有一次在调试一款手机的防误触功能时,我就是靠这个App的波形图,发现距离传感器的响应延迟高达200ms——这显然是不合格的。后来找驱动同事一查,果然是I2C总线时钟配置错了。
你看,自己写工具的好处就在这里——你能看到系统层看不到的细节。
公众号:蓝海资料掘金营,微信deep3321