Инженер по связям с разработчиками
Инженер по связям с разработчиками
Пользователи постоянно переключаются между телефонами, носимыми устройствами и аудиопериферией, поэтому для поддержания вовлеченности важно обеспечить плавный переход между устройствами. Однако стандартные сетевые подходы часто приводят к разрядке батареи, скачкам задержки и фрагментации пользовательских сеансов.
В этом подробном руководстве представлены специализированные API Android, созданные для связи между устройствами и предлагающие эффективные с точки зрения памяти и энергопотребления шаблоны, обеспечивающие мгновенный, надежный и интуитивно понятный переход между устройствами.
Расшифровка
Здравствуйте!
Добро пожаловать на курс "Как обеспечить удобное взаимодействие между устройствами".
Меня зовут Ханьжуй, и сегодня со мной мои коллеги Ясин и Натарадж.
Когда мы говорим о подключении, на ум часто приходит базовое сетевое соединение или Bluetooth-гарнитуры.
На Android возможности подключения гораздо шире.
Она позволяет устройствам лучше работать вместе.
Сегодня мы рассмотрим распространенные сценарии использования нескольких устройств и расскажем, какой API подходит для каждого из них.
Долгое время мы создавали приложения только для одного типа устройств – телефонов.
Затем появились носимые устройства.
Вскоре умные часы, подключенные к телефону, стали обычным явлением. Они синхронизировали данные в фоновом режиме и позволяли принимать звонки.
Добавь беспроводные наушники.
Теперь пользователи могут переключать аудиоустройства во время звонка, например с часов на телефон, наушники или автомобиль.
В последнее время у многих пользователей также есть второе устройство с большим экраном, например складной телефон или планшет.
и ожидают, что оно будет работать на них без сбоев.
Это касается и ноутбуков, в том числе Chromebook, на которых можно запускать приложения Android и оставаться на связи, не отходя от рабочего стола.
Даже в 2023 году у среднестатистического пользователя было от трех до четырех устройств.
Пользователи ожидают от этих устройств большего, чем просто автономная функциональность.
Они ищут необычайно лучший совместный опыт.
Это особенно заметно, когда люди находятся в одной комнате, как мы сейчас, и хотят быстро поделиться фотографиями, ссылками или приложениями с теми, кто рядом.
Если посмотреть на цифры, экосистема огромна и быстро растет.
В этом году в мире насчитывается около 22 миллиардов устройств IoT, что на 14 % больше, чем в прошлом году.
И среди всех этих конечных точек Wi-Fi составляет треть, а Bluetooth – четверть.
Связь между устройствами больше не является пограничным случаем.
Они стали частью нашей повседневной жизни.
Однако сегодня большинство приложений по-прежнему создают свои функции для нескольких устройств на основе низкоуровневых сетевых API и API Bluetooth, что создает три проблемы.
Во-первых, это батарея и память.
Работа фоновых сканирований и сервисов приводит к расходу батареи и памяти.
Второй фактор – задержка.
Непредсказуемые задержки нарушают звонки в реальном времени и синхронизацию данных.
В-третьих, это сложности с подключением.
Управлять обнаружением, подключением и повторным подключением на всех устройствах сложно, и это может привести к проблемам в работе.
Чтобы избавить вас от этой головной боли, мы создали специализированные API для трех основных областей, которые значительно упрощают работу с функциями для нескольких устройств.
Я начну с сопутствующих приложений, затем Натарадж расскажет о приложениях для VoIP, а Ясин – об одноранговом обмене.
Сначала рассмотрим сопутствующие приложения.
Сопутствующее приложение устанавливается на телефон, чтобы обеспечить его корректную работу с дополнительным устройством, например умными часами или датчиком глюкозы.
В этом разделе мы сосредоточимся на устройствах, подключенных по Bluetooth.
Обычно выделяют четыре категории дополнительных устройств.
Во-первых, это медицинские изделия, такие как датчики глюкозы и мониторы жизненно важных показателей.
и носимые устройства, например умные часы и фитнес-браслеты.
Гарнитуры с быстрым подключением и умным управлением.
и автомобильные комплекты, например для цифровых автомобильных ключей и звонков в режиме голосового управления.
У каждой категории есть свои ключевые пути пользователя, но все они зависят от надежного соединения между устройством и телефоном.
Для медицинских изделий это может быть критически важно.
Давайте рассмотрим четыре распространенные потребности, которые мы видим в этих категориях.
Во-первых, это управление устройствами.
Приложение должно найти нужное устройство среди всех, что находятся поблизости, например часы, а не наушники или колонку, и начать процесс подключения.
Во-вторых, проверка присутствия.
Приложению нужно знать, когда устройство находится поблизости, а когда нет.
Когда устройство появляется, приложению нужен способ пробуждения, чтобы оно могло выполнять свою работу, даже если пользователь его не открыл.
В-третьих, поддержание связи.
После подключения приложениям требуется надежное и постоянное соединение.
Если связь прервется, например пользователь выйдет из зоны действия, а затем вернется, приложение должно автоматически восстановить подключение, даже если оно не запущено.
И четвёртый — синхронизация и передача данных.
Вот в чём настоящая ценность.
Синхронизация показаний датчиков, отправка обновлений встроенного ПО или отправка настроек на устройство.
Для медицинских изделий это может быть критически важно.
Пропущенное измерение может означать пропущенное предупреждение.
Для этого нужно, чтобы процесс приложения был запущен и подключен в нужный момент.
Как приложения удовлетворяют все эти потребности?
Раньше приложения в основном полагались на активные службы.
Ниже приведен пример реализации.
Если вы уже создавали сопутствующие приложения, то, возможно, знаете, как это делается.
Вы объявляете разрешения на поиск Bluetooth-устройств и подключение к ним, разрешения для активных служб и тип активной службы подключенного устройства, а затем разрешение на уведомления, поскольку активная служба должна показывать постоянное уведомление.
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />Затем вы запускаете сервис, который поддерживает работу приложения.
class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}Это работает, но расходует заряд батареи и память, потому что сервис работает постоянно, даже когда устройства нет поблизости.
Поэтому мы создали менеджер подключенных устройств (CDM).
Во-первых, оно может активировать ваше приложение, когда устройство находится поблизости. Пока устройство остается в зоне действия, система будет поддерживать работу процесса приложения.
Активная служба не требуется.
Во-вторых, CDM поддерживает профили устройств.
Благодаря профилю устройства пользователь может предоставить все необходимые разрешения одним нажатием, а не в цепочке диалоговых окон.
Это значительно повышает удобство для пользователей и коэффициент конверсии.
Профиль часов доступен начиная с Android 12, а профиль очков – с Android 14.
В Android 17 мы добавили два новых профиля, предназначенных для медицинских изделий и фитнес-трекеров.
Также есть несколько профилей с расширенными правами, которые сейчас доступны только для системных приложений.
Если мы увидим, что в экосистеме есть потребность в таких API, то можем открыть доступ к ним для большего числа разработчиков.
Пользоваться CDM очень просто.
Рассмотрим пример с медицинским изделием.
Сначала объявите необходимые разрешения: одно для медицинского изделия, одно для работы в фоновом режиме и одно для отслеживания присутствия устройства.
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />Обратите внимание, что в отличие от разрешений, которые мы только что рассмотрели в связи с реализацией активной службы, все перечисленные здесь разрешения являются обычными. Это означает, что они будут предоставлены при установке без диалогового окна с запросом.
Пользователю нужно только подтвердить связь.
Давайте посмотрим, как это сделать.
Здесь мы создаем запрос на связывание с фильтром устройств, чтобы отображалось только нужное устройство.
val paringRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
.build()
companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
object : CompanionDeviceManager.Callback() {
override fun onAssociationPending(intentSender: IntentSender) {
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}
override fun onAssociationCreated(associationInfo: AssociationInfo) { … }
override fun onFailure(error: CharSequence?) { … }
}
)setSingleDevice в значение true, поскольку нам нужно только одно совпадение, и задайте профиль medical.
Затем вызовите сотрудника.
Сканирование выполняется CDM.
В onAssociationPending мы получим IntentSender.
Запустите его. Система покажет диалоговое окно сопоставления.
При использовании медицинского профиля это одно нажатие не только подтверждает саму связь, но и предоставляет все разрешения, необходимые профилю, даже обходя режим "Не беспокоить", что критически важно для критических оповещений.
Когда пользователь нажмет "Разрешить", мы получим onAssociationCreated с AssociationInfo, который включает идентификатор связи.
Затем мы можем наблюдать за присутствием устройства, вызвав startObservingDevicePresence с этим идентификатором ассоциации.
Вам также нужно реализовать сервис, который расширяет CompanionDeviceService.
class BleCompanionDeviceService : CompanionDeviceService() {
override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
when (event.event) {
DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
}
}
private fun onDeviceAppeared(associationId: Int) { … }
private fun onDeviceDisappeared(associationId: Int) { … }
}Когда устройство оказывается в зоне действия, система подключается к вашему сервису, даже если приложение не запущено.
Система вызовет onDevicePresenceEvent с EVENT_BLE_APPEARED.
После этого вы сможете подключить устройство и синхронизировать данные.
Когда устройство покинет зону, вы получите EVENT_BLE_DISAPPEARED.
Затем вы можете выполнить очистку.
Нет постоянно работающего сервиса и уведомлений, которые нужны только для того, чтобы приложение не закрывалось.
Однако у CDM есть несколько ограничений, о которых разработчикам нужно помнить.
Сначала версии ОС.
Большинство функций CDM появилось в Android 12 и 13.
Но в Android 16 появились некоторые улучшения, а в Android 17 – новые профили.
Поэтому проверяйте версию ОС во время выполнения и используйте резервный вариант для более старых версий.
При этом целевой уровень SDK повышается каждый год, а минимальный уровень SDK обычно следует за ним.
Чтобы справиться с нагрузкой на память, с которой приложения сталкиваются сейчас и будут сталкиваться в будущем, мы настоятельно рекомендуем как можно скорее перейти с активных служб на CDM.
Если у вас есть сценарий использования, который в настоящее время не поддерживается CDM, сообщите нам об этом.
Во-вторых, сейчас CDM поддерживает фоновое сканирование, но не фоновую рекламу.
Мы редко слышим о необходимости в этом.
Если же вашему приложению требуется, чтобы телефон действовал как периферийное устройство и постоянно передавал данные в фоновом режиме, мы хотели бы узнать об этом больше.
В-третьих, CDM использует режим сканирования с низким энергопотреблением.
Это позволяет экономить заряд батареи и память, но немного снижает скорость реагирования в реальном времени.
Поэтому не ждите мгновенного пробуждения.
Наконец, не заглядывайте в полезную нагрузку.
Вы не можете зарегистрироваться для получения определенной характеристики или рекламных данных.
Но как только устройство оказывается в зоне действия, приложение активируется.
Это позволяет ему подключаться и проверять полезную нагрузку по мере необходимости.
Это все, что нужно знать о сопутствующих приложениях и CDM.
Я передаю слово Натараджу, который расскажет о приложениях для IP-телефонии.
CompanionDeviceManager – это подходящий инструмент, если у вашего приложения есть выделенное периферийное устройство, с которым оно постоянно обменивается данными.
Здравствуйте!
Меня зовут Натарадж К. Р.
Теперь я расскажу о следующем сценарии использования нескольких устройств – звонках в реальном времени.
В наши дни то, как люди обрабатывают звонки, меняется.
Более 40 % звонков совершаются по сетям VoIP.
И Android играет в этом важную роль.
71 % таких звонков совершается на устройствах Android.
Это не просто звонки.
Экосистема устройств, которыми владеют люди, также значительно расширяется.
Сейчас на дорогах более 250 миллионов автомобилей с Android Auto, и мы поставляем пользователям более миллиарда устройств Bluetooth.
Наушники, часы Wear OS, автомобили с Android Auto, Bluetooth-колонки – все эти устройства пользователи подключают к телефону и хотят, чтобы звонки на них работали без проблем.
Например, когда пользователю звонят, он может посмотреть на часы, чтобы узнать, кто это.
Они ответят на звонок на часах.
Они включали и выключали микрофон по мере необходимости, вставляли наушники и продолжали разговор.
Если они хотят выйти на улицу, они запрыгнут в свой звонок - они запрыгнут в свою машину и будут ожидать, что их звонок плавно переключится на салон автомобиля.
Поэтому это стало нормальной рутиной для пользователя, и это не премиальный опыт.
Для пользователя это базовое требование, и мы, разработчики Android, должны обеспечить бесперебойную работу наших VoIP-приложений.
Но если вы попытаетесь создать все это самостоятельно с нуля, вы столкнетесь с тремя основными проблемами.
Во-первых, фрагментация с сокетами Bluetooth.
Поскольку система не знает, что ваше приложение обрабатывает вызов, вам придется создавать собственные сокеты Bluetooth и медиасеансы, чтобы вызов можно было перенаправить на часы или Android Auto.
Вторая проблема – фокус на аудио.
Поскольку вам приходится иметь дело с низкоуровневыми API AudioManager и Bluetooth, происходят утечки звука, и это не является приоритетом для системы, верно?
В вашем приложении выполняется VoIP-вызов. При поступлении вызова от оператора связи он прерывается, и модем захватывает аудио, оставляя ваше приложение без звука.
Хуже того, когда звонок по сотовой связи завершится, аудиофокус не вернется в ваше приложение.
И третья проблема – синхронизация.
Пользователь отключал звук на часах, но на телефоне отображалось, что звук включен.
Это создает большую путаницу.
Пользователь завершает звонок с помощью кнопки на руле, но на телефоне по-прежнему показывается уведомление о звонке.
Это очень расстраивает пользователя, когда такое происходит.
Чтобы решить все эти проблемы, мы создали Jetpack Telecom.
Вы регистрируете звонок в Jetpack Telecom с помощью CallsManager, и Jetpack Telecom выступает в качестве центрального координатора системных вызовов.
Он отвечает за проецирование звонка на часы, Android Auto и другие удаленные поверхности, принадлежащие пользователю.
и управляет аудиофокусом.
И самое главное – все это работает без проблем на устройствах с Android 8 и более поздними версиями.
Проецирование звонка на экран часов или автомобиля – это только половина дела, ведь пользователь, очевидно, хочет управлять состоянием звонка с этих устройств.
Они хотят иметь возможность отключать и включать звук на часах во время звонка.
Они хотят завершать звонки с помощью кнопки на рулевой колонке или двойного нажатия на наушники.
Jetpack Telecom прослушивает эти аппаратные события и представляет их в виде обратных вызовов в одном месте – CallControlScope.
Вам нужно только прослушивать эти обратные вызовы, управлять механизмом вызовов и соответствующим образом обновлять состояние вызова.
А Jetpack Telecom берет на себя ответственность за синхронизацию этого состояния вызова между устройствами.
Итак, библиотека дает преимущества в двух направлениях.
Во-первых, очевидно, что это решает все проблемы, о которых мы говорили.
Вместо того чтобы работать с низкоуровневыми API AudioManager, вы можете использовать декларативные конечные точки вызовов, управлять аудиофокусом и синхронизировать устройства.
Он отвечает за передачу вызова между устройствами.
И он также заботится о том, чтобы сотовая связь работала.
Но есть и другое преимущество. Поскольку библиотека построена на Kotlin, каждый раз, когда вы добавляете вызов с помощью Jetpack Telecom, открывается область сопрограммы Kotlin.
Таким образом, он использует преимущества сопрограмм Kotlin, и эта сопрограмма Kotlin остается активной ровно до окончания вызова.
Таким образом, любая задача, запущенная в связи со звонком, автоматически удаляется, как только звонок завершается.
Таким образом, у вас не останется нежелательных заданий, а жизненный цикл вызовов также будет обрабатываться библиотекой.
Итак, теперь вы, вероятно, думаете, что эта библиотека великолепна и вы хотите ее внедрить, верно?
Это очень просто.
Шаг 1. Очевидно, что вы должны объявить зависимость.
Затем объявите необходимое разрешение MANAGE_OWN_CALLS и создайте экземпляр объекта CallsManager.
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />После создания экземпляра объекта CallsManager зарегистрируйте в приложении функции, которые вы хотите в нем предоставлять, например видеовызовы или трансляцию звонков.
// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)Затем перейдите к шагу 2.
После регистрации приложения в Jetpack Telecom можно переходить к обработке входящих и исходящих вызовов.
Для этого нужно создать объект CallAttributesCompat, который содержит метаданные вызова, такие как displayName вызывающего или вызываемого абонента, адрес, который является конечной точкой вызывающего абонента, направление, указывающее, является ли вызов входящим вызовом или исходящим вызовом, чтобы Jetpack Telecom мог представить соответствующий пользовательский интерфейс на этих удаленных поверхностях, а затем callType, является ли это голосовым вызовом или видеовызовом, чтобы вы могли получить необходимые ресурсы.
val attributes = CallAttributesCompat(
displayName = "Kukk Nat",
address = Uri.parse("tel:+16505550199"),
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
)
callsManager.addCall(
attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
) {
// Coroutine CallControlScope active for the duration of the call
}Когда объект метаданных будет готов, добавьте его с помощью метода callsManager.addCall.
Это открывает область сопрограммы управления вызовами.
Итак, после того, как вы добавили звонок, переходим к шагу 3.
Шаг 3. Прослушивание действий, которые пользователь собирается выполнить на удаленных поверхностях.
Поскольку у вас есть CallControlScope, вам нужно только собрать потоки Kotlin.
callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
// 1. React to hardware mute toggles from Watch, Car, or Headset
launch {
isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
}
// 2. Observe available endpoints & request declarative switches
launch {
currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
}
requestEndpointChange(preferredBluetoothEndpoint)
}Вы собираете isMuted, чтобы реагировать на отключение звука пользователем на удаленных устройствах и обновлять статус вызова.
Соберите currentCallEndpoint, чтобы узнать, на какой конечной точке сейчас происходит вызов, и обновить интерфейс.
Вы можете показать это устройство в интерфейсе телефона.
Если пользователь хочет переключиться на другое устройство, вызовите метод requestEndpointChange с нужной конечной точкой.
Вот и все.
После выполнения этих трех шагов ваше приложение будет готово к работе с интегрированной библиотекой Jetpack Telecom.
Но это не всё.
В Jetpack Telecom 1.1.0 мы добавили много новых функций, например единый журнал звонков. Теперь все ваши VoIP-вызовы будут отображаться в системном приложении для звонков.
Преимущество в том, что пользователи смогут перезванивать прямо из системного номеронабирателя в ваше приложение.
Вам не придется искать нужное приложение среди всех остальных.
Они могут просто перезвонить из системного номеронабирателя, к которому привыкли.
Но мы также предоставляем детальные настройки.
Если вы не хотите, чтобы звонок или определенный звонок регистрировался в системе, вам просто нужно установить для флага isLogExcluded значение true внутри объекта CallAttributesCompat.
Объект метаданных, о котором я говорил ранее, вы просто делаете его истинным, и ваш звонок не будет зарегистрирован в системе, и он не будет отображаться в системном номеронабирателе.
Вторая отличная функция, которая появится в версии 1.1.0, – это сегментирование сетей 5G.
В поддерживаемых сетях операторы предоставляют премиум-слайсы. Это значит, что для определенного сценария использования оператор выделяет слайс сети с низкой задержкой и без прерываний, чтобы обеспечить более высокое качество и удобство.
В поддерживаемых сетях операторов эта функция будет доступна по умолчанию, поэтому вам не придется ничего делать, даже писать код.
После интеграции библиотеки ваши звонки будут использовать эти сегменты сети, и вы получите меньшую задержку и лучшую производительность.
Если вы не хотите использовать эту функцию, ее можно отключить с помощью флага CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK.
Jetpack Telecom уже используется в ведущих мировых приложениях для общения, таких как Google Meet, WhatsApp, KakaoTalk и Zoho Arattai.
Теперь вам нужно интегрировать эту библиотеку в свое приложение и обеспечить удобство для пользователей.
Теперь, когда мы обсудили звонки в реальном времени, я передаю слово моему коллеге Ясину, который расскажет о функции "Начать передачу".
Спасибо!
Итак, друзья, я знаю, что вы немного устали, поэтому не волнуйтесь, это последний раздел.
Итак, я собираюсь рассказать об одноранговом обмене файлами, но другие варианты использования мы также можем реализовать с помощью однорангового обмена.
В мобильной экосистеме мы видим, что каждый день в социальных сетях и мессенджерах публикуется более 14 миллиардов фотографий и видео. Это огромное количество.
И все эти файлы в основном передаются через облако. Вы делаете снимок, загружаете его через свой любимый сервис, а затем получатель должен скачать его таким же образом.
То же самое происходит, когда вы пытаетесь пригласить кого-то или друга в социальную сеть, чтобы пригласить кого-то к совместной работе над плейлистом или документом, вам обычно приходится вводить его имя пользователя или адрес электронной почты, что занимает некоторое время на клавиатуре.
Вы можете ошибиться, и это не очень удобно, особенно когда вы оба буквально сидите рядом друг с другом, и вам приходится отправлять через облако и ждать.
Просто не очень.
Сейчас у всех есть смартфоны, планшеты, складные телефоны, и мы делаем на них потрясающие снимки.
Но когда вы делитесь ими в социальных сетях или мессенджерах, они сжимаются, чтобы не занимать много места и быстрее загружаться. В результате на вашем потрясающем экране они выглядят пикселизированными или просто некачественными.
Это не то, чего вы ожидаете от устройства премиум-класса.
Во-вторых, файлы сейчас довольно большие.
Одна фотография легко может весить 10 МБ, а пятиминутное видео – 500 МБ. На загрузку и скачивание таких файлов уходит много времени.
И, наконец, с другой стороны, вам приходится ждать этой задержки.
Например, вы едете в автобусе, там много людей, и по мере движения автобуса у вас не лучшая задержка, не лучшее покрытие сети, и иногда передача файлов прерывается, иногда из-за этой действительно большой задержки у вас может быть тайм-аут, и передача файлов отменяется, что действительно расстраивает пользователя.
Поэтому вместо того, чтобы полагаться на облако, чтобы иметь возможность делиться всеми этими файлами и ждать всего этого, почему бы не использовать локальную сеть, например Bluetooth или Wi-Fi?
Но если вы попытаетесь сделать это самостоятельно, очевидно, вам придется иметь дело с менеджером Bluetooth, менеджером Wi-Fi, писать собственный протокол, а не у всех есть на это время.
Но на самом деле на Android уже есть кое-что похожее.
Она уже доступна, и вы, возможно, о ней слышали. Это Быстрая отправка.
Быстрая отправка на Android позволяет использовать эту абстракцию в системе и просто делиться файлами с другими устройствами, не управляя всем этим.
Она уже доступна в меню "Поделиться" и автоматически выбирает лучший протокол для передачи данных между устройствами. Функция работает на устройствах с Android Marshmallow и более поздними версиями, а это огромная аудитория.
И наконец, знакомый интерфейс и UX, которые вы видите сбоку, действительно помогают вашим пользователям.
Они знают, что такое пользовательский интерфейс, им не нужно учиться им пользоваться, они уже видели его в прошлом.
Но мы знаем, что разработчики хотят сами контролировать процесс.
Вы знаете свое приложение и своих пользователей лучше, чем мы.
Почему бы не добавить Быструю отправку прямо в ваше приложение?
Мы начали общаться с разработчиками и поняли, что Быстрая отправка уже довольно популярна.
Она доступна на Android, Chromebook, недавно появилась в Google Книгах, а также на Windows. Это более 3 миллиардов устройств. Огромная цифра.
А ещё в Pixel 11 мы добавили возможность делиться контентом одним касанием.
Вместо того чтобы выбирать устройство, с которым вы хотите поделиться контентом, вы можете просто коснуться его, и NFC сделает все за вас.
Вы можете выбрать фотографию или файл, а затем просто коснуться одним устройством другого, и файл будет передан.
И опять же, мы знаем разработчиков, которые хотят владеть опытом.
Это лучший способ предоставить пользователям доступ к определенной функции, в данном случае – к обмену файлами.
Почему бы не встроить Быструю отправку в ваше приложение?
Поэтому в следующем году мы выпустим SDK Быстрой отправки.
Вы сможете использовать его прямо в своем приложении. В нем уже есть встроенная защита – сквозное шифрование между устройствами.
Поскольку передача происходит между этими локальными сетями, у вас будет полная скорость, очевидно, в вашем домашнем Wi-Fi, но также между вами обоими может быть автоматически создана точка доступа, вам не нужно с этим иметь дело.
На устройствах с поддержкой Wi-Fi Direct она уже есть.
А файлы, которые вы сняли на свою потрясающую камеру, сохранят наилучшее разрешение и детализацию, поэтому вы сможете наслаждаться ими на большом экране.
А поскольку она использует локальные сети, вы можете использовать ее в метро, даже в самой глубокой его части, у вас все равно будет сеть, чтобы поделиться с другом.
Даже в самолете, на высоте нескольких тысяч метров, вы сможете делиться файлами.
И наконец, для вас как разработчика это огромная экономия средств на передачу данных, потому что, во-первых, вашим пользователям не нужно использовать тарифный план, а это очень важно, когда они путешествуют в качестве туристов.
Но также на ваших собственных серверах вам не нужно это поддерживать.
В прошлом году мы сообщили разработчикам и пользователям, что с помощью Быстрой отправки можно будет передавать файлы на устройства iPhone через AirDrop.
Функция "Быстрая отправка" позволяет разработчикам делать то же самое.
Мы планируем выпустить SDK Быстрой отправки для iOS, чтобы разработчики приложений для iOS могли обмениваться файлами между обеими платформами независимо от устройства.
Это была презентация. Теперь давайте посмотрим на код.
Мы постарались сделать его максимально простым.
Вы инициируете QuickShareClient, прикрепляете файлы, которыми хотите поделиться, и после запуска сеанса у вас появится это красивое модальное окно, которое вы видите сбоку, нижний лист, где пользователи смогут видеть файлы, которые отправляются в качестве предварительного просмотра, и если вы считаете контент конфиденциальным, вы также можете включить эту опцию, которую видите на экране.
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val attachment = FileAttachment(
fileName = "report.pdf",
size = 1_048_576L,
uri = fileUri,
mimeType = "application/pdf",
// Sensitive content will be masked in preview
isSensitiveContent = false,
)
val content = ShareableContent.withFiles(listOf(attachment))
val request = SharingRequest.withContent(content)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)Если вы хотите получить файл, то в приложении нужно сделать то же самое, но не добавлять вложение. После начала сеанса вам больше ничего не нужно делать.
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val request = SharingRequest.withoutContent()
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
// Open the Quick Share overlay in receive mode
session.launchWindow(activity, contentSelectionIntent = null)Пока что вы видели буквально 20 строк кода с обеих сторон.
Мы знаем, что Быстрая отправка и начать передачу, то есть возможность делиться файлами, просто прикоснувшись устройствами, открывают новые варианты использования.
Приложение может быть закрыто на другом устройстве. Если нажать на уведомление, то сразу после передачи файла приложение автоматически откроется и вы получите URI, чтобы выполнить обработку на устройстве или показать что-то пользователю прямо в приложении.
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val urlAttachment = TextAttachment(
text = groupAddUrl,
type = TextAttachment.Type.URL,
)
val content = ShareableContent.withText(urlAttachment)
val appLink = AppLink("https://myapp.com/group/invite")
val request = SharingRequest.withContent(content, appLink)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)Поэтому, опять же, вам не нужно открывать приложение на принимающем устройстве.
При использовании функции «Начать передачу» оно откроется автоматически.
И, конечно, если вы хотите, чтобы ваш пользовательский интерфейс реагировал на эту передачу файлов, у вас есть Kotlin Flow, и у вас будут все события, чтобы вы могли это обработать.
// Final API may undergo further updates
scope.launch {
for (event in session.sharingEvents) {
when (event) {
is SharingEvent.SendComplete ->
println("Send finished")
is SharingEvent.ReceiveComplete ->
println("Received, files=${event.content?.files?.size}")
else -> println("other event")
}
}
}Сегодня мы рассмотрели много вопросов, поэтому я буду заканчивать.
И не волнуйтесь, у вас будет несколько минут, чтобы сделать перерыв.
CompanionDeviceManager – это лучший API для обработки любых взаимодействий по Bluetooth, в том числе непрерывных, как ранее рассказывал Ханьжуй.
Если в вашем приложении есть функция VoIP-звонков, она не обязательно должна быть основной. Например, это может быть возможность позвонить в службу поддержки.
В этом случае, опять же, лучший способ обрабатывать звонки по протоколу Voice over IP на Android, Jetpack Com-- Jetpack Telecom — это решение.
И наконец, для однорангового обмена файлами или открытия приложений, как я уже упоминал, с помощью функции "Начать передачу" можно использовать Быструю отправку.
Спасибо всем. И ещё кое-что: после презентации мы будем в холле, чтобы ответить на ваши вопросы.
Если у вас есть вопросы по любому из представленных вариантов использования, вы можете обратиться к нам за персональной консультацией.
И не забудьте поделиться в социальных сетях тем, что вы узнали из своего выступления или других сессий.
У нас есть #AndroidDevSummit.
Нам очень важно знать, что мероприятие вам понравилось и вы активно участвовали в нем.
Это помогает нам понять, что у нас есть сообщество, которое мы хотим видеть снова.
Не забудьте забрать на стойке информации брелок с ботом Android, который можно добавить на связку ключей.
Я все правильно сделал?
Спасибо!
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />val paringRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(true)
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
.build()
companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
object : CompanionDeviceManager.Callback() {
override fun onAssociationPending(intentSender: IntentSender) {
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}
override fun onAssociationCreated(associationInfo: AssociationInfo) { … }
override fun onFailure(error: CharSequence?) { … }
}
)class BleCompanionDeviceService : CompanionDeviceService() {
override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
when (event.event) {
DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
}
}
private fun onDeviceAppeared(associationId: Int) { … }
private fun onDeviceDisappeared(associationId: Int) { … }
}<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)val attributes = CallAttributesCompat(
displayName = "Kukk Nat",
address = Uri.parse("tel:+16505550199"),
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
)
callsManager.addCall(
attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
) {
// Coroutine CallControlScope active for the duration of the call
}callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
// 1. React to hardware mute toggles from Watch, Car, or Headset
launch {
isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
}
// 2. Observe available endpoints & request declarative switches
launch {
currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
}
requestEndpointChange(preferredBluetoothEndpoint)
}// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val attachment = FileAttachment(
fileName = "report.pdf",
size = 1_048_576L,
uri = fileUri,
mimeType = "application/pdf",
// Sensitive content will be masked in preview
isSensitiveContent = false,
)
val content = ShareableContent.withFiles(listOf(attachment))
val request = SharingRequest.withContent(content)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val request = SharingRequest.withoutContent()
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
// Open the Quick Share overlay in receive mode
session.launchWindow(activity, contentSelectionIntent = null)// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val urlAttachment = TextAttachment(
text = groupAddUrl,
type = TextAttachment.Type.URL,
)
val content = ShareableContent.withText(urlAttachment)
val appLink = AppLink("https://myapp.com/group/invite")
val request = SharingRequest.withContent(content, appLink)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)// Final API may undergo further updates
scope.launch {
for (event in session.sharingEvents) {
when (event) {
is SharingEvent.SendComplete ->
println("Send finished")
is SharingEvent.ReceiveComplete ->
println("Received, files=${event.content?.files?.size}")
else -> println("other event")
}
}
}