如何结合Retrofit、RxJava将服务器数据存储到SQLite数据库?
嘿,我来帮你搞定Retrofit拉取数据后存入SQLite的事儿!先理清楚你的代码逻辑:你用flatMap把两个API请求串起来了,先拿ImagePathResponse,再拿CtbLeidSource的数据对吧?那存储的核心就是在合适的时机把响应数据塞进数据库,而且得保证操作在IO线程,别卡主线程。下面分步骤给你讲:
第一步:搞定SQLite数据库操作类
首先你得有个数据库操作的工具类,这里推荐用Jetpack Room(比原生SQLite省心太多,还自带线程安全校验),当然原生SQLite的实现也给你列出来:
用Room的情况(推荐)
先给你的响应模型对应写个数据库实体类:
@Entity(tableName = "image_path") public class ImagePathEntity { @PrimaryKey(autoGenerate = true) private int id; @ColumnInfo(name = "message") private String message; @ColumnInfo(name = "has_columns") private Integer hasColumns; // 把ImagePathResponse里的其他字段都对应加上,再写好getter/setter }
然后写Dao接口(数据访问对象):
@Dao public interface ImagePathDao { @Insert(onConflict = OnConflictStrategy.REPLACE) // 重复数据自动替换 void insertImagePath(ImagePathEntity entity); // 按需添加查询、更新、删除方法 }
再写数据库单例类(避免重复创建连接):
@Database(entities = {ImagePathEntity.class, CtbLeidSourceEntity.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract ImagePathDao imagePathDao(); public abstract CtbLeidSourceDao ctbLeidSourceDao(); // 对应第二个请求的Dao 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, "app_database") .build(); } } } return INSTANCE; } }
用原生SQLite的情况
写个SQLiteOpenHelper子类:
public class DatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "app_db"; private static final int DB_VERSION = 1; // 建表语句,对应ImagePathResponse的字段 private static final String CREATE_TABLE_IMAGE_PATH = "CREATE TABLE image_path (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "message TEXT," + "has_columns INTEGER)"; public DatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_IMAGE_PATH); // 别忘了给CtbLeidSource也建对应的表 } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 版本升级时的表结构变更逻辑,比如删表重建或添加字段 } // 插入ImagePath数据的方法 public void insertImagePath(ImagePathResponse response) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("message", response.getMessage()); values.put("has_columns", response.getHasColumns()); // 把其他字段也塞进ContentValues db.insert("image_path", null, values); db.close(); } // 同理写CtbLeidSource的插入方法 }
第二步:在Retrofit的订阅流程里加存储逻辑
你的代码是串联两个请求,两个数据都要存的话,就分别在flatMap(处理第一个请求响应)和subscribe(处理第二个请求响应)里加存储操作:
用Room的代码示例
// 先拿到数据库实例(建议在Application或Activity中获取) AppDatabase db = AppDatabase.getInstance(getApplicationContext()); // 你的原有请求链,加上存储逻辑 Disposable disposable = WebserviceHandler.getImagePathSelectAl() .subscribeOn(Schedulers.io()) // 整个流程跑在IO线程 .flatMap(imagePathResponse -> { // 先把第一个请求的数据存进数据库 ImagePathEntity entity = new ImagePathEntity(); entity.setMessage(imagePathResponse.getMessage()); entity.setHasColumns(imagePathResponse.getHasColumns()); // 映射其他字段 db.imagePathDao().insertImagePath(entity); // 继续发起第二个请求 return WebserviceHandler.getCtbLeidSource(); }) .subscribe(ctbLeidSourceResponse -> { // 存储第二个请求的数据 CtbLeidSourceEntity ctbEntity = new CtbLeidSourceEntity(); // 把ctbLeidSourceResponse的字段映射到实体类 db.ctbLeidSourceDao().insertCtbLeidSource(ctbEntity); // 存储成功后,切回主线程更新UI或提示用户 runOnUiThread(() -> { Toast.makeText(this, "数据存储完成", Toast.LENGTH_SHORT).show(); }); }, throwable -> { // 处理错误,比如打印日志或提示用户 Log.e("DB_ERROR", "存储失败: " + throwable.getMessage()); runOnUiThread(() -> { Toast.makeText(this, "数据存储失败", Toast.LENGTH_SHORT).show(); }); }); // 别忘了在页面销毁时取消订阅,防止内存泄漏 @Override protected void onDestroy() { super.onDestroy(); if (disposable != null && !disposable.isDisposed()) { disposable.dispose(); } }
用原生SQLite的代码示例
逻辑和Room一致,只是把存储换成调用DatabaseHelper的方法:
DatabaseHelper dbHelper = new DatabaseHelper(getApplicationContext()); Disposable disposable = WebserviceHandler.getImagePathSelectAl() .subscribeOn(Schedulers.io()) .flatMap(imagePathResponse -> { // 存储第一个请求的数据 dbHelper.insertImagePath(imagePathResponse); return WebserviceHandler.getCtbLeidSource(); }) .subscribe(ctbLeidSourceResponse -> { // 存储第二个请求的数据 dbHelper.insertCtbLeidSource(ctbLeidSourceResponse); runOnUiThread(() -> { Toast.makeText(this, "数据存储完成", Toast.LENGTH_SHORT).show(); }); }, throwable -> { Log.e("DB_ERROR", "存储失败: " + throwable.getMessage()); runOnUiThread(() -> { Toast.makeText(this, "数据存储失败", Toast.LENGTH_SHORT).show(); }); }); // 同样要在页面销毁时取消订阅 @Override protected void onDestroy() { super.onDestroy(); if (disposable != null && !disposable.isDisposed()) { disposable.dispose(); } }
几个关键注意点
- 线程安全:数据库操作绝对不能在主线程执行,你的代码已经用
subscribeOn(Schedulers.io())把整个请求链放在IO线程,这点没问题;Room默认会强制禁止主线程操作,写错会直接报错,很贴心。 - 数据映射:如果响应模型字段多,别手动一个个写映射,可以用MapStruct这类工具库简化操作。
- 内存泄漏:用RxJava时一定要在页面销毁时取消订阅,不然会持有页面引用导致内存泄漏。
- 冲突处理:如果可能出现重复数据,Room的
@Insert可以加onConflict = OnConflictStrategy.REPLACE/IGNORE,原生SQLite可以用insertWithOnConflict方法处理。
这样应该就能顺利把Retrofit拉到的数据存进SQLite啦!
内容的提问来源于stack exchange,提问作者Basi
相关产品推荐
相关产品推荐

