You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 07:05:40