Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

离线访问数据

Cloud Firestore 支持离线数据持久化。这一功能会为您的应用正在使用的 Cloud Firestore 数据缓存一份副本,以便让您的应用可以在设备离线时访问数据。您可以写入、读取、监听和查询缓存的数据。当设备恢复在线状态时,Cloud Firestore 会将您的应用在本地所做的任何更改同步到 Cloud Firestore 后端。

您无需更改用于访问 Cloud Firestore 数据的代码,即可使用离线持久化功能。启用离线持久化后,Cloud Firestore 客户端库会自动管理在线和离线数据访问,并在设备恢复在线状态时同步本地数据。

配置离线持久化

在初始化 Cloud Firestore 时,您可以启用或停用离线持久化:

  • 对于 Android 版和 iOS 版应用,离线持久化默认处于启用状态。如需停用持久化,请将 PersistenceEnabled 选项设置为 false
  • 对于 Web 应用,离线持久化默认处于停用状态。如需启用持久化,请调用 enablePersistence 方法。在不同会话之间切换时,系统不会自动清除 Cloud Firestore 的缓存。因此,如果您的 Web 应用涉及到敏感信息的处理,请务必在启用持久化之前询问用户其设备是否可信。

Web 版本 9

import { enableIndexedDbPersistence } from "firebase/firestore";

enableIndexedDbPersistence(db)
  .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 successfully

Web 版本 8

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 successfully
Swift
let settings = FirestoreSettings()
settings.isPersistenceEnabled = true

// Any additional options
// ...

// Enable offline data persistence
let db = Firestore.firestore()
db.settings = settings
Objective-C
FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init];
settings.persistenceEnabled = YES;

// Any additional options
// ...

// Enable offline data persistence
FIRFirestore *db = [FIRFirestore firestore];
db.settings = settings;

Java

FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
        .setPersistenceEnabled(true)
        .build();
db.setFirestoreSettings(settings);

Kotlin+KTX

val settings = firestoreSettings {
    isPersistenceEnabled = true
}
db.firestoreSettings = settings

配置缓存大小

启用持久化后,Cloud Firestore 会缓存从后端接收的每个文档以便离线访问。Cloud Firestore 会为缓存大小设置默认阈值。超出默认值后,Cloud Firestore 会定期尝试清理较旧的未使用文档。您可以配置不同的缓存大小阈值,也可以完全停用清理功能:

Web 版本 9

import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore";

const firestoreDb = initializeFirestore(app, {
  cacheSizeBytes: CACHE_SIZE_UNLIMITED
});

Web 版本 8

firebase.firestore().settings({
    cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED
});
Swift
// 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
settings.cacheSizeBytes = FirestoreCacheSizeUnlimited
Firestore.firestore().settings = settings
Objective-C
// 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;
settings.cacheSizeBytes = kFIRFirestoreCacheSizeUnlimited;
[FIRFirestore firestore].settings = 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);

Kotlin+KTX


// 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

监听离线数据

当设备离线时,如果您已启用离线持久化,当本地缓存的数据发生更改时,您的监听器将收到监听事件。您可以监听文档、集合和查询。

如需检查您收到的数据是来自服务器还是来自缓存,可使用快照事件的 SnapshotMetadata 中的 fromCache 属性。如果 fromCachetrue,则说明数据来自缓存,可能已过时或不完整。如果 fromCachefalse,则说明数据是完整的,并且与服务器上的最新更新保持一致。

默认情况下,如果只有 SnapshotMetadata 发生更改,则不会引发任何事件。如果您依赖于 fromCache 值,请在附加监听处理程序时指定 includeMetadataChanges 监听选项。

Web 版本 9

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);
    });
});

Web 版本 8

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);
      });
  });
Swift
// 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)")
}
Objective-C
// 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);
    }];

Java

db.collection("cities").whereEqualTo("state", "CA")
        .addSnapshotListener(MetadataChanges.INCLUDE, new EventListener<QuerySnapshot>() {
            @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);
                }

            }
        });

Kotlin+KTX

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")
            }
        }

获取离线数据

如果您在设备离线时获取了某个文档,Cloud Firestore 会从缓存中返回数据。

查询集合时,如果没有缓存的文档,系统会返回空结果。提取特定文档时,系统会返回错误。

查询离线数据

查询功能可与离线持久化功能结合使用。您可以直接使用 get 或通过监听(如前面的部分所述)来检索查询的结果。当设备离线时,您也可以创建新查询来查询本地持久化的数据,但这些查询最初只会查询缓存的文档。

停用和启用网络访问

您可以使用以下方法为您的 Cloud Firestore 客户端停用网络访问权限。网络访问权限停用后,所有快照监听器和文档请求都将从缓存中检索结果。写入操作会排入队列,直到网络访问权限重新启用。

Web 版本 9

import { disableNetwork } from "firebase/firestore";

await disableNetwork(db);
console.log("Network disabled!");
// Do offline actions
// ...

Web 版本 8

firebase.firestore().disableNetwork()
    .then(() => {
        // Do offline actions
        // ...
    });
Swift
Firestore.firestore().disableNetwork { (error) in
    // Do offline things
    // ...
}
Objective-C
[[FIRFirestore firestore] disableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do offline actions
  // ...
}];

Java

db.disableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do offline things
                // ...
            }
        });

Kotlin+KTX

db.disableNetwork().addOnCompleteListener {
    // Do offline things
    // ...
}

使用以下方法可重新启用网络访问权限:

Web 版本 9

import { enableNetwork } from "firebase/firestore";

await enableNetwork(db);
// Do online actions
// ...

Web 版本 8

firebase.firestore().enableNetwork()
    .then(() => {
        // Do online actions
        // ...
    });
Swift
Firestore.firestore().enableNetwork { (error) in
    // Do online things
    // ...
}
Objective-C
[[FIRFirestore firestore] enableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do online actions
  // ...
}];

Java

db.enableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do online things
                // ...
            }
        });

Kotlin+KTX

db.enableNetwork().addOnCompleteListener {
    // Do online things
    // ...
}