如何实现Realm本地数据与线上实时数据的同步更新?
实现Realm本地与API的Git式数据同步方案
我来给你梳理一套能实现这个Git式同步流程的具体方案,结合你提供的Realm模型和现有代码,一步步调整,满足先推本地变更再拉取线上数据、线上优先但本地非空补全的核心需求。
1. 给Realm模型增加同步状态标记
首先得给Imei类加一个字段,用来追踪这条数据是否已经成功同步到线上。这是实现“先推再拉”的核心依据:
public class Imei extends RealmObject { @PrimaryKey public int id; public int deviceId; public String imei; // 新增:标记该记录是否已同步至线上API public boolean isSynced; }
2. 调整本地存储逻辑,兼顾线上优先与本地补全
修改你的addToDB方法,加入校验逻辑:当拉取到的线上字段为空,但本地已有非空值时,用本地值覆盖线上拉取的空字段;同时保留同步状态(如果是待同步的记录,不能随便改成已同步)。
public void addToDB(Imei imei) { Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); // 查询本地是否已有该ID的记录 Imei localImei = realm.where(Imei.class).equalTo("id", imei.id).findFirst(); if (localImei != null) { // 核心逻辑:线上字段为空时,用本地非空值补全 if (TextUtils.isEmpty(imei.imei) && !TextUtils.isEmpty(localImei.imei)) { imei.imei = localImei.imei; } // 保留本地原有的同步状态(避免把待同步的记录误标记为已同步) imei.isSynced = localImei.isSynced; } else { // 新记录默认标记为未同步(用户输入的新数据) imei.isSynced = false; } realm.copyToRealmOrUpdate(imei); realm.commitTransaction(); realm.close(); }
3. 实现“先推再拉”的启动同步流程
在App启动或者需要拉取线上数据的时机,先上传所有本地未同步的记录,成功后再拉取线上数据。
3.1 批量获取本地待同步数据
private List<Imei> getUnsyncedImeis() { Realm realm = Realm.getDefaultInstance(); // 查询所有未同步的IMEI记录 List<Imei> unsyncedList = realm.where(Imei.class).equalTo("isSynced", false).findAll(); // 复制为非Realm对象,避免在事务外操作原数据 List<Imei> result = realm.copyFromRealm(unsyncedList); realm.close(); return result; }
3.2 上传待同步数据,完成后拉取线上
这里假设你用Retrofit做网络请求,你可以替换成自己的网络框架:
private void startSyncFlow() { List<Imei> unsyncedImeis = getUnsyncedImeis(); if (unsyncedImeis.isEmpty()) { // 没有待同步数据,直接拉取线上 fetchOnlineImeiData(); return; } // 构造批量上传请求 Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create(); String requestBody = gson.toJson(unsyncedImeis); ApiService apiService = RetrofitClient.getInstance().create(ApiService.class); Call<Void> uploadCall = apiService.uploadImeis(requestBody); uploadCall.enqueue(new Callback<Void>() { @Override public void onResponse(Call<Void> call, Response<Void> response) { if (response.isSuccessful()) { // 上传成功,标记这些记录为已同步 markImeisAsSynced(unsyncedImeis); } // 无论上传成功与否,都执行拉取(失败的话下次启动再重试) fetchOnlineImeiData(); } @Override public void onFailure(Call<Void> call, Throwable t) { // 网络失败,保留待同步标记,直接拉取线上数据 fetchOnlineImeiData(); } }); } // 批量标记记录为已同步 private void markImeisAsSynced(List<Imei> imeis) { Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); for (Imei imei : imeis) { Imei localImei = realm.where(Imei.class).equalTo("id", imei.id).findFirst(); if (localImei != null) { localImei.isSynced = true; } } realm.commitTransaction(); realm.close(); }
3.3 调整拉取响应的处理逻辑
拉取线上数据时,要确保线上有值则覆盖本地,但如果线上字段为空,用本地非空值补全,同时保留同步状态:
@Override public void onResponse(JSONObject response) { Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create(); ImeiResponse imeiResponse = gson.fromJson(response.toString(), ImeiResponse.class); Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); for (Imei onlineImei : imeiResponse.imeis()) { Imei localImei = realm.where(Imei.class).equalTo("id", onlineImei.id).findFirst(); if (localImei != null) { // 线上字段为空,用本地非空值替换 if (TextUtils.isEmpty(onlineImei.imei) && !TextUtils.isEmpty(localImei.imei)) { onlineImei.imei = localImei.imei; // 保留本地的同步状态(如果本地是待同步,说明这个值还没推到线上) onlineImei.isSynced = localImei.isSynced; } else { // 线上有有效数据,覆盖本地并标记为已同步 onlineImei.isSynced = true; } } else { // 线上新记录,默认标记为已同步 onlineImei.isSynced = true; } realm.copyToRealmOrUpdate(onlineImei); } realm.commitTransaction(); realm.close(); }
4. 用户输入数据后的即时上传
当用户输入IMEI并保存到本地时,立即尝试上传,成功后标记为已同步:
public void saveUserInputImei(Imei userInputImei) { // 新输入的数据默认标记为未同步 userInputImei.isSynced = false; addToDB(userInputImei); // 立即触发上传 uploadSingleImei(userInputImei); } private void uploadSingleImei(Imei imei) { Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create(); String requestBody = gson.toJson(imei); ApiService apiService = RetrofitClient.getInstance().create(ApiService.class); Call<Void> uploadCall = apiService.uploadImei(requestBody); uploadCall.enqueue(new Callback<Void>() { @Override public void onResponse(Call<Void> call, Response<Void> response) { if (response.isSuccessful()) { // 上传成功,标记为已同步 Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); Imei localImei = realm.where(Imei.class).equalTo("id", imei.id).findFirst(); if (localImei != null) { localImei.isSynced = true; } realm.commitTransaction(); realm.close(); } // 上传失败的话,保留未同步标记,下次启动再重试 } @Override public void onFailure(Call<Void> call, Throwable t) { // 网络失败,同步状态保持为false } }); }
核心逻辑总结
- 同步状态追踪:用
isSynced字段明确区分本地待同步和已同步数据,确保不会遗漏本地变更 - 先推再拉:每次拉取线上数据前,先上传所有未同步的本地记录,保证本地变更优先提交到线上
- 线上优先+本地补全:拉取时优先用线上数据,但线上字段为空时,用本地非空值补全,同时保留同步状态(确保补全后的数据能推回线上)
- 自动重试机制:用户输入后即时上传,失败则留到下次启动时再尝试,无需用户手动操作
内容的提问来源于stack exchange,提问作者Lewis Smith
相关产品推荐
相关产品推荐

