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

应用退至后台时Cloud Firestore本地缓存数据回滚问题求助

游戏应用Firestore本地数据回滚问题排查求助

我开发了一款游戏应用,使用Cloud Firestore进行本地用户数据存储。数据持久化逻辑简单:用户操作数据时立即保存,任何Activity调用onPause()时也会保存数据。所有加载操作均使用Source.CACHE作为数据源,当前仅关注离线设备级数据持久化,不涉及云端同步。

经过6个月50名用户测试未出现问题,但开放Beta后部分用户反馈:应用通过设备“应用历史/后台按钮”退至后台再打开时,数据会回滚到旧状态;正常退出应用则无此问题。此问题并非遗漏保存导致的数据丢失,而是明确的回滚,业务逻辑中无回滚相关代码。

核心代码实现

加载方法

//I use this class to load a character
public class CharacterLoader implements FetcherInterface {

    String documentId; //The document id of the character
    String accountId; //The account id of the user
    Source source = Source.CACHE; //Always, and always Source.CACHE. Never loading from the cloud.

    //My very simple constructor
    public CharacterLoader(String accountId, String documentId) {
        this.documentId = documentId;
        this.accountId = accountId;
    }

    //This method basically launch the loading procedure
    @Override
    public void go(DbResultListener dbResultListener) {

        FirebaseFirestore db = FirebaseFirestore.getInstance();

        //Building the query to fetch the correct character by its documentId
        Query query = db.collectionGroup("localData").whereEqualTo("documentId", documentId);
        query.get(this.source).addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
            @Override
            public void onSuccess(QuerySnapshot queryDocumentSnapshots) {

                //If the loading was successful but there is no such character
                if (queryDocumentSnapshots.getDocuments().size() == 0) {
                    dbResultListener.onError("Error - There was an error during loading the character. Please try again.");
                } else {
                    //Loading was successful
                    DocumentSnapshot documentSnapshot = queryDocumentSnapshots.getDocuments().get(0);
                    Chara chara = documentSnapshot.toObject(Chara.class);
                    if (chara == null) {
                        //Character was null for some reason
                        dbResultListener.onError("Error - Character not found!");
                    } else {
                        //If the character found, I pass it to a listener callback so my app can use it
                        dbResultListener.onSuccess(chara);
                    }
                }
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                //If ther was an error
                e.printStackTrace();
                dbResultListener.onError("Error on character loading: "+e.getMessage());
            }
        });
    }
}

保存方法

//I use this class to save character
public class CharacterSaver implements FetcherInterface {

    Chara chara; //The data entity which we save
    String accountId; //User's account id
    String baseLink; //Link/path to my Cloud Firestore storage

    //A very simple constructor, it stops the app with a runtime error if the data parameter is null.
    public CharacterSaver(String baseLink, String accountId, Chara chara) {
        if (chara == null) {
            throw new RuntimeException("Chara object was null in parameter, cannot save!");
        }
        this.accountId = accountId;
        this.chara = chara;
        this.baseLink = baseLink;
    }

    //This is my save method
    @Override
    public void go(DbResultListener dbResultListener) {

        FirebaseFirestore db = FirebaseFirestore.getInstance();

        //Getting the document reference on the correct path with character's document id
        DocumentReference docRef = db.collection(baseLink).document(accountId).collection("localData").document(chara.getDocumentId());
        docRef.set(chara).addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                //Saving was successful, noting to do.
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(Exception e) {
                e.printStackTrace();
                //Passing the error with a listener to the application
                dbResultListener.onError("There was an error during saving character: " + e.getLocalizedMessage());
            }
        });
    }
}

onPause()实现及静态角色实例

@Override
protected void onPause() {
    super.onPause();
    CharacterSaver saver = new CharacterSaver(Server.getUsersUrl(this), Identifiers.getAccountIdentifier(this), getChara());
        
    saver.go(new DbResultListener() {
        @Override
        public void onError(String error) {
            DialogHelper.show(BaseActivity.this, error);
        }
    });
}
public static Chara getChara() {
    return charaPlayingStaticInstance;
}

已尝试的排查措施

  • 用Firebase Crashalytics全应用日志排查空指针、空数据状态
  • 记录onError()回调信息
  • 记录角色版本号、保存/加载时间戳
  • 保存后立即加载验证数据正确性
  • 为Chara类添加拷贝构造函数,避免引用传递问题
  • 在线/离线环境下尝试复现问题
  • 在多款设备及模拟器上尝试复现,均未成功

部分用户频繁出现此问题,而多数用户无此情况,现求助排查思路。

内容的提问来源于stack exchange,提问作者Adam Varhegyi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 02:13:18