Cloud Firestore 支持离线数据持久化。这一功能会为您的应用正在使用的 Cloud Firestore 数据缓存一份副本,以便让您的应用可以在设备离线时访问数据。您可以写入、读取、监听和查询缓存的数据。当设备恢复在线状态时,Cloud Firestore 会将您的应用在本地所做的任何更改同步到 Cloud Firestore 后端。
注意:只有 Android、Apple 和 Web 应用才支持离线持久化。
您无需更改用于访问 Cloud Firestore 数据的代码,即可使用离线持久化功能。启用离线持久化后,Cloud Firestore 客户端库会自动管理在线和离线数据访问,并在设备恢复在线状态时同步本地数据。
配置离线持久化
在初始化 Cloud Firestore 时,您可以启用或停用离线持久化:
对于 Android 和 Apple 平台,离线持久化默认处于启用状态。如需停用持久化,请将 PersistenceEnabled 选项设置为 false。
对于 Web 应用,离线持久化默认处于停用状态。如需启用持久化,请调用 enablePersistence 方法。在不同会话之间切换时,系统不会自动清除 Cloud Firestore 的缓存。因此,如果您的 Web 应用涉及到敏感信息的处理,请务必在启用持久化之前询问用户其设备是否可信。
重要提示:对于 Web 应用,只有 Chrome、Safari 和 Firefox 网络浏览器支持离线持久化。
Web
详细了解可实现摇树优化的 Web v9 模块化 SDK,并从 v8 升级。
// Memory cache is the default if no config is specified.
initializeFirestore(app);
// This is the default behavior if no persistence is specified.
initializeFirestore(app, {localCache: memoryLocalCache()});
// Defaults to single-tab persistence if no tab manager is specified.
initializeFirestore(app, {localCache: persistentLocalCache(/*settings*/{})});
// Same as `initializeFirestore(app, {localCache: persistentLocalCache(/*settings*/{})})`,
// but more explicit about tab management.
initializeFirestore(app,
{localCache:
persistentLocalCache(/*settings*/{tabManager: persistentSingleTabManager()})
});
// Use multi-tab IndexedDb persistence.
initializeFirestore(app,
{localCache:
persistentLocalCache(/*settings*/{tabManager: persistentMultipleTabManager()})
});
Web
详细了解可实现摇树优化的 Web v9 模块化 SDK,并从 v8 升级。
firebase.firestore().enablePersistence()
.catch((err) => {
if (err.code == 'failed-precondition') {
// Multiple tabs open, persistence can only be enabled
// in one tab at a a time.
// ...
} else if (err.code == 'unimplemented') {
// The current browser does not support all of the
// features required to enable persistence
// ...
}
});
// Subsequent queries will use persistence, if it was enabled successfullytest.firestore.js
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
let settings = FirestoreSettings()
// Use memory-only cache
settings.cacheSettings =
MemoryCacheSettings(garbageCollectorSettings: MemoryLRUGCSettings())
// Use persistent disk cache, with 100 MB cache size
settings.cacheSettings = PersistentCacheSettings(sizeBytes: 100 * 1024 * 1024 as NSNumber)
// Any additional options
// ...
// Enable offline data persistence
let db = Firestore.firestore()
db.settings = settingsViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init];
// Use memory-only cache
settings.cacheSettings = [[FIRMemoryCacheSettings alloc]
initWithGarbageCollectorSettings:[[FIRMemoryLRUGCSettings alloc] init]];
// Use persistent disk cache (default behavior)
// This example uses 100 MB.
settings.cacheSettings = [[FIRPersistentCacheSettings alloc]
initWithSizeBytes:@(100 * 1024 * 1024)];
// Any additional options
// ...
// Enable offline data persistence
FIRFirestore *db = [FIRFirestore firestore];
db.settings = settings;ViewController.m
Kotlin
val settings = firestoreSettings {
// Use memory cache
setLocalCacheSettings(memoryCacheSettings {})
// Use persistent disk cache (default)
setLocalCacheSettings(persistentCacheSettings {})
}
db.firestoreSettings = settingsDocSnippets.kt
Java
FirebaseFirestoreSettings settings =
new FirebaseFirestoreSettings.Builder(db.getFirestoreSettings())
// Use memory-only cache
.setLocalCacheSettings(MemoryCacheSettings.newBuilder().build())
// Use persistent disk cache (default)
.setLocalCacheSettings(PersistentCacheSettings.newBuilder()
.build())
.build();
db.setFirestoreSettings(settings);DocSnippets.java
Dart
// Apple and Android
db.settings = const Settings(persistenceEnabled: true);
// Web
await db
.enablePersistence(const PersistenceSettings(synchronizeTabs: true));firestore.dart
配置缓存大小
启用持久化后,Cloud Firestore 会缓存从后端接收的每个文档以便在离线状态下进行访问。Cloud Firestore 会为缓存大小设置默认阈值。超出默认值后,Cloud Firestore 会定期尝试清理较旧的未使用文档。您可以配置不同的缓存大小阈值,也可以完全停用清理功能:
Web
import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore";
const firestoreDb = initializeFirestore(app, {
cacheSizeBytes: CACHE_SIZE_UNLIMITED
});fs_setup_cache.js
Web
详细了解可实现摇树优化的模块化 Web API 及其相对于命名空间型 API 的优势。firebase.firestore().settings({
cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED
});test.firestore.js
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "FirestoreCacheSizeUnlimited"
// to disable clean-up.
let settings = Firestore.firestore().settings
// Set cache size to 100 MB
settings.cacheSettings = PersistentCacheSettings(sizeBytes: 100 * 1024 * 1024 as NSNumber)
Firestore.firestore().settings = settingsViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "kFIRFirestoreCacheSizeUnlimited"
// to disable clean-up.
FIRFirestoreSettings *settings = [FIRFirestore firestore].settings;
// Set cache size to 100 MB
settings.cacheSettings =
[[FIRPersistentCacheSettings alloc] initWithSizeBytes:@(100 * 1024 * 1024)];
[FIRFirestore firestore].settings = settings;ViewController.m
Kotlin
// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED"
// to disable clean-up.
val settings = FirebaseFirestoreSettings.Builder()
.setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
.build()
db.firestoreSettings = settings
Java
// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes"
// for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED"
// to disable clean-up.
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
.build();
db.setFirestoreSettings(settings);
Dart
db.settings = const Settings(
persistenceEnabled: true,
cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);firestore.dart
监听离线数据
当设备离线时,如果您已启用离线持久化,则当本地缓存的数据发生更改时,您的监听器将收到监听事件。您可以监听文档、集合和查询。
如需检查您收到的数据是来自服务器还是来自缓存,可使用快照事件的 SnapshotMetadata 中的 fromCache 属性。如果 fromCache 为 true,则说明数据来自缓存,可能是过时的或不完整的。如果 fromCache 为 false,则说明数据是完整的,并且与服务器上的最新更新保持一致。
默认情况下,如果只有 SnapshotMetadata 发生更改,则不会引发任何事件。如果您依赖于 fromCache 值,请在附加监听处理程序时指定 includeMetadataChanges 监听选项。
Web
import { collection, onSnapshot, where, query } from "firebase/firestore";
const q = query(collection(db, "cities"), where("state", "==", "CA"));
onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("New city: ", change.doc.data());
}
const source = snapshot.metadata.fromCache ? "local cache" : "server";
console.log("Data came from " + source);
});
});use_from_cache.js
Web
详细了解可实现摇树优化的模块化 Web API 及其相对于命名空间型 API 的优势。db.collection("cities").where("state", "==", "CA")
.onSnapshot({ includeMetadataChanges: true }, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("New city: ", change.doc.data());
}
var source = snapshot.metadata.fromCache ? "local cache" : "server";
console.log("Data came from " + source);
});
});test.firestore.js
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
// Listen to metadata updates to receive a server snapshot even if
// the data is the same as the cached data.
db.collection("cities").whereField("state", isEqualTo: "CA")
.addSnapshotListener(includeMetadataChanges: true) { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error retreiving snapshot: \(error!)")
return
}
for diff in snapshot.documentChanges {
if diff.type == .added {
print("New city: \(diff.document.data())")
}
}
let source = snapshot.metadata.isFromCache ? "local cache" : "server"
print("Metadata: Data fetched from \(source)")
}ViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
// Listen to metadata updates to receive a server snapshot even if
// the data is the same as the cached data.
[[[db collectionWithPath:@"cities"] queryWhereField:@"state" isEqualTo:@"CA"]
addSnapshotListenerWithIncludeMetadataChanges:YES
listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
if (snapshot == nil) {
NSLog(@"Error retreiving snapshot: %@", error);
return;
}
for (FIRDocumentChange *diff in snapshot.documentChanges) {
if (diff.type == FIRDocumentChangeTypeAdded) {
NSLog(@"New city: %@", diff.document.data);
}
}
NSString *source = snapshot.metadata.isFromCache ? @"local cache" : @"server";
NSLog(@"Metadata: Data fetched from %@", source);
}];ViewController.m
Kotlin
db.collection("cities").whereEqualTo("state", "CA")
.addSnapshotListener(MetadataChanges.INCLUDE) { querySnapshot, e ->
if (e != null) {
Log.w(TAG, "Listen error", e)
return@addSnapshotListener
}
for (change in querySnapshot!!.documentChanges) {
if (change.type == DocumentChange.Type.ADDED) {
Log.d(TAG, "New city: ${change.document.data}")
}
val source = if (querySnapshot.metadata.isFromCache) {
"local cache"
} else {
"server"
}
Log.d(TAG, "Data fetched from $source")
}
}DocSnippets.kt
Java
db.collection("cities").whereEqualTo("state", "CA")
.addSnapshotListener(MetadataChanges.INCLUDE, new EventListener
@Override
public void onEvent(@Nullable QuerySnapshot querySnapshot,
@Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen error", e);
return;
}
for (DocumentChange change : querySnapshot.getDocumentChanges()) {
if (change.getType() == Type.ADDED) {
Log.d(TAG, "New city:" + change.getDocument().getData());
}
String source = querySnapshot.getMetadata().isFromCache() ?
"local cache" : "server";
Log.d(TAG, "Data fetched from " + source);
}
}
});DocSnippets.java
Dart
db
.collection("cities")
.where("state", isEqualTo: "CA")
.snapshots(includeMetadataChanges: true)
.listen((querySnapshot) {
for (var change in querySnapshot.docChanges) {
if (change.type == DocumentChangeType.added) {
final source =
(querySnapshot.metadata.isFromCache) ? "local cache" : "server";
print("Data fetched from $source}");
}
}
});firestore.dart
获取离线数据
如果您在设备离线时获取某个文档,Cloud Firestore 会从缓存中返回数据。
查询集合时,如果没有缓存的文档,系统会返回空结果。提取特定文档时,系统会返回错误。
查询离线数据
查询功能可与离线持久化功能结合使用。您可以直接使用 get 方法或通过监听(如前面的部分所述)来检索查询的结果。当设备离线时,您也可以针对已在本地永久保存的数据创建新的查询,但这些查询最开始只会针对缓存的文档运行。
配置离线查询索引
默认情况下,在执行离线查询时,Firestore SDK 会在其本地缓存中扫描集合内包含的所有文档。鉴于此默认行为,如果用户长时间处于离线状态,离线查询的性能可能会受到影响。
启用持久缓存后,您可以通过允许 SDK 自动创建本地查询索引来提高离线查询的性能。
默认情况下,自动编入索引功能处于停用状态。您的应用每次启动时都必须启用自动编入索引功能。按如下所示控制是否启用自动编入索引功能。
Swift
if let indexManager = Firestore.firestore().persistentCacheIndexManager {
// Indexing is disabled by default
indexManager.enableIndexAutoCreation()
} else {
print("indexManager is nil")
}
Objective-C
PersistentCacheIndexManager *indexManager = [FIRFirestore firestore].persistentCacheIndexManager;
if (indexManager) {
// Indexing is disabled by default
[indexManager enableIndexAutoCreation];
}
Kotlin
// return type: PersistentCacheManager?
Firebase.firestore.persistentCacheIndexManager?.apply {
// Indexing is disabled by default
enableIndexAutoCreation()
} ?: println("indexManager is null")
Java
// return type: @Nullable PersistentCacheIndexManager
PersistentCacheIndexManager indexManager = FirebaseFirestore.getInstance().getPersistentCacheIndexManager();
if (indexManager != null) {
// Indexing is disabled by default
indexManager.enableIndexAutoCreation();
}
// If not check indexManager != null, IDE shows warning: Method invocation 'enableIndexAutoCreation' may produce 'NullPointerException'
FirebaseFirestore.getInstance().getPersistentCacheIndexManager().enableIndexAutoCreation();
启用自动编入索引功能后,SDK 会评估哪些集合具有大量缓存的文档,并优化本地查询的性能。
SDK 提供了一种删除查询索引的方法。
停用和启用网络访问
您可以使用以下方法为您的 Cloud Firestore 客户端停用网络访问权限。网络访问权限停用后,所有快照监听器和文档请求都将从缓存中检索结果。写入操作会排入队列,直到网络访问权限重新启用。
Web
import { disableNetwork } from "firebase/firestore";
await disableNetwork(db);
console.log("Network disabled!");
// Do offline actions
// ...disable_network.js
Web
详细了解可实现摇树优化的模块化 Web API 及其相对于命名空间型 API 的优势。firebase.firestore().disableNetwork()
.then(() => {
// Do offline actions
// ...
});test.firestore.js
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
Firestore.firestore().disableNetwork { (error) in
// Do offline things
// ...
}ViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
[[FIRFirestore firestore] disableNetworkWithCompletion:^(NSError *_Nullable error) {
// Do offline actions
// ...
}];ViewController.m
Kotlin
db.disableNetwork().addOnCompleteListener {
// Do offline things
// ...
}DocSnippets.kt
Java
db.disableNetwork()
.addOnCompleteListener(new OnCompleteListener
@Override
public void onComplete(@NonNull Task
// Do offline things
// ...
}
});DocSnippets.java
Dart
db.disableNetwork().then((_) {
// Do offline things
});firestore.dart
使用以下方法可重新启用网络访问权限:
Web
import { enableNetwork } from "firebase/firestore";
await enableNetwork(db);
// Do online actions
// ...enable_network.js
Web
详细了解可实现摇树优化的模块化 Web API 及其相对于命名空间型 API 的优势。firebase.firestore().enableNetwork()
.then(() => {
// Do online actions
// ...
});test.firestore.js
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
Firestore.firestore().enableNetwork { (error) in
// Do online things
// ...
}ViewController.swift
Objective-C
注意:此产品不适用于 watchOS 和 App Clip 目标。
[[FIRFirestore firestore] enableNetworkWithCompletion:^(NSError *_Nullable error) {
// Do online actions
// ...
}];ViewController.m
Kotlin
db.enableNetwork().addOnCompleteListener {
// Do online things
// ...
}DocSnippets.kt
Java
db.enableNetwork()
.addOnCompleteListener(new OnCompleteListener
@Override
public void onComplete(@NonNull Task
// Do online things
// ...
}
});DocSnippets.java
Dart
db.enableNetwork().then((_) {
// Back online
});firestore.dart