Włączanie funkcji offline w systemie Android

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Aplikacje Firebase działają nawet wtedy, gdy Twoja aplikacja tymczasowo utraci połączenie sieciowe. Ponadto Firebase udostępnia narzędzia do lokalnego utrwalania danych, zarządzania obecnością i obsługi opóźnień.

Trwałość dysku

Aplikacje Firebase automatycznie obsługują tymczasowe przerwy w działaniu sieci. Dane z pamięci podręcznej są dostępne w trybie offline, a Firebase ponownie wysyła wszystkie zapisy po przywróceniu łączności sieciowej.

Po włączeniu utrwalania dysku aplikacja zapisuje dane lokalnie na urządzeniu, dzięki czemu aplikacja może zachować stan w trybie offline, nawet jeśli użytkownik lub system operacyjny ponownie uruchomi aplikację.

Trwałość dysku można włączyć za pomocą tylko jednego wiersza kodu.

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

Kotlin+KTX

Firebase.database.setPersistenceEnabled(true)

Zachowanie wytrwałości

Po włączeniu trwałości wszelkie dane, które klient bazy danych czasu rzeczywistego Firebase zsynchronizowałby w trybie online, pozostają na dysku i są dostępne w trybie offline, nawet po ponownym uruchomieniu aplikacji przez użytkownika lub system operacyjny. Oznacza to, że Twoja aplikacja działa tak, jakby działała online, korzystając z danych lokalnych przechowywanych w pamięci podręcznej. Wywołania zwrotne słuchacza będą nadal uruchamiane w przypadku lokalnych aktualizacji.

Klient bazy danych czasu rzeczywistego Firebase automatycznie przechowuje w kolejce wszystkie operacje zapisu, które są wykonywane, gdy aplikacja jest w trybie offline. Gdy trwałość jest włączona, ta kolejka jest również utrwalana na dysku, dzięki czemu wszystkie zapisy są dostępne po ponownym uruchomieniu aplikacji przez użytkownika lub system operacyjny. Gdy aplikacja odzyska łączność, wszystkie operacje są wysyłane na serwer bazy danych czasu rzeczywistego Firebase.

Jeśli Twoja aplikacja korzysta z uwierzytelniania Firebase , klient bazy danych czasu rzeczywistego Firebase zachowuje token uwierzytelniania użytkownika po ponownym uruchomieniu aplikacji. Jeśli token uwierzytelniania wygaśnie, gdy aplikacja jest w trybie offline, klient wstrzymuje operacje zapisu do momentu ponownego uwierzytelnienia użytkownika przez aplikację, w przeciwnym razie operacje zapisu mogą zakończyć się niepowodzeniem z powodu reguł zabezpieczeń.

Utrzymywanie aktualności danych

Baza danych czasu rzeczywistego Firebase synchronizuje i przechowuje lokalną kopię danych aktywnych słuchaczy. Ponadto możesz synchronizować określone lokalizacje.

Java

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

Kotlin+KTX

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

Klient bazy danych czasu rzeczywistego Firebase automatycznie pobiera dane w tych lokalizacjach i synchronizuje je, nawet jeśli odwołanie nie ma aktywnych detektorów. Możesz ponownie wyłączyć synchronizację za pomocą następującego wiersza kodu.

Java

scoresRef.keepSynced(false);

Kotlin+KTX

scoresRef.keepSynced(false)

Domyślnie buforowane jest 10 MB wcześniej zsynchronizowanych danych. To powinno wystarczyć dla większości aplikacji. Jeśli pamięć podręczna przekroczy skonfigurowany rozmiar, Baza danych czasu rzeczywistego Firebase usuwa dane, które były ostatnio używane. Dane, które są zsynchronizowane, nie są usuwane z pamięci podręcznej.

Odpytywanie danych w trybie offline

Baza danych czasu rzeczywistego Firebase przechowuje dane zwrócone z zapytania do użytku w trybie offline. W przypadku zapytań utworzonych w trybie offline baza danych czasu rzeczywistego Firebase nadal działa dla wcześniej załadowanych danych. Jeśli żądane dane nie zostały załadowane, baza danych czasu rzeczywistego Firebase ładuje dane z lokalnej pamięci podręcznej. Gdy łączność sieciowa będzie ponownie dostępna, dane zostaną załadowane i będą odzwierciedlać zapytanie.

Na przykład ten kod pyta o ostatnie cztery elementy w bazie danych Firebase Realtime zawierającej wyniki

Java

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

    // ...
});

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

    // ...
})

Załóżmy, że użytkownik traci połączenie, przechodzi w tryb offline i ponownie uruchamia aplikację. W trybie offline aplikacja wysyła zapytanie o dwa ostatnie elementy z tej samej lokalizacji. To zapytanie pomyślnie zwróci dwa ostatnie elementy, ponieważ aplikacja załadowała wszystkie cztery elementy w powyższym zapytaniu.

Java

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

    // ...
});

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

    // ...
})

W poprzednim przykładzie klient bazy danych czasu rzeczywistego Firebase wywołuje zdarzenia „dodania dziecka” dla dwóch dinozaurów o najwyższym wyniku, korzystając z trwałej pamięci podręcznej. Ale nie wywoła zdarzenia „value”, ponieważ aplikacja nigdy nie wykonała tego zapytania w trybie online.

Jeśli aplikacja zażądałaby ostatnich sześciu elementów w trybie offline, od razu otrzymałaby zdarzenia „dodania dziecka” dla czterech elementów w pamięci podręcznej. Gdy urządzenie wróci do trybu online, klient bazy danych czasu rzeczywistego Firebase synchronizuje się z serwerem i otrzymuje dwa ostatnie zdarzenia „dodania elementów podrzędnych” i „wartość” dla aplikacji.

