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

在 Android 上啟用離線功能

即使您的應用暫時失去網絡連接,Firebase 應用也能正常工作。此外,Firebase 提供了用於在本地保存數據、管理狀態和處理延遲的工具。

磁盤持久性

Firebase 應用會自動處理臨時網絡中斷。離線時緩存數據可用,Firebase 會在網絡連接恢復後重新發送任何寫入。

當您啟用磁盤持久性時,您的應用程序會將數據本地寫入設備,以便您的應用程序可以在脫機時保持狀態,即使用戶或操作系統重新啟動應用程序也是如此。

您只需一行代碼即可啟用磁盤持久性。

爪哇

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

科特林+KTX

Firebase.database.setPersistenceEnabled(true)

持久性行為

通過啟用持久性,即使用戶或操作系統重新啟動應用程序,Firebase 實時數據庫客戶端在線時同步的任何數據仍會保留在磁盤上並可離線使用。這意味著您的應用程序通過使用存儲在緩存中的本地數據可以像在線一樣工作。偵聽器回調將繼續觸發本地更新。

Firebase 實時數據庫客戶端會自動保留在您的應用離線時執行的所有寫入操作的隊列。啟用持久性後,此隊列也會持久化到磁盤,因此當用戶或操作系統重新啟動應用程序時,您的所有寫入都可用。當應用重新連接時,所有操作都會發送到 Firebase 實時數據庫服務器。

如果應用程序使用火力地堡認證,在火力地堡實時數據庫客戶端仍然存在,用戶的身份驗證跨應用程序重新啟動令牌。如果 auth 令牌在您的應用離線時過期,客戶端將暫停寫入操作,直到您的應用重新對用戶進行身份驗證,否則寫入操作可能會因安全規則而失敗。

保持數據新鮮

Firebase 實時數據庫同步並存儲活動偵聽器的本地數據副本。此外,您可以使特定位置保持同步。

爪哇

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.keepSynced(true);

科特林+KTX

val scoresRef = Firebase.database.getReference("scores")
scoresRef.keepSynced(true)

Firebase 實時數據庫客戶端會自動下載這些位置的數據並保持同步,即使參考沒有活動的偵聽器。您可以使用以下代碼行關閉同步。

爪哇

scoresRef.keepSynced(false);

科特林+KTX

scoresRef.keepSynced(false)

默認情況下,會緩存 10MB 先前同步的數據。這對於大多數應用程序來說應該足夠了。如果緩存超出其配置的大小,Firebase 實時數據庫會清除最近最少使用的數據。保持同步的數據不會從緩存中清除。

離線查詢數據

Firebase 實時數據庫存儲查詢返回的數據以供離線使用。對於離線時構建的查詢,Firebase 實時數據庫會繼續為之前加載的數據工作。如果請求的數據尚未加載,Firebase 實時數據庫會從本地緩存加載數據。當網絡連接再次可用時,數據將加載並將反映查詢。

例如,此代碼查詢 Firebase 實時分數數據庫中的最後四項

爪哇

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) {
        Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }

    // ...
});

科特林+KTX

val scoresRef = Firebase.database.getReference("scores")
scoresRef.orderByValue().limitToLast(4).addChildEventListener(object : ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) {
        Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}")
    }

    // ...
})

假設用戶失去連接、下線並重新啟動應用程序。在仍處於離線狀態時,該應用程序會從同一位置查詢最後兩個項目。此查詢將成功返回最後兩項,因為應用程序已加載上述查詢中的所有四項。

爪哇

scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) {
        Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }

    // ...
});

科特林+KTX

scoresRef.orderByValue().limitToLast(2).addChildEventListener(object : ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) {
        Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}")
    }

    // ...
})

在前面的示例中,Firebase 實時數據庫客戶端使用持久緩存為得分最高的兩隻恐龍引發“添加子項”事件。但它不會引發 'value' 事件,因為該應用程序從未在在線時執行過該查詢。

如果應用程序在離線時請求最後六個項目,它會立即獲得四個緩存項目的“添加子項”事件。當設備重新上線時,Firebase 實時數據庫客戶端與服務器同步並獲取應用的最後兩個“添加子項”和“值”事件。

離線處理交易

應用程序離線時執行的任何事務都會排隊。一旦應用程序重新獲得網絡連接,事務就會發送到實時數據庫服務器。

管理存在

在實時應用程序中,檢測客戶端何時連接和斷開連接通常很有用。例如,您可能希望在用戶的客戶端斷開連接時將其標記為“離線”。

Firebase 數據庫客戶端提供簡單的原語,當客戶端與 Firebase 數據庫服務器斷開連接時,您可以使用這些原語寫入數據庫。無論客戶端是否完全斷開連接,這些更新都會發生,因此即使連接斷開或客戶端崩潰,您也可以依靠它們來清理數據。所有的寫操作,包括設置、更新和刪除,都可以在斷開連接時進行。

下面是通過使用寫在斷開數據的一個簡單的例子onDisconnect原始:

爪哇

DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");

科特林+KTX

val presenceRef = Firebase.database.getReference("disconnectmessage")
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!")

onDisconnect 如何工作

當你建立一個onDisconnect()操作,該操作所居住的火力地堡實時數據庫服務器上。服務器會檢查安全性以確保用戶可以執行請求的寫入事件,並在無效時通知您的應用程序。然後服務器監視連接。如果在任何時候連接超時或被實時數據庫客戶端主動關閉,服務器將再次檢查安全性(以確保操作仍然有效),然後調用該事件。

