Двусторонняя связь, устанавливаемая без интернета, открывает новые возможности для пользователей. К примеру, позволяет работать большой командой и обмениваться видео, файлами и текстовыми сообщениями.
Конечно, существуют ограничения на количество человек. Bluetooth может поддерживать до 4 соединений одновременно, а Wi-Fi — в диапазоне от 10 до 100 (в зависимости от того, доступны ли настенные маршрутизаторы в качестве мостов).
В отличие от Nearby Messages API, который работает на Android и iOS, Nearby Connections API доступен только на Android. В то же время у Nearby Connections API есть два ключевых преимущества.
- Он позволяет передавать неограниченное количество данных по Bluetooth и одноранговому Wi-Fi, в то время как Nearby Messages API предназначен только для небольших пакетов (до 4 Кб).
- Nearby Connections API предоставляет пользователям возможность общаться друг с другом без интернет-соединения, а для Nearby Messages API требуется подключение.
Чтобы разрешить приложению включить Nearby Connections API для пользователя, необходимо сначала добавить в модуль приложения последнюю версию описанной ниже зависимости Gradle.
dependencies {
implementation 'com.google.android.gms:play-services-nearby:18.0.0'
}
Не менее важно и то, что в настоящее время Android запрашивает у пользователя разрешения ACCESS_FINE_LOCATION
для приложения от его имени.
В манифест необходимо добавить следующие разрешения, даже если пользователю не будет предложено их предоставить:
BLUETOOTH
;BLUETOOTH_ADMIN
;ACCESS_WIFI_STATE
;CHANGE_WIFI_STATE
;ACCESS_FINE_LOCATION
.
До Android 10 (уровень API 29) приложению требовалось только запросить разрешение ACCESS_COARSE_LOCATION
. Однако для устройств на базе Android 10 и выше требуется ACCESS_FINE_LOCATION
. Поскольку разрешение на точное местоположение подразумевает разрешение на грубое местоположение в рамках Android — и обратное неверно — приложение может просто запросить ACCESS_FINE_LOCATION
для всех уровней API.
Важно помнить: если пользователь не предоставит необходимые разрешения, функция не будет работать, а приложение должно корректно обработать этот отказ. Пользователь всегда может перейти в настройки, чтобы отозвать предоставленные разрешения.
Чтобы два человека (устройство X и устройство Y) приложения могли подключаться и обмениваться контентом с помощью Nearby Connections API, необходимо проделать нижеописанные действия.
Оба пользователя должны выбрать одну и ту же стратегию
Nearby Connections API требует, чтобы вы указали одноранговую стратегию (Strategy), которую приложение будет использовать для связи.
Существует три варианта стратегии:
P2P_CLUSTER
;P2P_STAR
;P2P_POINT_TO_POINT.
.
Хотя ограничения пропускной способности — не всегда самые главные факторы при выборе стратегии, полезно знать следующее.
P2P_CLUSTER
, как правило, приводит к подключениям с более низкой пропускной способностью, чемP2P_STAR
.- Из трех стратегий
P2P_POINT_TO_POINT
обычно обладает максимально возможной пропускной способностью.
Как только вы определитесь со Strategy
, оповещатель должен использовать ее для оповещения:
AdvertisingOptions.Builder().setStrategy(Strategy.P2P_STAR).build()
Обнаружитель должен использовать ту же Strategy
во время обнаружения:
DiscoveryOptions.Builder().setStrategy(Strategy.P2P_STAR).build()
Приложение на обоих устройствах должно использовать один и тот же serviceId
serviceId
— это идентификатор, который приложение отправляет в API, чтобы программный интерфейс мог отличить его от всех остальных.
Связь происходит только между одним и тем же Id-сервисом. Например, serviceId=”YouTube”
никогда не обнаружит устройство с оповещением serviceId=”Gmail”
. Идентификатор должен быть уникальным. Поэтому рекомендуется использовать имя пакета приложения в качестве уникального идентификатора для всех вызовов API, требующих этого.
Вызов и ответ
Когда приложение на устройстве X вызывает функцию startAdvertising()
, передается сигнал по Bluetooth.
Когда сигнал достигает устройства Y, Nearby Connections API запускает обратный вызов EndpointDiscoveryCallback
в приложении на устройстве Y.
// private val connectionsClient: ConnectionsClient by lazy{Nearby.getConnectionsClient(this)}
private fun startAdvertising(){
// Примечание: Оповещение может не сработать. Мы не рассматриваем случаи сбоев, чтобы не усложнять процесс демонстрации.
// Мы также продемонстрируем connectionLifecycleCallback позже.
connectionsClient.startAdvertising(yourCodeName, packageName, connectionLifecycleCallback,
AdvertisingOptions.Builder().setStrategy(Strategy.P2P_STAR).build())
}
В ответ на обнаружение оповещения с устройства X устройство Y может отправить запрос на подключение устройству X.
Примечание. В режиме обнаружения устройство Y потенциально может обнаруживать конечные точки со 100 близлежащих устройств, и в этом случае метод onEndpointFound()
будет срабатывать 100 раз. Следовательно, пользователю обычно приходится выбирать, на какое устройство отправлять запрос на подключение. В нашем примере пользователь выбирает устройство X.
private fun startDiscovery(){
val endpointDiscoveryCallback:EndpointDiscoveryCallback =
object:EndpointDiscoveryCallback(){
override fun onEndpointFound(endpointId: String, info: DiscoveredEndpointInfo) {
connectionsClient.requestConnection(yourCodeName, endpointId, connectionLifecycleCallback)
}
override fun onEndpointLost(endpointId: String) {
Log.d(TAG, "onEndpointLost")
}
}
// Примечание: Обнаружение может не сработать. Мы не рассматриваем случаи сбоев, чтобы не усложнять процесс демонстрации.
connectionsClient.startDiscovery(packageName,endpointDiscoveryCallback,
DiscoveryOptions.Builder().setStrategy(Strategy.P2P_STAR).build())
}
Установление связи
Запрос на подключение исходит от устройства Y в ответ на оповещение с устройства X. Однако оба устройства должны принять соединение, чтобы между ними была установлена фактическая связь.
Фактическое принятие происходит в рамках обратного вызова ConnectionLifecycleCallback
. Как показано в предыдущих фрагментах, оповещатель передает этот обратный вызов API через вызов метода startAdvertising()
, в то время как обнаружитель передает обратный вызов через вызов метода requestConnection()
.
Тем не менее запросы на подключение доставляются в ConnectionLifecycleCallback#onConnectionInitiated()
.
// Обратный вызов для подключения к другим устройствам: как оповещатель, так и обнаружитель должны
// реализовать его.
private val connectionLifecycleCallback = object:ConnectionLifecycleCallback(){
override fun onConnectionInitiated(endpointId: String, connectionInfo: ConnectionInfo) {
Log.d(TAG, "onConnectionInitiated: accepting connection")
connectionsClient.acceptConnection(endpointId,payloadCallback)
friendCodeName = connectionInfo.endpointName
}
override fun onConnectionResult(endpointId: String, result: ConnectionResolution) {
// Вы можете использовать функцию if(result.status.isSuccess){}else{} или проверить statusCode с помощью "when"
when (result.status.statusCode) {
ConnectionsStatusCodes.STATUS_OK -> {
// Мы подключились! Теперь можем начать отправлять и получать данные.
// Как только вы успешно подключитесь к устройствам своих друзей, вы сможете выйти из
// режима обнаружения, чтобы прекратить обнаружение других устройств
connectionsClient.stopDiscovery();
// Вы также можете завершить передачу оповещений
connectionsClient.stopAdvertising()
friendEndpointId = endpointId
}
ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED -> {
// Соединение было отклонено одной или обеими сторонами.
}
ConnectionsStatusCodes.STATUS_ERROR -> {
// Соединение прервалось до того, как его удалось принять.
}
else -> {
// Неизвестный код состояния
}
}
}
override fun onDisconnected(endpointId: String) {
Log.d(TAG, "onDisconnected: from friend")
// Выполните необходимую очистку
}
}
Соединение, показанное в предыдущем фрагменте, принимается автоматически. Однако лучше установить дополнительные меры предосторожности при обмене конфиденциальными данными.
API предоставляет уникальный токен для каждого соединения через объект ConnectionInfo
, принадлежащий onConnectionInitiated(String endpointId, ConnectionInfo info)
. Вы можете получить токен из info.getAuthenticationDigits().
.
Рекомендуем показывать токены обоим пользователям, чтобы они могли визуально подтвердить, что они равны. Если визуальное подтверждение невозможно из-за того, что пользователи не могут видеть экраны друг друга, вы можете попросить одно устройство зашифровать необработанный токен, отправить его на другое для расшифровки и сравнения, прежде чем начать делиться конфиденциальным контентом (подробнее о шифровании Android).
Общение
Пока два устройства подключены, они могут отправлять контент друг другу с помощью вызова метода sendPayload()
и обратного вызова onPayloadReceived()
.
Полезной нагрузкой может быть все, чем пользователи хотят поделиться: стримы, файлы, музыка, видео, фотографии, текст и т. д.
Каждое устройство зарегистрировало обратный вызов onPayloadReceived()
через Nearby.getConnectionsClient(context).acceptConnection(endpointId, payloadCallback)
. Затем в любое время, когда у пользователей возникает желание поделиться контентом, они просто вызывают:
connectionsClient.sendPayload(
toEndpointId, Payload.fromBytes(someData.getBytes(UTF_8)))
Резюме
Nearby Connections API идеально подходит для двусторонней связи между пользователями Android, особенно если они хотят обмениваться контентом. При этом нет потребности ни в сотовой системе передачи данных, ни в подключении к интернету.
Для получения более подробной информации о том, как использовать Nearby Connections API, см. документацию по Nearby Connections API.
Читайте также:
- Тестирование уровня данных в Android Room с помощью Rxjava, LiveData и сопрограмм Kotlin
- Android 12: радикально новый дизайн от Google
- Корутины и управление разрешениями в Android
Читайте нас в Telegram, VK и Дзен
Перевод статьи Isai Damier: Two-way communication without internet: Nearby Connections