Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Включение автономных возможностей на Android

Приложения Firebase работают, даже если ваше приложение временно теряет сетевое соединение. Кроме того, Firebase предоставляет инструменты для локального сохранения данных, управления присутствием и обработки задержек.

Сохранение диска

Приложения Firebase автоматически обрабатывают временные отключения сети. Кэшированные данные доступны в автономном режиме, и Firebase повторно отправляет любые записи при восстановлении сетевого подключения.

Когда вы включаете постоянство диска, ваше приложение записывает данные локально на устройство, чтобы ваше приложение могло поддерживать состояние в автономном режиме, даже если пользователь или операционная система перезапускают приложение.

Вы можете включить сохранение на диске с помощью всего одной строчки кода.

Ява

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

Котлин + KTX

Firebase.database.setPersistenceEnabled(true)

Поведение настойчивости

При включении постоянства любые данные, которые клиент Firebase Realtime Database будет синхронизировать во время работы в сети, сохраняются на диске и доступны в автономном режиме, даже когда пользователь или операционная система перезапускают приложение. Это означает, что ваше приложение работает так же, как и в сети, используя локальные данные, хранящиеся в кеше. Обратные вызовы слушателя будут продолжать активироваться для локальных обновлений.

Клиент базы данных Firebase Realtime автоматически сохраняет очередь всех операций записи, которые выполняются, пока ваше приложение отключено. Когда постоянство включено, эта очередь также сохраняется на диске, поэтому все ваши записи доступны, когда пользователь или операционная система перезапускают приложение. Когда приложение восстанавливает подключение, все операции отправляются на сервер базы данных Firebase Realtime.

Если ваше приложение использует аутентификацию Firebase, клиент базы данных Firebase Realtime сохраняет токен аутентификации пользователя при перезапуске приложения. Если срок действия токена аутентификации истекает, когда ваше приложение находится в автономном режиме, клиент приостанавливает операции записи до тех пор, пока ваше приложение не повторно аутентифицирует пользователя, в противном случае операции записи могут завершиться ошибкой из-за правил безопасности.

Сохранение актуальности данных

База данных Firebase Realtime синхронизирует и хранит локальную копию данных для активных слушателей. Кроме того, вы можете синхронизировать определенные места.

Ява

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

Котлин + KTX

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

Клиент базы данных Firebase Realtime автоматически загружает данные в эти места и синхронизирует их, даже если у ссылки нет активных слушателей. Вы можете отключить синхронизацию с помощью следующей строки кода.

Ява

scoresRef.keepSynced(false);

Котлин + KTX

scoresRef.keepSynced(false)

По умолчанию кэшируется 10 МБ ранее синхронизированных данных. Этого должно хватить для большинства приложений. Если размер кеша превышает настроенный размер, база данных Firebase Realtime удаляет данные, которые использовались не так давно. Данные, которые хранятся в синхронизации, не удаляются из кеша.

Запросы данных в автономном режиме

База данных Firebase Realtime хранит данные, полученные в результате запроса, для использования в автономном режиме. Для запросов, созданных в автономном режиме, база данных Firebase Realtime продолжает работать для ранее загруженных данных. Если запрошенные данные не загружены, база данных Firebase Realtime загружает данные из локального кеша. Когда сетевое соединение снова становится доступным, данные загружаются и отражают запрос.

Например, этот код запрашивает последние четыре элемента в базе данных оценок Firebase Realtime.

Ява

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 Realtime Database с помощью постоянного кэша вызывает события «добавлены дочерние элементы» для двух динозавров с наивысшей оценкой. Но это не вызовет событие «значение», поскольку приложение никогда не выполняло этот запрос в сети.

Если бы приложение запрашивало последние шесть элементов в автономном режиме, оно сразу получало бы события «добавлен дочерний элемент» для четырех кэшированных элементов. Когда устройство возвращается в сеть, клиент Firebase Realtime Database синхронизируется с сервером и получает последние два события «добавлен дочерний элемент» и событие «значение» для приложения.

Обработка транзакций в автономном режиме

Любые транзакции, которые выполняются, когда приложение отключено, ставятся в очередь. Как только приложение восстанавливает сетевое подключение, транзакции отправляются на сервер базы данных реального времени.

Управление присутствием

В приложениях реального времени часто бывает полезно определять, когда клиенты подключаются и отключаются. Например, вы можете пометить пользователя как «не в сети», когда его клиент отключается.

Клиенты Firebase Database предоставляют простые примитивы, которые вы можете использовать для записи в базу данных, когда клиент отключается от серверов базы данных 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() на сервере базы данных Firebase Realtime. Сервер проверяет безопасность, чтобы убедиться, что пользователь может выполнить запрошенное событие записи, и сообщает вашему приложению, если оно недействительно. Затем сервер контролирует соединение. Если в какой-то момент время ожидания соединения истекает или оно активно закрывается клиентом Realtime Database, сервер проверяет безопасность во второй раз (чтобы убедиться, что операция все еще действительна), а затем вызывает событие.

Ваше приложение может использовать обратный вызов для операции записи, чтобы убедиться, что 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()

Определение состояния подключения

Для многих функций, связанных с присутствием, вашему приложению полезно знать, когда оно находится в сети или офлайн. База данных Firebase Realtime предоставляет специальное место в /.info/connected которое обновляется каждый раз, когда изменяется состояние подключения клиента базы данных Firebase Realtime. Вот пример:

Ява

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 как false, это не гарантирует, что отдельный клиент также прочитает false.

В Android Firebase автоматически управляет состоянием подключения, чтобы уменьшить пропускную способность и расход заряда батареи. Когда у клиента нет активных слушателей, нет ожидающих операций записи или onDisconnect и он явно не отключен методом goOffline , Firebase закрывает соединение через 60 секунд бездействия.

Обработка задержки

Отметки времени сервера

Серверы Firebase Realtime Database предоставляют механизм для вставки временных меток, созданных на сервере, в качестве данных. Эта функция в сочетании с 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 намного точнее и предпочтительнее для большинства операций чтения / записи, иногда может быть полезно оценить отклонение часов клиента относительно серверов базы данных Firebase Realtime. Вы можете прикрепить обратный вызов к местоположению /.info/serverTimeOffset чтобы получить значение в миллисекундах, которое клиенты базы данных Firebase Realtime добавляют к местному сообщаемому времени (время эпохи в миллисекундах) для оценки времени сервера. Обратите внимание, что на точность этого смещения может влиять задержка в сети, поэтому он полезен в первую очередь для обнаружения больших (> 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")
    }
})

Пример приложения Presence

Комбинируя операции отключения с мониторингом состояния подключения и отметками времени сервера, вы можете создать систему присутствия пользователей. В этой системе каждый пользователь хранит данные в расположении базы данных, чтобы указать, подключен ли клиент базы данных реального времени. Клиенты устанавливают для этого местоположения значение 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")
    }
})