Aplikacje Firebase działają nawet wtedy, gdy tymczasowo utracą połączenie sieciowe. Dodatkowo Firebase udostępnia narzędzia do lokalizacji 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 połączenia sieciowego.
Gdy włączysz trwałość dysku, aplikacja będzie zapisywać dane lokalnie na urządzeniu, dzięki czemu będzie mogła zachować stan, gdy będzie offline, nawet jeśli użytkownik lub system operacyjny ponownie ją uruchomi.
Możesz włączyć trwałość dysku za pomocą tylko jednego wiersza kodu.
Kotlin
Firebase.database.setPersistenceEnabled(true)
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
Zachowanie trwałości
Po włączeniu trwałości wszystkie dane, które klient Firebase Realtime Database synchronizuje w trybie online, są zapisywane na dysku i dostępne offline, nawet gdy użytkownik lub system operacyjny ponownie uruchomi aplikację. Oznacza to, że aplikacja działa tak, jakby była online, korzystając z danych lokalnych przechowywanych w pamięci podręcznej. Wywołania zwrotne detektora będą nadal uruchamiane w przypadku aktualizacji lokalnych.
Klient Firebase Realtime Database automatycznie utrzymuje kolejkę wszystkich operacji zapisu, które są wykonywane, gdy aplikacja jest offline. Gdy trwałość jest włączona, ta kolejka jest również zapisywana na dysku, dzięki czemu wszystkie zapisy są dostępne, gdy użytkownik lub system operacyjny ponownie uruchomi aplikację. Gdy aplikacja odzyska połączenie, wszystkie operacje są wysyłane na serwer Firebase Realtime Database.
Jeśli aplikacja korzysta z Uwierzytelniania Firebase, klient Firebase Realtime Database zachowuje token uwierzytelniania użytkownika po ponownym uruchomieniu aplikacji. Jeśli token uwierzytelniania wygaśnie, gdy aplikacja będzie offline, klient wstrzyma operacje zapisu do czasu ponownego uwierzytelnienia użytkownika przez aplikację. W przeciwnym razie operacje zapisu mogą się nie powieść z powodu reguł zabezpieczeń.
Aktualizowanie danych
Firebase Realtime Database synchronizuje i przechowuje lokalną kopię danych aktywnych detektorów. Możesz też synchronizować konkretne lokalizacje.
Kotlin
val scoresRef = Firebase.database.getReference("scores") scoresRef.keepSynced(true)
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.keepSynced(true);
Klient Firebase Realtime Database automatycznie pobiera dane z tych lokalizacji i synchronizuje je, nawet jeśli nie ma aktywnych detektorów odwołania. Możesz wyłączyć synchronizację za pomocą tego wiersza kodu.
Kotlin
scoresRef.keepSynced(false)
Java
scoresRef.keepSynced(false);
Domyślnie w pamięci podręcznej jest przechowywane 10 MB wcześniej zsynchronizowanych danych. Powinno to wystarczyć w przypadku większości aplikacji. Jeśli pamięć podręczna przekroczy skonfigurowany rozmiar, Firebase Realtime Database usunie dane, które były używane najdawniej. Dane, które są synchronizowane, nie są usuwane z pamięci podręcznej.
Zapytania o dane offline
Firebase Realtime Database przechowuje dane zwrócone przez zapytanie do użytku w trybie offline. W przypadku zapytań utworzonych w trybie offline Firebase Realtime Database nadal działa w odniesieniu do wcześniej wczytanych danych. Jeśli żądane dane nie zostały wczytane, Firebase Realtime Database wczytuje dane z lokalnej pamięci podręcznej. Gdy połączenie sieciowe będzie ponownie dostępne, dane zostaną wczytane i będą odzwierciedlać zapytanie.
Na przykład ten kod wysyła zapytanie o ostatnie 4 elementy w Firebase Realtime Database wyników:
Kotlin
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}") } // ... })
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()); } // ... });
Załóżmy, że użytkownik utraci połączenie, przejdzie w tryb offline i ponownie uruchomi aplikację. Aplikacja, nadal w trybie offline, wysyła zapytanie o 2 ostatnie elementy z tej samej lokalizacji. To zapytanie zwróci 2 ostatnie elementy, ponieważ aplikacja wczytała wszystkie 4 elementy w powyższym zapytaniu.
Kotlin
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}") } // ... })
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()); } // ... });
W poprzednim przykładzie klient Firebase Realtime Database wywołuje zdarzenia „child added” dla 2 dinozaurów z najwyższymi wynikami, korzystając z trwałej pamięci podręcznej. Nie wywoła jednak zdarzenia „value”, ponieważ aplikacja nigdy nie wykonała tego zapytania w trybie online.
Jeśli aplikacja zażąda 6 ostatnich elementów w trybie offline, od razu otrzyma zdarzenia „child added” (dodano element podrzędny) dotyczące 4 elementów z pamięci podręcznej. Gdy urządzenie ponownie połączy się z internetem, klient Firebase Realtime Database zsynchronizuje się z serwerem i otrzyma 2 ostatnie zdarzenia „child added” oraz zdarzenie „value” dla aplikacji.
Obsługa transakcji offline
Wszystkie transakcje wykonywane, gdy aplikacja jest offline, są umieszczane w kolejce. Gdy aplikacja odzyska połączenie sieciowe, transakcje zostaną wysłane na serwer Realtime Database.
Zarządzanie obecnością
W aplikacjach działających w czasie rzeczywistym często przydaje się wykrywanie, kiedy klienci łączą się i rozłączają. Możesz na przykład oznaczyć użytkownika jako „offline”, gdy jego klient się rozłączy.
Klienty bazy danych Firebase udostępniają proste elementy podstawowe, których można używać do zapisywania w bazie danych, gdy klient rozłączy się z serwerami bazy danych Firebase. Te aktualizacje są wykonywane niezależnie od tego, czy klient rozłączy się prawidłowo, czy nie, więc możesz na nich polegać, aby wyczyścić dane, nawet jeśli połączenie zostanie przerwane lub klient ulegnie awarii. Wszystkie operacje zapisu, w tym ustawianie, aktualizowanie i usuwanie, można wykonywać po rozłączeniu.
Oto prosty przykład zapisu danych po rozłączeniu przy użyciu elementu podstawowego onDisconnect:
Kotlin
val presenceRef = Firebase.database.getReference("disconnectmessage") // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!")
Java
DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!");
Jak działa onDisconnect
Gdy utworzysz operację onDisconnect(), będzie ona działać na serwerze Firebase Realtime Database. 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 monitoruje połączenie. Jeśli w dowolnym momencie upłynie limit czasu połączenia lub zostanie ono aktywnie zamknięte przez klienta Realtime Database, serwer ponownie sprawdzi zabezpieczenia (aby upewnić się, że operacja jest nadal ważna), a następnie wywoła zdarzenie.
Aplikacja może użyć wywołania zwrotnego w operacji zapisu, aby upewnić się, że obiekt onDisconnect został prawidłowo dołączony:
Kotlin
presenceRef.onDisconnect().removeValue { error, reference -> error?.let { Log.d(TAG, "could not establish onDisconnect event: ${error.message}") } }
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()); } } });
Wydarzenie onDisconnect można też anulować, wywołując funkcję .cancel():
Kotlin
val onDisconnectRef = presenceRef.onDisconnect() onDisconnectRef.setValue("I disconnected") // ... // some time later when we change our minds // ... onDisconnectRef.cancel()
Java
OnDisconnect 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, a kiedy offline. Firebase Realtime Database udostępnia specjalną lokalizację /.info/connected, która jest aktualizowana za każdym razem, gdy zmienia się stan połączenia klienta Firebase Realtime Database. Oto przykład:
Kotlin
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") } })
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"); } });
/.info/connected to wartość logiczna, która nie jest synchronizowana między klientami Realtime Database, ponieważ zależy od stanu klienta. Innymi słowy, jeśli jeden klient odczyta /.info/connected jako „fałsz”, nie ma gwarancji, że inny klient również odczyta tę wartość jako „fałsz”.
Na Androidzie Firebase automatycznie zarządza stanem połączenia, aby zmniejszyć wykorzystanie przepustowości i baterii. Gdy klient nie ma aktywnych detektorów, oczekujących operacji zapisu ani operacji onDisconnect i nie został wyraźnie rozłączony za pomocą metody goOffline, Firebase zamyka połączenie po 60 sekundach braku aktywności.
Obsługa czasu oczekiwania
Sygnatury czasowe serwera
Serwery Firebase Realtime Database udostępniają mechanizm wstawiania sygnatur czasowych wygenerowanych na serwerze jako danych. Ta funkcja w połączeniu z onDisconnect umożliwia łatwe i niezawodne odnotowanie czasu rozłączenia klienta Realtime Database:
Kotlin
val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline") userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)
Java
DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
Odchylenie zegara
Chociaż firebase.database.ServerValue.TIMESTAMP jest znacznie dokładniejszy i preferowany w przypadku większości operacji odczytu/zapisu, czasami przydatne może być oszacowanie przesunięcia zegara klienta względem serwerów Firebase Realtime Database. Możesz dołączyć wywołanie zwrotne do lokalizacji /.info/serverTimeOffset, aby uzyskać wartość w milisekundach, którą klienci Firebase Realtime Database dodają do lokalnego zgłoszonego czasu (czasu epoki w milisekundach), aby oszacować czas serwera. Pamiętaj, że na dokładność tego przesunięcia mogą wpływać opóźnienia sieci, dlatego jest ono przydatne głównie do wykrywania dużych (> 1 sekunda) rozbieżności w czasie zegarowym.
Kotlin
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") } })
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"); } });
Przykładowa aplikacja do wykrywania obecności
Łącząc operacje rozłączania z monitorowaniem stanu połączenia i sygnaturami czasowymi serwera, możesz utworzyć system obecności użytkowników. W tym systemie każdy użytkownik przechowuje dane w lokalizacji bazy danych, aby wskazać, czy klient Realtime Database jest online. Klienci ustawiają tę lokalizację na wartość „true”, gdy przechodzą do trybu online, i sygnaturę czasową, gdy się rozłączają. Ta sygnatura czasowa wskazuje, kiedy dany użytkownik był ostatnio online.
Pamiętaj, że aplikacja powinna umieścić operacje rozłączenia w kolejce, zanim użytkownik zostanie oznaczony jako online. Pozwoli to uniknąć sytuacji wyścigu, gdyby połączenie sieciowe klienta zostało przerwane, zanim oba polecenia zostaną wysłane do serwera.
Oto prosty system obecności użytkowników:
Kotlin
// 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") } })
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(@NonNull 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(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled at .info/connected"); } });