Obsługa transakcji offline

Wszelkie transakcje wykonywane, gdy aplikacja jest w trybie offline, są umieszczane w kolejce. Gdy aplikacja odzyska łączność z siecią, transakcje są wysyłane do serwera bazy danych czasu rzeczywistego.

Zarządzanie obecnością

W aplikacjach czasu rzeczywistego często przydatne jest wykrywanie, kiedy klienci łączą się i rozłączają. Na przykład możesz chcieć oznaczyć użytkownika jako „offline”, gdy jego klient się rozłączy.

Klienci bazy danych Firebase udostępniają proste operacje podstawowe, których można używać do zapisywania w bazie danych, gdy klient odłącza się od serwerów bazy danych Firebase. Aktualizacje te pojawiają się bez względu na to, czy klient rozłącza się czysto, czy nie, dzięki czemu można na nich polegać w celu oczyszczenia danych nawet w przypadku zerwania połączenia lub awarii klienta. Wszystkie operacje zapisu, w tym ustawianie, aktualizowanie i usuwanie, można wykonać po rozłączeniu.

Oto prosty przykład zapisywania danych po rozłączeniu przy użyciu operacji podstawowej onDisconnect :

Java

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

Kotlin+KTX

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

Jak działa onDisconnect

Po ustanowieniu operacji onDisconnect() jest ona aktywna na serwerze bazy danych czasu rzeczywistego Firebase. Serwer sprawdza zabezpieczenia, aby upewnić się, że użytkownik może wykonać żądane zdarzenie zapisu, i informuje aplikację, jeśli jest ono nieprawidłowe. Serwer następnie monitoruje połączenie. Jeśli w dowolnym momencie połączenie zostanie przekroczone lub zostanie aktywnie zamknięte przez klienta Realtime Database, serwer ponownie sprawdza zabezpieczenia (aby upewnić się, że operacja jest nadal prawidłowa), a następnie wywołuje zdarzenie.

Twoja aplikacja może użyć wywołania zwrotnego w operacji zapisu, aby upewnić się, że onDisconnect został prawidłowo dołączony:

Java

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

Kotlin+KTX

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

Zdarzenie onDisconnect można również anulować, wywołując .cancel() :

Java

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

Kotlin+KTX

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

Wykrywanie stanu połączenia

W przypadku wielu funkcji związanych z obecnością przydatne jest, aby aplikacja wiedziała, kiedy jest online lub offline. Baza danych czasu rzeczywistego Firebase udostępnia specjalną lokalizację pod adresem /.info/connected , która jest aktualizowana za każdym razem, gdy zmienia się stan połączenia klienta Bazy danych czasu rzeczywistego Firebase. Oto przykład:

Java

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

Kotlin+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 to wartość logiczna, która nie jest synchronizowana między klientami Bazy danych czasu rzeczywistego, ponieważ jest zależna od stanu klienta. Innymi słowy, jeśli jeden klient odczyta /.info/connected jako fałsz, nie gwarantuje to, że oddzielny klient również odczyta fałsz.

W Androidzie Firebase automatycznie zarządza stanem połączenia, aby zmniejszyć przepustowość i zużycie baterii. Gdy klient nie ma aktywnych odbiorników, żadnych oczekujących operacji zapisu lub onDisconnect i nie jest jawnie rozłączony przez metodę goOffline , Firebase zamyka połączenie po 60 sekundach braku aktywności.

Obsługa opóźnienia

Sygnatury czasowe serwera

Serwery bazy danych czasu rzeczywistego Firebase udostępniają mechanizm wstawiania jako danych znaczników czasu wygenerowanych na serwerze. Ta funkcja, w połączeniu z onDisconnect , zapewnia łatwy sposób na niezawodne zapisanie czasu, w którym klient Bazy danych czasu rzeczywistego rozłączył się:

Java

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

Kotlin+KTX

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

Przekrzywiony zegar

Chociaż firebase.database.ServerValue.TIMESTAMP jest znacznie dokładniejsza i preferowana w przypadku większości operacji odczytu/zapisu, czasami może być przydatne oszacowanie przesunięcia zegara klienta w odniesieniu do serwerów bazy danych czasu rzeczywistego Firebase. Możesz dołączyć wywołanie zwrotne do lokalizacji /.info/serverTimeOffset , aby uzyskać wartość w milisekundach, którą klienci bazy danych czasu rzeczywistego Firebase dodają do lokalnego czasu raportowania (czas epoki w milisekundach) w celu oszacowania czasu serwera. Należy zauważyć, że na dokładność tego przesunięcia może mieć wpływ opóźnienie sieci, dlatego jest to przydatne przede wszystkim do wykrywania dużych (> 1 sekunda) rozbieżności w czasie zegara.

Java

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

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

Przykładowa aplikacja obecności

Łącząc operacje rozłączenia z monitorowaniem stanu połączenia i znacznikami czasu serwera, można zbudować system obecności użytkownika. W tym systemie każdy użytkownik przechowuje dane w lokalizacji bazy danych, aby wskazać, czy klient Bazy danych czasu rzeczywistego jest w trybie online. Klienci ustawiają tę lokalizację na wartość true, gdy przechodzą do trybu online, i znacznik czasu, gdy się rozłączają. Ten znacznik czasu wskazuje, kiedy ostatnio dany użytkownik był online.

Należy pamiętać, że aplikacja powinna kolejkować operacje rozłączania przed oznaczeniem użytkownika w trybie online, aby uniknąć sytuacji wyścigu w przypadku utraty połączenia sieciowego klienta przed wysłaniem obu poleceń do serwera.

Oto prosty system obecności użytkownika:

Java

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

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