Aplikacje Firebase działają nawet wtedy, gdy aplikacja tymczasowo utraci połączenie z siecią. Firebase udostępnia też narzędzia do lokalnego przechowywania danych, zarządzania obecnością i obsługi opóźnień.
Trwałość na dysku
Aplikacje Firebase automatycznie obsługują tymczasowe przerwy w połączeniu z siecią. Dane w pamięci podręcznej są dostępne w trybie offline, a Firebase ponownie wysyła wszystkie zapisy po przywróceniu połączenia z siecią.
Gdy włączysz trwałość na dysku, aplikacja będzie zapisywać dane lokalnie na urządzeniu, dzięki czemu będzie mogła zachować stan w trybie offline, nawet jeśli użytkownik lub system operacyjny ponownie uruchomi aplikację.
Trwałość na dysku możesz włączyć za pomocą tylko 1 wiersza kodu.
Kotlin
Firebase.database.setPersistenceEnabled(true)
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
Zachowanie trwałości
Gdy włączysz trwałość, wszystkie dane, które klient Firebase Realtime Database zsynchronizowałby w trybie online, będą przechowywane na dysku i dostępne w trybie offline, nawet jeśli użytkownik lub system operacyjny ponownie uruchomi aplikację. Oznacza to, że aplikacja działa tak samo jak w trybie online, korzystając z danych lokalnych przechowywanych w pamięci podręcznej. Wywołania zwrotne odbiornika będą nadal wywoływane w przypadku aktualizacji lokalnych.
Klient Firebase Realtime Database automatycznie utrzymuje kolejkę wszystkich operacji zapisu wykonywanych, gdy aplikacja jest offline. Gdy trwałość jest włączona, ta kolejka jest też przechowywana 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 zostaną wysłane do serwera Firebase Realtime Database.
Jeśli aplikacja korzysta z uwierzytelniania Firebase, klient Firebase Realtime Database przechowuje token uwierzytelniania użytkownika między ponownymi uruchomieniami aplikacji. Jeśli token uwierzytelniania wygaśnie, gdy aplikacja jest offline, klient wstrzyma operacje zapisu do czasu, aż aplikacja ponownie uwierzytelni użytkownika. W przeciwnym razie operacje zapisu mogą się nie powieść z powodu reguł bezpieczeństwa.
Odświeżanie danych
Firebase Realtime Database synchronizuje i przechowuje lokalną kopię danych dla aktywnych odbiorników. Możesz też synchronizować określone lokalizacje in sync.
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 odwołanie nie ma aktywnych odbiorników. Synchronizację możesz wyłączyć za pomocą tego wiersza kodu.
Kotlin
scoresRef.keepSynced(false)
Java
scoresRef.keepSynced(false);
Domyślnie w pamięci podręcznej przechowywane jest 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 najrzadziej. Dane, które są synchronizowane, nie są usuwane z pamięci podręcznej.
Wykonywanie zapytań o dane w trybie offline
Firebase Realtime Database przechowuje dane zwrócone przez zapytanie do wykorzystania w trybie offline. W przypadku zapytań utworzonych w trybie offline, Firebase Realtime Database nadal działa w przypadku 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 z siecią zostanie przywrócone, dane zostaną wczytane i będą odzwierciedlać zapytanie.
Ten kod wysyła zapytanie o 4 ostatnie elementy w Firebase Realtime Database zawierającej wyniki.
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 traci połączenie, przechodzi w tryb offline i ponownie uruchamia aplikację. W trybie offline aplikacja 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 zapytaniu powyżej.
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” (dodano element podrzędny) dla 2 dinozaurów z najwyższymi wynikami, korzystając z trwałej pamięci podręcznej. Nie wywoła jednak zdarzenia „value” (wartość), ponieważ aplikacja nigdy nie wykonała tego zapytania w trybie online.
Jeśli aplikacja poprosi o 6 ostatnich elementów w trybie offline, od razu otrzyma zdarzenia „child added” (dodano element podrzędny) dla 4 elementów w pamięci podręcznej. Gdy urządzenie wróci do trybu online, klient Firebase Realtime Database zsynchronizuje się z serwerem i otrzyma 2 ostatnie zdarzenia „child added” (dodano element podrzędny) oraz zdarzenie „value” (wartość) dla aplikacji.
Obsługa transakcji w trybie offline
Wszystkie transakcje wykonywane, gdy aplikacja jest offline, są umieszczane w kolejce. Gdy aplikacja odzyska połączenie z siecią, transakcje zostaną wysłane do serwera Realtime Database.
Zarządzanie obecnością
W aplikacjach czasu rzeczywistego 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.
Klienci bazy danych Firebase udostępniają proste elementy pierwotne, których możesz użyć do zapisywania w bazie danych, gdy klient rozłączy się z serwerami bazy danych Firebase. Te aktualizacje występują niezależnie od tego, czy klient rozłączy się prawidłowo, czy nie, więc możesz na nich polegać w zakresie zwalniania miejsca na dane, nawet jeśli połączenie zostanie przerwane lub klient ulegnie awarii. Po rozłączeniu można wykonywać wszystkie operacje zapisu, w tym ustawianie, aktualizowanie i usuwanie.
Oto prosty przykład zapisywania danych po rozłączeniu za pomocą elementu pierwotnego
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 funkcja 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. Następnie serwer
monitoruje połączenie. Jeśli w dowolnym momencie połączenie zostanie przerwane lub zostanie
aktywnie zamknięte przez klienta Realtime Database, serwer ponownie sprawdzi zabezpieczenia (aby upewnić się, że operacja jest nadal prawidłowa), a następnie wywoła
zdarzenie.
Aplikacja może użyć wywołania zwrotnego w operacji zapisu
aby upewnić się, że onDisconnect została prawidłowo dołączona:
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()); } } });
Zdarzenie onDisconnect można też anulować, wywołując metodę .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ą przydaje się, gdy aplikacja
wie, czy jest online, czy 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 wartość /.info/connected jako false, nie ma
gwarancji, że inny klient też odczyta wartość false.
W Androidzie Firebase automatycznie zarządza stanem połączenia, aby zmniejszyć zużycie przepustowości i wykorzystanie baterii. Gdy klient nie ma aktywnych odbiorników,
oczekujących operacji zapisu ani onDisconnect
operacji i nie jest wyraźnie rozłączony przez
goOffline metodę,
Firebase zamyka połączenie po 60 sekundach bezczynności.
Obsługa opóźnień
Sygnatury czasowe serwera
Serwery Firebase Realtime Database udostępniają mechanizm wstawiania
sygnatur czasowych generowanych na serwerze jako danych. Ta funkcja w połączeniu z
onDisconnect, ułatwia niezawodne zapisywanie czasu, w którym klient Realtime Database został rozłączony:
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);
Różnica czasu
Chociaż firebase.database.ServerValue.TIMESTAMP jest znacznie dokładniejsza i preferowana w przypadku większości operacji odczytu i zapisu, czasami może się przydać oszacowanie różnicy czasu między zegarem klienta a serwerami Firebase Realtime Database. Możesz dołączyć wywołanie zwrotne do lokalizacji /.info/serverTimeOffset aby uzyskać wartość w milisekundach, którą Firebase Realtime Database klienci dodają do lokalnego czasu (czas epoki w milisekundach), aby oszacować czas serwera. Pamiętaj, że na dokładność tego przesunięcia może wpływać opóźnienie sieci, dlatego przydaje się ono głównie do wykrywania dużych (> 1 sekunda) różnic w czasie zegara.
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 zarządzania 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 true, gdy przechodzą do trybu online, a 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łączania w kolejce, zanim użytkownik zostanie oznaczony jako online, aby uniknąć sytuacji wyścigu, gdy połączenie sieciowe klienta zostanie utracone, 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"); } });