客户端重置 - Java SDK
SDK 读取和写入设备上的 Realm 文件。当您使用 Atlas Device Sync 时,此本地 Realm 会与应用程序后端同步。 某些情况可能会导致 Realm 无法与后端同步。 发生这种情况时,您会收到客户端重置错误。
此错误意味着您必须重置客户端应用程序中的 Realm 文件。处于这种状态的客户端可以继续运行并在本地保存数据。 在执行客户端重置之前,Realm 不会与后端同步。
选择客户端重置策略来处理客户端重置错误。 这些策略将 Realm 恢复到可同步状态,但需要权衡利弊:
丢弃未同步的更改。 通过丢弃自上次同步。 维护变更侦听器。
手动恢复未同步的更改: 移动未同步的域并下载新副本。 使变更侦听器失效。
这两个选项都允许您编写自定义逻辑来恢复本地更改。 这两个选项都无法为您恢复本地更改。
放弃未同步的更改是手动恢复的一种不太复杂的替代方案。 但是,此策略无法处理每个客户端重置错误。 您必须维护手动客户端重置处理程序作为后备措施。
丢弃未同步的更改
版本 10.10.0 中的新增内容。
丢弃未同步的更改是 SDK 提供的客户端重置策略。 此策略需要最少的代码。 此策略会在不关闭 Realm 或丢失通知的情况下执行重置。
它会删除自上次成功同步以来在本地所做的所有更改。 这包括已写入 Realm 但尚未同步到应用程序后端的任何数据。 如果您的应用程序不会丢失未同步的数据,请勿使用此策略。
丢弃未同步的更改无法处理中断性或破坏性的模式更改。 发生重大更改时,SDK 会回退到手动恢复模式。
要使用此策略,请在实例化 App
时将DiscardUnsyncedChangesStrategy的实例传递给defaultSyncClientResetStrategy()构建器方法。 您的DiscardUnsyncedChangesStrategy
实例必须实现以下方法:
onBeforeReset()
。当 SDK 从后端收到客户端重置错误时,它会调用此区块。 这发生在 SDK 执行客户端重置策略之前。onAfterReset()
。 SDK 在成功执行该策略后调用该区块。 该区块提供了原始 Realm 的冻结副本。 它还返回处于可同步状态的 Realm 活动实例。onError()
。 SDK 在中断模式更改期间调用此方法。 行为与defaultClientResetStrategy() 类似。
以下示例实施了该策略:
String appID = YOUR_APP_ID; // replace this with your App ID App app = new App(new AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(new DiscardUnsyncedChangesStrategy() { public void onBeforeReset(Realm realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.getPath()); } public void onAfterReset(Realm before, Realm after) { Log.w("EXAMPLE", "Finished client reset for " + before.getPath()); } public void onError(SyncSession session, ClientResetRequiredError error) { Log.e("EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual recovery: " + error.getErrorMessage()); handleManualReset(session.getUser().getApp(), session, error); } }) .build());
val appID: String = YOUR_APP_ID // replace this with your App ID val app = App( AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { override fun onBeforeReset(realm: Realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.path) } override fun onAfterReset(before: Realm, after: Realm) { Log.w("EXAMPLE", "Finished client reset for " + before.path) } override fun onError(session: SyncSession, error: ClientResetRequiredError) { Log.e( "EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual recovery: " + error.errorMessage ) handleManualReset(session.user.app, session, error) } }) .build() )
在中断性模式更改后丢弃未同步的更改
重要
中断性模式更改需要更新应用模式
发生重大模式更改后:
所有客户端都必须执行客户端重置。
您必须更新中断性模式更改影响的客户端模型。
丢弃未同步更改策略无法处理破坏性变更 (breaking change)。您必须在onError()
方法中手动处理客户端重置。 此示例手动丢弃未同步的更改以处理客户端重置:
String appID = YOUR_APP_ID; // replace this with your App ID App app = null; AtomicReference<App> globalApp = new AtomicReference<>(app); // accessing the app from within the lambda below requires an effectively final object app = new App(new AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(new DiscardUnsyncedChangesStrategy() { public void onBeforeReset(Realm realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.getPath()); } public void onAfterReset(Realm before, Realm after) { Log.w("EXAMPLE", "Finished client reset for " + before.getPath()); } public void onError(SyncSession session, ClientResetRequiredError error) { Log.e("EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual client reset execution: " + error.getErrorMessage()); // close all instances of your realm -- this application only uses one globalRealm.close(); try { Log.w("EXAMPLE", "About to execute the client reset."); // execute the client reset, moving the current realm to a backup file error.executeClientReset(); Log.w("EXAMPLE", "Executed the client reset."); } catch (IllegalStateException e) { Log.e("EXAMPLE", "Failed to execute the client reset: " + e.getMessage()); // The client reset can only proceed if there are no open realms. // if execution failed, ask the user to restart the app, and we'll client reset // when we first open the app connection. AlertDialog restartDialog = new AlertDialog.Builder(activity) .setMessage("Sync error. Restart the application to resume sync.") .setTitle("Restart to Continue") .create(); restartDialog.show(); } // open a new instance of the realm. This initializes a new file for the new realm // and downloads the backend state. Do this in a background thread so we can wait // for server changes to fully download. ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { Realm newRealm = Realm.getInstance(globalConfig); // ensure that the backend state is fully downloaded before proceeding try { globalApp.get().getSync().getSession(globalConfig).downloadAllServerChanges(10000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } Log.w("EXAMPLE", "Downloaded server changes for a fresh instance of the realm."); newRealm.close(); }); // execute the recovery logic on a background thread try { executor.awaitTermination(20000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } }) .build()); globalApp.set(app);
val appID = YOUR_APP_ID // replace this with your App ID var app: App? = null app = App( AppConfiguration.Builder(appID) .defaultSyncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { override fun onBeforeReset(realm: Realm) { Log.w("EXAMPLE", "Beginning client reset for " + realm.path) } override fun onAfterReset(before: Realm, after: Realm) { Log.w("EXAMPLE", "Finished client reset for " + before.path) } override fun onError(session: SyncSession, error: ClientResetRequiredError) { Log.e( "EXAMPLE", "Couldn't handle the client reset automatically." + " Falling back to manual client reset execution: " + error.errorMessage ) // close all instances of your realm -- this application only uses one globalRealm!!.close() try { Log.w("EXAMPLE", "About to execute the client reset.") // execute the client reset, moving the current realm to a backup file error.executeClientReset() Log.w("EXAMPLE", "Executed the client reset.") } catch (e: java.lang.IllegalStateException) { Log.e("EXAMPLE", "Failed to execute the client reset: " + e.message) // The client reset can only proceed if there are no open realms. // if execution failed, ask the user to restart the app, and we'll client reset // when we first open the app connection. val restartDialog = AlertDialog.Builder(activity) .setMessage("Sync error. Restart the application to resume sync.") .setTitle("Restart to Continue") .create() restartDialog.show() } // open a new instance of the realm. This initializes a new file for the new realm // and downloads the backend state. Do this in a background thread so we can wait // for server changes to fully download. val executor = Executors.newSingleThreadExecutor() executor.execute { val newRealm = Realm.getInstance(globalConfig) // ensure that the backend state is fully downloaded before proceeding try { app!!.sync.getSession(globalConfig) .downloadAllServerChanges( 10000, TimeUnit.MILLISECONDS ) } catch (e: InterruptedException) { e.printStackTrace() } Log.w( "EXAMPLE", "Downloaded server changes for a fresh instance of the realm." ) newRealm.close() } // execute the recovery logic on a background thread try { executor.awaitTermination(20000, TimeUnit.MILLISECONDS) } catch (e: InterruptedException) { e.printStackTrace() } } }) .build() )
手动恢复未同步的更改
提示
手动恢复会替换已弃用的SyncSession.ClientResetHandler
。 使用已弃用处理程序的客户端可以更新为手动恢复,无需进行逻辑更改。
我们不建议手动客户端重置恢复。 它要求:
大量代码
模式让步
复杂的冲突解决逻辑。
要了解更多信息,请参阅手动客户端重置数据恢复高级指南。
测试客户端重置处理
您可以通过终止并重新启用 Device Sync 来手动测试应用程序的客户端重置处理。
当您终止并重新启用 Sync 时,之前使用 Sync 连接的客户端在执行客户端重置之前无法进行连接。 终止同步会从服务器中删除允许客户端同步的元数据。 客户端必须从服务器下载 Realm 的新副本。 服务器向这些客户端发送客户端重置错误。 因此,当您终止同步时,就会trigger客户端重置条件。
要测试客户端重置处理,请执行以下操作:
从客户端应用程序写入数据并等待其同步。
终止并重新启用 Device Sync。
再次运行客户端应用程序。 当应用尝试连接到服务器时,应该会出现客户端重置错误。
警告
当您在客户端应用程序中迭代进行客户端重置处理时,您可能需要反复终止并重新启用 Sync。 终止并重新启用同步会导致所有现有客户端在完成客户端重置之前无法进行同步。 为了避免在生产中出现这种情况,请在开发环境中测试客户端重置处理。