本指南介绍了如何实现日历数据的“增量同步”。使用此方法,您可以在节省带宽的同时,使所有日历集合中的数据保持同步。
目录
概览
增量同步包含两个阶段:
初始完整同步在最开始时执行一次,以便将客户端的状态与服务器的状态完全同步。客户端将获取需要持久保存的同步令牌。
增量同步会重复执行,并使用自上次同步以来发生的所有更改来更新客户端。每次,客户端都会提供从服务器获取的上一个同步令牌,并存储响应中的新同步令牌。
初始完全同步
初始完整同步是指对要同步的集合的所有资源的原始请求。如果您只想同步特定子集的资源,可以选择使用请求参数来限制列表请求。
在对列表操作的响应中,您会找到一个名为 nextSyncToken
的字段,表示同步令牌。您需要存储 nextSyncToken
的值。如果结果集过大,并且响应会进行分页,则 nextSyncToken
字段仅出现在最后一页上。
增量同步
借助增量同步,您可以检索自上次同步请求以来已修改的所有资源。为此,您需要执行列表请求,并在 syncToken
字段中指定最新的同步令牌。请注意,结果始终会包含已删除的条目,以便客户端有机会将其从存储空间中移除。
如果自上次增量同步请求以来,大量资源发生了变化,您可能会在列表结果中看到 pageToken
而不是 syncToken
。在这些情况下,您需要执行与增量同步中用于检索第一页的列表查询完全相同的查询(具有完全相同的 syncToken
),将 pageToken
附加到该查询,然后对所有后续请求进行分页,直到在最后一页上找到另一个 syncToken
。请务必存储此 syncToken
,以供日后在下一次同步请求中使用。
以下是需要增量分页同步的用例的查询示例:
原始查询
GET /calendars/primary/events?maxResults=10&singleEvents=true&syncToken=CPDAlvWDx70CEPDAlvWDx
// Result contains the following
"nextPageToken":"CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA",
正在检索下一页
GET /calendars/primary/events?maxResults=10&singleEvents=true&syncToken=CPDAlvWDx70CEPDAlvWDx&pageToken=CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA
服务器要求进行完全同步
有时,服务器会因各种原因(包括令牌过期或相关 ACL 发生更改)使同步令牌失效。在这种情况下,服务器将使用响应代码 410
来响应增量请求。这应会触发客户端商店的完全清除和新的完全同步。
示例代码
下面的示例代码段演示了如何将同步令牌与 Java 客户端库搭配使用。首次调用 run 方法时,它会执行完整同步并存储同步令牌。在后续每次执行时,它都会加载已保存的同步令牌并执行增量同步。
private static void run() throws IOException { // Construct the {@link Calendar.Events.List} request, but don't execute it yet. Calendar.Events.List request = client.events().list("primary"); // Load the sync token stored from the last execution, if any. String syncToken = syncSettingsDataStore.get(SYNC_TOKEN_KEY); if (syncToken == null) { System.out.println("Performing full sync."); // Set the filters you want to use during the full sync. Sync tokens aren't compatible with // most filters, but you may want to limit your full sync to only a certain date range. // In this example we are only syncing events up to a year old. Date oneYearAgo = Utils.getRelativeDate(java.util.Calendar.YEAR, -1); request.setTimeMin(new DateTime(oneYearAgo, TimeZone.getTimeZone("UTC"))); } else { System.out.println("Performing incremental sync."); request.setSyncToken(syncToken); } // Retrieve the events, one page at a time. String pageToken = null; Events events = null; do { request.setPageToken(pageToken); try { events = request.execute(); } catch (GoogleJsonResponseException e) { if (e.getStatusCode() == 410) { // A 410 status code, "Gone", indicates that the sync token is invalid. System.out.println("Invalid sync token, clearing event store and re-syncing."); syncSettingsDataStore.delete(SYNC_TOKEN_KEY); eventDataStore.clear(); run(); } else { throw e; } } List<Event> items = events.getItems(); if (items.size() == 0) { System.out.println("No new events to sync."); } else { for (Event event : items) { syncEvent(event); } } pageToken = events.getNextPageToken(); } while (pageToken != null); // Store the sync token from the last request to be used during the next execution. syncSettingsDataStore.set(SYNC_TOKEN_KEY, events.getNextSyncToken()); System.out.println("Sync complete."); }
旧版同步
对于事件集合,仍可采用旧版方式进行同步,即保留事件列表请求中已更新字段的值,然后使用 modifiedSince
字段检索已更新的事件。此方法不再推荐使用,因为它更容易出现错过更新的情况(例如,如果未强制执行查询限制)。此外,它仅适用于活动。