Włączanie funkcji offline na urządzeniach z Androidem

Aplikacje Firebase działają nawet wtedy, gdy aplikacja tymczasowo utraci połączenie z siecią połączenia. Firebase zapewnia też narzędzia do przechowywania danych lokalnie, zarządzania obecnością i czasu oczekiwania na obsługę.

Trwałość dysku

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

Gdy włączysz trwałość dysku, aplikacja będzie zapisywać dane lokalnie w na urządzeniu, dzięki czemu aplikacja może zachowywać stan offline, nawet jeśli użytkownik lub system operacyjny ponownie uruchomi aplikację.

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

Kotlin+KTX

Firebase.database.setPersistenceEnabled(true)

Java

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

Zachowanie trwałości

Jeśli włączysz trwałość, wszystkie dane przetwarzane przez klienta Firebase Realtime Database zostanie zsynchronizowany, gdy tryb online będzie zapisany na dysku i będzie dostępny w trybie offline, nawet wtedy, gdy użytkownik lub system operacyjny ponownie uruchomi aplikację. Oznacza to, że aplikacja działa tak samo jak online, używając 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 przechowuje kolejkę wszystkich zapisywanie operacji wykonywanych, gdy aplikacja jest offline. Gdy włączona jest trwałość, ta kolejka jest również utrwalana na dysku, więc wszystkie Twoich zapisów jest dostępne, gdy użytkownik lub system operacyjny spowoduje ponowne uruchomienie aplikacji. Gdy aplikacja odzyska połączenie, wszystkie operacje są wysyłane do serwera Firebase Realtime Database.

Jeśli aplikacja używa Uwierzytelnianie Firebase, klient Firebase Realtime Database kontynuuje uwierzytelnianie użytkownika token prywatności w przypadku ponownych uruchomień aplikacji. Jeśli token uwierzytelniania wygaśnie, gdy aplikacja będzie offline, klient zostanie wstrzymany operacji zapisu do czasu ponownego uwierzytelnienia użytkownika przez aplikację. W przeciwnym razie operacje zapisu mogą się nie powieść ze względu na reguły zabezpieczeń.

Dbanie o aktualność danych

Firebase Realtime Database synchronizuje i przechowuje lokalną kopię pliku w przypadku aktywnych detektorów. Możesz też zachować określone lokalizacje zsynchronizowane.

Kotlin+KTX

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 ze strony te lokalizacje i jest synchronizowana, nawet jeśli w pliku referencyjnym nie ma aktywnych detektorów. Synchronizację możesz wyłączyć ponownie, w tym wierszu.

Kotlin+KTX

scoresRef.keepSynced(false)

Java

scoresRef.keepSynced(false);

Domyślnie w pamięci podręcznej przechowywane jest 10 MB wcześniej zsynchronizowanych danych. Powinien to być która wystarcza do większości zastosowań. Jeśli pamięć podręczna przepełni swój skonfigurowany rozmiar, Firebase Realtime Database trwale usuwa dane, które były używane najdawniej. Zsynchronizowane dane nie są trwale usuwane z pamięci podręcznej.

Wysyłanie zapytań o dane offline

Firebase Realtime Database przechowuje w celu użycia dane zwrócone z zapytania gdy jesteś offline. W przypadku zapytań utworzonych w trybie offline Firebase Realtime Database nadal działa w przypadku danych wczytanych wcześniej. Jeśli żądane dane nie zostały wczytane, Firebase Realtime Database się wczyta z lokalnej pamięci podręcznej. Gdy połączenie sieciowe będzie znów dostępne, dane zostaną wczytane i będą odzwierciedlały zapytanie.

Na przykład ten kod wysyła zapytania dotyczące ostatnich cztery elementy w Firebase Realtime Database wyników

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

    // ...
})

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ę. Będąc w trybie offline, aplikacja wysyła zapytanie o ostatnie 2 elementy z w tej samej lokalizacji. To zapytanie zwróci 2 ostatnie elementy bo aplikacja wczytała wszystkie 4 elementy w powyższym zapytaniu.

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

    // ...
})

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 generuje „dodano dziecko” dla najlepszych wyników dwóch dinozaurów, wykorzystując utrwalonej pamięci podręcznej. Nie spowoduje to jednak zwiększenia „wartości” zdarzenia, bo aplikacja nigdy nie wykonał tego zapytania w trybie online.

Gdyby w trybie offline aplikacja zażądała ostatnich 6 elementów, otrzyma żądanie „dodano dziecko” dla czterech elementów w pamięci podręcznej. Gdy urządzenie powróci do trybu online, klient Firebase Realtime Database zsynchronizuje się z serwerem i otrzymuje dwa ostatnie atrybuty „podrzędne” oraz „value” zdarzeń dla aplikacji.

Obsługa transakcji offline

Wszystkie transakcje wykonane, gdy aplikacja jest offline, są umieszczane w kolejce. Gdy aplikacja odzyska połączenie z siecią, transakcje zostaną przesłane do serwer Realtime Database.

