27、结合Room数据库:将传感器数据持久化存储、历史数据查询

好,咱们终于聊到数据持久化了。

前面几章,我们一直在实时获取霍尔传感器的数值。但说实话,光看实时数据,很多时候是不够的。你想想看,如果你在做一款智能门锁,或者一个磁场监测设备,你肯定需要知道“过去一小时磁场变化趋势”,或者“昨天这个时候磁场强度是多少”。

这时候,就需要数据库登场了。

在Android里,我个人最常用的本地数据库方案就是Room。它是Google官方推出的ORM框架,说白了就是帮我们把SQLite的操作封装得更加优雅。你不用写一堆繁琐的SQL语句,用注解就能搞定。

为什么选Room?

我记得刚入行那会儿,大家都是直接写SQLiteOpenHelper,建表、升级、CRUD全得手写。那叫一个痛苦。后来Google推出了Room,我第一时间就迁移过去了。原因很简单:

  • 编译时校验SQL:写错了直接编译报错,不会等到运行时才崩溃
  • 与LiveData/Flow无缝集成:数据一变,UI自动更新
  • 代码量减少一半以上:不用写那么多模板代码
我的习惯:只要涉及本地数据存储,我首选Room。除非数据量极小(比如几个配置项),我才会用SharedPreferences。

霍尔传感器数据模型设计

先定义我们要存什么。霍尔传感器通常返回三个值:x、y、z轴的磁场强度(单位微特斯拉)。另外,我们肯定要记录时间戳。

@Entity(tableName = "hall_sensor_data")
public class HallSensorData {
    @PrimaryKey(autoGenerate = true)
    private long id;

    private float xValue;
    private float yValue;
    private float zValue;
    private long timestamp;  // 毫秒级时间戳

    // 构造函数、getter、setter 省略
    // 实际开发中建议用 Kotlin data class,这里用Java示例
}

嗯,这里要注意:@PrimaryKey(autoGenerate = true) 会让Room自动生成自增ID。我个人习惯把ID设成long类型,因为int在数据量大时可能不够用。

DAO接口:数据访问层

DAO是Room的核心。你只需要定义接口,Room会自动生成实现类。

@Dao
public interface HallSensorDao {
    @Insert
    void insert(HallSensorData data);

    @Query("SELECT * FROM hall_sensor_data ORDER BY timestamp DESC")
    LiveData<List<HallSensorData>> getAllData();

    @Query("SELECT * FROM hall_sensor_data WHERE timestamp BETWEEN :startTime AND :endTime ORDER BY timestamp ASC")
    LiveData<List<HallSensorData>> getDataBetween(long startTime, long endTime);

    @Query("DELETE FROM hall_sensor_data WHERE timestamp < :cutoffTime")
    void deleteOldData(long cutoffTime);
}

看到没?查询条件直接用SQL写,但Room会在编译时检查语法。我曾经在项目里把表名写错了,编译直接报红,省了我半小时调试时间。

避坑指南:千万不要在DAO里写复杂的联表查询。Room虽然支持,但性能不一定好。我建议把复杂查询拆成多次简单查询,然后在业务层组装。

Database类:数据库的入口

定义一个抽象类,继承RoomDatabase。这里要指定实体类和版本号。

@Database(entities = {HallSensorData.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    public abstract HallSensorDao hallSensorDao();

    private static volatile AppDatabase INSTANCE;

    public static AppDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class,
                        "hall_sensor_db"
                    ).build();
                }
            }
        }
        return INSTANCE;
    }
}

这里用了单例模式。为什么?因为创建数据库实例开销不小,重复创建会浪费资源。我建议所有Room数据库都用单例。

在传感器回调中存储数据

现在,我们把实时数据存进去。注意,数据库操作不能在主线程执行,Room默认也不允许。所以我们要用异步方式。

// 在SensorEventListener的onSensorChanged中
new Thread(() -> {
    HallSensorData data = new HallSensorData();
    data.setXValue(event.values[0]);
    data.setYValue(event.values[1]);
    data.setZValue(event.values[2]);
    data.setTimestamp(System.currentTimeMillis());

    AppDatabase.getInstance(context).hallSensorDao().insert(data);
}).start();

当然,实际项目中我建议用线程池或者协程。这里为了演示,用了最简单的new Thread。

我的经验:传感器数据频率很高(比如50Hz),如果每来一次数据就写一次数据库,性能会出问题。我通常的做法是:先缓存到内存列表里,每攒够100条或者每隔1秒,批量插入一次。这样能大幅减少IO开销。

历史数据查询与展示

查询历史数据,用LiveData监听最方便。数据一变,UI自动刷新。

// 在Activity或Fragment中
AppDatabase.getInstance(this).hallSensorDao()
    .getDataBetween(startOfDay, endOfDay)
    .observe(this, dataList -> {
        // 更新图表或列表
        updateChart(dataList);
    });

这里我用了observe(this, ...),生命周期感知,不会内存泄漏。Room + LiveData 简直是绝配。

数据清理:别让数据库无限膨胀

霍尔传感器数据如果一直存,手机存储迟早会爆。我建议定期清理旧数据。

// 删除7天前的数据
long cutoff = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L;
AppDatabase.getInstance(this).hallSensorDao().deleteOldData(cutoff);

可以在应用启动时执行一次,或者用WorkManager定时清理。

知识体系图

下面这张图,帮你理清Room与霍尔传感器数据持久化的整体流程:

Room + 霍尔传感器数据持久化流程 霍尔传感器 onSensorChanged 回调 内存缓存(List) 攒够100条或间隔1秒 Room DB hall_sensor_db DAO 查询 getDataBetween / getAllData LiveData 自动更新UI 图表/列表 展示历史数据 定期清理旧数据

总结一下

Room数据库让霍尔传感器数据的持久化变得非常轻松。你只需要定义好Entity、DAO和Database,剩下的交给Room处理。配合LiveData,UI能自动响应数据变化。

我个人建议:一定要做批量插入和定期清理。这是我在多个项目里踩过坑之后总结出来的经验。否则,数据库写爆、查询卡顿,迟早找上门。

核心要点回顾:

  • Entity定义数据模型,注意时间戳字段
  • DAO用注解写SQL,编译时校验
  • Database用单例模式,避免重复创建
  • 批量插入提升性能,定期清理防止膨胀
  • LiveData + Room 实现数据驱动UI

好了,这一章就到这里。代码不多,但都是实战中提炼出来的。你拿去用,基本不会出大问题。


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