16、数据存储与展示:将采集到的光照数据存入SQLite数据库,使用RecyclerView展示历史数据。
好,咱们继续往前走。前面几章我们把光线传感器调通了,数据也能实时看到了。但有个问题——你关掉App再打开,之前的数据全没了。这就像你写日记,写完就烧掉,那还记个啥?
所以这一章,我们得把数据存起来。我个人的习惯是,在项目初期就考虑好数据持久化方案,不然后面改起来很痛苦。咱们就用Android自带的SQLite,轻量级,不用搭服务器,够用。
16.1 为什么选SQLite?
你可能会问:现在有Room、有Realm,为什么还要学SQLite?
嗯,这个问题我当年也纠结过。说白了,SQLite是Android系统自带的,零依赖。Room底层也是封装了SQLite。你理解了SQLite,再用Room就一通百通。我在一个嵌入式项目里,连Room都没法用,只能手写SQLite,那时候就庆幸自己基础打得牢。
SQLite的几个特点:
- 轻量级:整个数据库就是一个文件
- 零配置:不用安装,不用配置
- 可靠:Google官方推荐,经过大量验证
16.2 设计数据表
存光照数据,我们需要记录什么?
至少三个字段:
- 唯一ID(自增主键)
- 光照值(lux)
- 采集时间(时间戳)
我建议再加一个备注字段,方便以后扩展。比如记录一下当时是室内还是室外。别问我为什么,我在项目里吃过亏——数据存了一大堆,回头想分析,发现不知道哪个数据对应什么场景。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键,自增 |
| lux | FLOAT | 光照强度 |
| timestamp | LONG | 采集时间(毫秒) |
| note | TEXT | 备注(可选) |
16.3 创建数据库帮助类
Android里操作SQLite,标准做法是继承SQLiteOpenHelper。我刚开始学的时候,直接拿SQLiteDatabase的静态方法搞,结果各种坑。后来老老实实写Helper类,世界清净了。
public class LightDataDbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "light_data.db";
private static final int DATABASE_VERSION = 1;
public static final String TABLE_NAME = "light_records";
public static final String COLUMN_ID = "id";
public static final String COLUMN_LUX = "lux";
public static final String COLUMN_TIMESTAMP = "timestamp";
public static final String COLUMN_NOTE = "note";
private static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_LUX + " REAL NOT NULL, " +
COLUMN_TIMESTAMP + " INTEGER NOT NULL, " +
COLUMN_NOTE + " TEXT)";
public LightDataDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 生产环境要做迁移,这里简单处理
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
16.4 插入数据
采集到光照值后,我们把它存进去。这里我封装了一个方法:
public void insertLightData(float lux, String note) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_LUX, lux);
values.put(COLUMN_TIMESTAMP, System.currentTimeMillis());
values.put(COLUMN_NOTE, note);
db.insert(TABLE_NAME, null, values);
db.close();
}
你想想看,每次采集都调这个方法,数据就一条条存进去了。简单吧?
16.5 查询数据
存进去是为了取出来。我们要展示历史数据,一般按时间倒序排列,最新的在最上面。
public List<LightRecord> getAllRecords() {
List<LightRecord> records = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null,
COLUMN_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
do {
LightRecord record = new LightRecord();
record.setId(cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
record.setLux(cursor.getFloat(cursor.getColumnIndex(COLUMN_LUX)));
record.setTimestamp(cursor.getLong(cursor.getColumnIndex(COLUMN_TIMESTAMP)));
record.setNote(cursor.getString(cursor.getColumnIndex(COLUMN_NOTE)));
records.add(record);
} while (cursor.moveToNext());
}
cursor.close();
db.close();
return records;
}
16.6 用RecyclerView展示数据
数据取出来了,怎么展示?用ListView?别,那东西太老了。RecyclerView才是现代Android的标配。它更灵活,性能更好。
首先,定义一个数据模型:
public class LightRecord {
private long id;
private float lux;
private long timestamp;
private String note;
// getter和setter省略,自己补上
}
然后,写一个Adapter:
public class LightRecordAdapter extends RecyclerView.Adapter<LightRecordAdapter.ViewHolder> {
private List<LightRecord> records;
public LightRecordAdapter(List<LightRecord> records) {
this.records = records;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_light_record, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
LightRecord record = records.get(position);
holder.tvLux.setText(String.format("%.1f lux", record.getLux()));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
holder.tvTime.setText(sdf.format(new Date(record.getTimestamp())));
holder.tvNote.setText(record.getNote() != null ? record.getNote() : "");
}
@Override
public int getItemCount() {
return records.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvLux, tvTime, tvNote;
ViewHolder(View itemView) {
super(itemView);
tvLux = itemView.findViewById(R.id.tv_lux);
tvTime = itemView.findViewById(R.id.tv_time);
tvNote = itemView.findViewById(R.id.tv_note);
}
}
}
布局文件item_light_record.xml也很简单,三个TextView排成一行或者一列,看你喜好。
最后,在Activity或Fragment里把RecyclerView和Adapter连起来:
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
LightDataDbHelper dbHelper = new LightDataDbHelper(this);
List<LightRecord> records = dbHelper.getAllRecords();
LightRecordAdapter adapter = new LightRecordAdapter(records);
recyclerView.setAdapter(adapter);
搞定。运行一下,你就能看到历史数据一条条列出来了。
16.7 整体流程梳理
这一套下来,其实就三个步骤:存、取、展示。我画了个图,帮你理清思路:
16.8 避坑指南
最后,分享几个我踩过的坑:
- 数据库线程问题:不要在UI线程做耗时数据库操作。虽然SQLite很快,但数据量大时还是会卡。我建议用AsyncTask或者Room的协程。
- Cursor没关闭:前面说过了,血的教训。每次用完Cursor,记得close。
- RecyclerView刷新:插入新数据后,记得调用
adapter.notifyDataSetChanged()或者更精准的notifyItemInserted()。不然你插了数据,界面还是老样子。 - 时间格式化:存时间戳用long,展示时再格式化。别存格式化后的字符串,不然排序、计算都很麻烦。
好了,这一章的内容就到这。你把代码跑起来,看看历史数据是不是乖乖躺在列表里了?嗯,那种感觉还是挺爽的。