您的應用程序可以使用回調的寫操作,以確保onDisconnect被正確安裝:

爪哇

presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() {
    @Override
    public void onComplete(DatabaseError error, @NonNull DatabaseReference reference) {
        if (error != null) {
            Log.d(TAG, "could not establish onDisconnect event:" + error.getMessage());
        }
    }
});

科特林+KTX

presenceRef.onDisconnect().removeValue { error, reference ->
    error?.let {
        Log.d(TAG, "could not establish onDisconnect event: ${error.message}")
    }
}

一個onDisconnect事件也可以通過調用取消.cancel()

爪哇

OnDisconnect onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.setValue("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();

科特林+KTX

val onDisconnectRef = presenceRef.onDisconnect()
onDisconnectRef.setValue("I disconnected")
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel()

檢測連接狀態

對於許多與在線狀態相關的功能,您的應用程序知道它何時在線或離線非常有用。火力地堡實時數據庫提供了一個特殊的位置,在/.info/connected這是每一次火力實時數據庫客戶端的連接狀態的變化更新。下面是一個例子:

爪哇

DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            Log.d(TAG, "connected");
        } else {
            Log.d(TAG, "not connected");
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Log.w(TAG, "Listener was cancelled");
    }
});

科特林+KTX

val connectedRef = Firebase.database.getReference(".info/connected")
connectedRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val connected = snapshot.getValue(Boolean::class.java) ?: false
        if (connected) {
            Log.d(TAG, "connected")
        } else {
            Log.d(TAG, "not connected")
        }
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled")
    }
})

/.info/connected是因為該值依賴於客戶端的狀態未實時數據庫客戶端之間的同步的一個布爾值。換句話說,如果一個客戶端讀取/.info/connected為假,這是不能保證一個單獨的客戶端也將讀假。

在 Android 上,Firebase 會自動管理連接狀態以減少帶寬和電池使用量。當客戶端不具有活性的聽眾,沒有未決寫入或onDisconnect操作,而且未明確通過斷開goOffline方法,火力地堡關閉不活動的60秒後的連接。

處理延遲

服務器時間戳

Firebase 實時數據庫服務器提供了一種將服務器上生成的時間戳作為數據插入的機制。此功能,結合onDisconnect ,提供了一種簡單的方法來可靠地記下在哪個實時數據庫客戶端斷開時間:

爪哇

DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);

科特林+KTX

val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline")
userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)

時鐘偏移

雖然firebase.database.ServerValue.TIMESTAMP是大多數讀/寫操作更加準確,並且最好,它可以偶爾來估計客戶端的時鐘偏斜關於火力地堡實時數據庫的服務器非常有用。您可以將回調到該位置/.info/serverTimeOffset獲得的價值,以毫秒為單位火力地堡實時數據庫客戶端添加到本地報告的時間(以毫秒為單位劃時代的時間)來估計服務器時間。請注意,此偏移量的準確性可能會受到網絡延遲的影響,因此主要用於發現時鐘時間中的大(> 1 秒)差異。

爪哇

DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset");
offsetRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        double offset = snapshot.getValue(Double.class);
        double estimatedServerTimeMs = System.currentTimeMillis() + offset;
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Log.w(TAG, "Listener was cancelled");
    }
});

科特林+KTX

val offsetRef = Firebase.database.getReference(".info/serverTimeOffset")
offsetRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val offset = snapshot.getValue(Double::class.java) ?: 0.0
        val estimatedServerTimeMs = System.currentTimeMillis() + offset
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled")
    }
})

示例狀態應用程序

通過將斷開連接操作與連接狀態監控和服務器時間戳相結合,您可以構建用戶呈現系統。在該系統中,每個用戶將數據存儲在一個數據庫位置以指示實時數據庫客戶端是否在線。客戶端在聯機時將此位置設置為 true,並在斷開連接時設置時間戳。此時間戳表示給定用戶最後一次在線。

請注意,您的應用應在用戶被標記為在線之前將斷開連接操作排隊,以避免在客戶端的網絡連接在兩個命令都可以發送到服務器之前丟失的情況下出現任何爭用情況。

這是一個簡單的用戶呈現系統:

爪哇

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline");

final DatabaseReference connectedRef = database.getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            DatabaseReference con = myConnectionsRef.push();

            // When this device disconnects, remove it
            con.onDisconnect().removeValue();

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);

            // Add this device to my connections list
            // this value could contain info about the device or a timestamp too
            con.setValue(Boolean.TRUE);
        }
    }

    @Override
    public void onCancelled(DatabaseError error) {
        Log.w(TAG, "Listener was cancelled at .info/connected");
    }
});

科特林+KTX

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
val database = Firebase.database
val myConnectionsRef = database.getReference("users/joe/connections")

// Stores the timestamp of my last disconnect (the last time I was seen online)
val lastOnlineRef = database.getReference("/users/joe/lastOnline")

val connectedRef = database.getReference(".info/connected")
connectedRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        val connected = snapshot.getValue<Boolean>() ?: false
        if (connected) {
            val con = myConnectionsRef.push()

            // When this device disconnects, remove it
            con.onDisconnect().removeValue()

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)

            // Add this device to my connections list
            // this value could contain info about the device or a timestamp too
            con.setValue(java.lang.Boolean.TRUE)
        }
    }

    override fun onCancelled(error: DatabaseError) {
        Log.w(TAG, "Listener was cancelled at .info/connected")
    }
})