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

如何实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:57:43