Zarządzanie obecnością

W aplikacjach działających w czasie rzeczywistym często przydaje się możliwość wykrywania, nawiąż i rozłącz. Możesz na przykład chcesz oznaczyć użytkownika jako użytkownika offline. gdy klient się rozłącza.

Klienty bazy danych Firebase udostępniają proste elementy podstawowe umożliwiające zapisywanie w bazie danych, gdy klient odłączy się od bazy danych Firebase serwerów. Te aktualizacje pojawiają się niezależnie od tego, czy klient się rozłącza, czy nie, Możesz na nich polegać, jeśli wyczyścisz dane nawet wtedy, gdy połączenie zostanie przerwane lub po awarii klienta. Wszystkie operacje zapisu, w tym ustawienia aktualizacji i usuwania można przeprowadzić po rozłączeniu.

Oto prosty przykład zapisywania danych po odłączeniu za pomocą polecenia Element podstawowy onDisconnect:

Kotlin+KTX

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 onOdłącz działa?

Gdy utworzysz operację onDisconnect(), działa na serwerze Firebase Realtime Database. Serwer sprawdza zabezpieczenia Upewnij się, że użytkownik może wykonać żądane zdarzenie zapisu oraz informuje aplikacji, jeśli jest ona nieprawidłowa. Serwer oraz monitoruje połączenie. jeśli w którymkolwiek momencie połączenie zostanie przekroczone lub będzie aktywnie zamknięte przez klienta Realtime Database, serwer sprawdza zabezpieczenia po raz drugi (aby upewnić się, że operacja jest nadal prawidłowa), a następnie wywołuje do zdarzenia.

Aplikacja może używać wywołania zwrotnego podczas zapisu aby upewnić się, że łącznik onDisconnect został prawidłowo podłączony:

Kotlin+KTX

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ć, dzwoniąc pod numer .cancel():

Kotlin+KTX

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

Wykrywam stan połączenia

W przypadku wielu funkcji związanych z obecnością opcja jest przydatna w aplikacji czy jest online czy offline. Firebase Realtime Database udostępnia specjalną lokalizację pod adresem /.info/connected, która jest aktualizowany za każdym razem, gdy stan połączenia klienta Firebase Realtime Database zmian. Oto przykład:

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

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 jest wartością logiczną, która nie jest zsynchronizowana między Realtime Database klientami, ponieważ wartość to w zależności od stanu klienta. Innymi słowy, jeśli jeden klient odczytuje /.info/connected jako fałsz, to nie jest zagwarantowanie, że oddzielny klient również odczyta wartość „false” (fałsz).

W Androidzie Firebase automatycznie zarządza stanem połączenia – zmniejsza przepustowość i wykorzystanie baterii. Jeśli klient nie ma aktywnych detektorów, brak oczekującego zapisu lub onDisconnect i nie jest wyraźnie odłączony przez funkcję goOffline, Firebase zamyka połączenie po 60 sekundach braku aktywności.

Czas oczekiwania na obsługę

Sygnatury czasowe serwera

Serwery Firebase Realtime Database zapewniają mechanizm wstawiania sygnatur czasowych wygenerowanych na serwerze jako dane. Ta funkcja w połączeniu z onDisconnect umożliwia łatwe zanotowanie godzina rozłączenia klienta Realtime Database:

Kotlin+KTX

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

Zniekształcenie zegara

firebase.database.ServerValue.TIMESTAMP to znacznie więcej dokładne i zalecane w większości operacji odczytu/zapisu, czasami może być przydatne do oszacowania zniekształcenia zegara klienta, w odniesieniu do serwerów Firebase Realtime Database. Ty może dołączyć oddzwonienie do lokalizacji /.info/serverTimeOffset aby uzyskać w milisekundach wartość klientów Firebase Realtime Database dodaj do szacunkowego czasu raportowania lokalnego (czasu epoki w milisekundach) czas serwera. Pamiętaj, że na dokładność przesunięcia może mieć wpływ opóźnienia sieciowe, co przydaje się przede wszystkim do wykrywania duże (> 1 sekundę) rozbieżności w czasie zegara.

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

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 Obecność

Łącząc operacje rozłączania z monitorowaniem stanu połączenia sygnatury czasowe serwera, możesz utworzyć system wykrywania obecności użytkowników. W tym systemie każdy użytkownik przechowuje dane w lokalizacji bazy danych, aby wskazać, czy Realtime Database klient jest online. Klienci ustawili tę lokalizację na Prawda, gdy: kiedy przechodzą do trybu online, oraz podaj sygnaturę czasową po rozłączeniu. Ta sygnatura czasowa wskazuje, kiedy dany użytkownik ostatnio był online.

Pamiętaj, że aplikacja powinna zakoleć operacje rozłączania, zanim użytkownik oznaczone jako online, co pozwala uniknąć wyścigów w przypadku, gdy utracisz połączenie sieciowe, zanim możliwe będzie wysłanie obu poleceń do serwera.

Oto prosty system wykrywania obecności użytkowników:

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

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