Android scanning ble in background

Android Bluetooth Low Energy (BLE) — готовим правильно, часть #1 (scanning)

Содержание

Часть #1 (scanning), вы здесь.

Могу точно сказать – это было сложней, чем представлял, мне пришлось приложить немало усилий для стабильной работы под Android. Я изучил много статей в свободном доступе, некоторые оказались ошибочными, многие были очень полезными и помогли в деле. В этой серии статей я хочу описать свои выводы, чтобы вы не тратили уйму времени на поиски как я.

Особенности работы BLE под Android

Google документация по BLE очень общая, в некоторых случаях нет важной информации или она устарела, примеры приложений не показывают, как правильно использовать BLE. Я обнаружил лишь несколько источников, как правильно сделать BLE. Презентация Stuart Kent дает замечательный материал для старта. Для некоторых продвинутых тем есть хорошая статья Nordic.

Android BLE API это низкоуровневые операции, в реальных приложениях нужно использовать несколько слоев абстракции (как например сделано «из коробки» в iOS-CoreBluetooth). Обычно нужно самостоятельно сделать: очередь команд, bonding, обслуживание соединений, обработка ошибок и багов, мультипоточный доступ . Самые известные библиотеки: SweetBlue, RxAndroidBle и Nordic. На мой взгляд самая легкая для изучения — Nordic, см. детали тут.

Производители делают изменения в Android BLE стеке или полностью заменяют на свою реализацию. И надо учитывать разницу поведения для разных устройств в приложении. То что прекрасно работает на одном телефоне, может не работать на других! В целом не все так плохо, например реализация Samsung сделана лучше собственной реализации от Google!

В Android есть несколько известных (и неизвестных) багов которые должны быть обработаны, особенно в версиях 4,5 и 6. Более поздние версии работают намного лучше, но тоже имеют определенные проблемы, такие как случайные сбои соединения с ошибкой 133. Подробнее об этом ниже.

Не претендую на то, что я решил все проблемы, но мне удалось выйти на «приемлемый» уровень. Начнем со сканирования.

Сканирование устройств

Перед подключением к устройству вам нужно его просканировать. Это делается при помощи класса BluetoothLeScanner :

Сканер пытается найти устройства в соответствии с настройками filters и scanSettings , при обнаружении устройства вызывается scanCallback :

В результате сканирования мы получаем экземпляр ScanResult , в котором есть объект BluetoothDevice , его используют для подключения к устройству. Но прежде чем начать подключаться, поговорим о сканировании подробнее, ScanResult содержит несколько полезных сведений об устройстве:

Advertisement data — массив байтов с информацией об устройстве, для большинства устройств это имя и UUID сервисов, можно задать в filters имя устройства и UUID сервисов для поиска конкретных устройств.

RSSI уровень — уровень сигнала (насколько близко устройство).

… дополнительные данные, см. документацию по ScanResult здесь.

Помним про жизненный цикл Activity , onScanResult может вызываться многократно для одних и тех же устройств, при пересоздании Activity сканирование может запускаться повторно, вызываю лавину вызовов onScanResult .

Настраиваем фильтр для сканирования

Вообще можно передать null вместо фильтров и получить все ближайшие устройства, иногда это полезно, но чаще требуются устройства с определенным именем или набором UUID сервисов.

Сканирование устройств по UUID сервиса

Используется если вам необходимо найти устройства определенной категории, например мониторы артериального давления со стандартным сервисным UUID: 1810. При сканировании устройство может содержать в Advertisement data UUID сервис, который характеризует это устройство. На самом деле эти данные ненадежные, фактически сервисы могут не поддерживаться, или подделываться Advertisement data данные, в общем тут есть творческий момент.

Прим. переводчика: одно из моих устройств со специфичной прошивкой, вообще не содержало список UUID сервисов в Advertisement data, хотя все остальные прошивки этого устройства работали ожидаемо.

Пример сканирования службы с артериальным давлением:

Обратите внимание на короткий UUID (например 1810 ), он называется 16-bit UUID и является частью длинного 128-bit UUID (в данном случае 00001810-000000-1000-8000-000-00805f9b34fb ). Короткий UUID это BASE_PART длинного UUID, см. спецификацию здесь.

Сканирование устройств по имени

Поиск устройств использует точное совпадение имени устройства, обычно это применяется в двух случаях:

поиск конкретного устройства

поиск конкретной модели устройства, например, мой нагрудный напульсник Polar H7 определяется как «Polar H7 391BBB014», первая часть — «Polar H7» общая для всех таких устройств этой модели, а последняя часть «391BBB014» — уникальный серийный номер. Это очень распространенная практика. Если вы хотите найти все устройства «Polar H7», то фильтр по имени вам не поможет, придется искать подстроку у всех отсканированных устройств в ScanResult . Пример с поиском точно по имени:

Читайте также:  Как создать мессенджер для android своими руками пошаговая инструкция

Сканирование устройств по MAC-адресам.

Обычно применяется для переподключения к уже известным устройствам. Обычно мы не знаем MAC-адрес девайса, если не сканировали его раньше, иногда адрес печатается на коробке или на корпусе самого устройства, особенно это касается медицинских приборов. Существует другой способ повторного подключения, но в некоторых случаях придется еще раз сканировать устройство, например при очистке кеша Bluetooth.

Вероятно вы уже поняли, что можно комбинировать в фильтре UUID, имя и MAC-адрес устройства. Выглядит неплохо, но на практике я не применял такое. Хотя может быть вам это пригодится.

Настройка ScanSettings

ScanSettings объясняют Android как сканировать устройства. Там есть ряд настроек, которые можно задать, ниже полный пример:

ScanMode

Безусловно, это самый важный параметр. Определяет метод и время сканирования в Bluetooth стеке. Такая операция требует много энергии и необходим контроль над этим процессом, чтобы не разрядить батарею телефона быстро. Есть 4 режима работы, в соответствии с руководством Nordics и официальной документацией:

SCAN_MODE_LOW_POWER . В этом режиме Android сканирует 0.5с, потом делает паузу на 4.5с. Поиск может занять относительно длительное время, зависит от того насколько часто устройство посылает пакет advertisement данных.

SCAN_MODE_BALANCED . Время сканирования: 2с, время паузы: 3с, «компромиссный» режим работы.

SCAN_MODE_LOW_LATENCY . В этом случае, Android сканирует непрерывно, что очевидно требует больше энергозатрат, при этом получаются лучшие результаты сканирования. Режим подходит если вы хотите найти свое устройство как можно быстрее. Не стоит использовать для длительного сканирования.

SCAN_MODE_OPPORTUNISTIC . Результаты будут получены, если сканирование выполняется другими приложениями! Строго говоря, это вообще не гарантирует, что обнаружится ваше устройство. Стек Android использует этот режим в случае долгого сканирования, для понижения качества результатов (см. ниже «Непрерывное сканирование»).

Callback Type

Эта настройка контролирует как будет вызываться callback со ScanResult в соответствии с заданными фильтрами, есть 3 варианта:

CALLBACK_TYPE_ALL_MATCHES . Callback будет вызывать каждый раз, при получении advertisement пакета от устройств. На практике — каждые 200-500мс будет срабатывать сallback, в зависимости от частоты отправки advertisement пакетов устройствами.

CALLBACK_TYPE_FIRST_MATCH . Callback сработает один раз для устройства, даже если оно далее будет снова посылать advertisement пакеты.

CALLBACK_TYPE_MATCH_LOST . Callback будет вызван, если получен первый advertisement пакет от устройства и дальнейшие advertisement пакеты не обнаружены. Немного странное поведение.

В практике обычно используются настройка CALLBACK_TYPE_ALL_MATCHES или CALLBACK_TYPE_FIRST_MATCH . Правильный тип зависит от конкретного случая. Если не знаете — используйте CALLBACK_TYPE_ALL_MATCHES , это дает больше контроля при получении callback, если вы останавливаете сканирование после получения нужных результатов — фактически это CALLBACK_TYPE_FIRST_MATCH .

Match mode

Настройка того, как Android определяет «совпадения».

MATCH_MODE_AGGRESSIVE . Агрессивность обуславливается поиском минимального количества advertisement пакетов и устройств даже со слабым сигналом.

MATCH_MODE_STICKY . В противоположность, этот режим требует большего количества advertisement пакетов и хорошего уровня сигнала от устройств.

Я не тестировал эти настройки подробно, но я в основном использую MATCH_MODE_AGGRESSIVE , это помогает быстрее найти устройства.

Number of matches

Параметр определяет сколько advertisement данных необходимо для совпадения.

MATCH_NUM_ONE_ADVERTISEMENT . Одного пакета достаточно.

MATCH_NUM_FEW_ADVERTISEMENT . Несколько пакетов нужно для соответствия.

MATCH_NUM_MAX_ADVERTISEMENT . Максимальное количество advertisement данных, которые устройство может обработать за один временной кадр.

Нет большой необходимости в таком низкоуровневом контроле. Все что вам надо — быстро найти свое устройство, обычно используются первые 2 варианта.

Report delay

Задержка для вызова сallback в миллисекундах. Если она больше нуля, Android будет собирать результаты в течение этого времени и вышлет их сразу все в обработчике onBatchScanResults . Важно понимать что onScanResult не будет вызываться. Обычно применяется, когда есть несколько устройств одного типа и мы хотим дать пользователю выбрать одно из них. Единственная проблема здесь — предоставить информацию пользователю для выбора, это должен быть не только MAC-адрес (например имя устройства).

Важно: есть известный баг для Samsung S6 / Samsung S6 Edge, когда все результаты сканирования имеют один и тот же RSSI (уровень сигнала) при задержке больше нуля.

Кеширование Android Bluetooth стека

В результате процесса сканирования вы получаете список BLE устройств и при этом данные устройств «кешируются» в Bluetooth стеке. Там хранится основная информация: имя, MAC-адрес, тип адреса (публичный, случайный), тип устройства (Classic, Dual, BLE) и т.д. Android нужны эти данные, чтобы подключится к устройству быстрее. Он кеширует все устройства, которые видит при сканировании. Для каждого из них записывается небольшой файл с данными. Когда вы пытаетесь подключиться к устройству, стек Android ищет соответствующий файл, чтобы прочитать данные для подключения. Важный момент — одного MAC-адреса недостаточно для успешного подключения к устройству!

Очистка кеша

Bluetooth кеш, как и любой другой, не существует вечно, есть 3 ситуации, когда он очищается:

Выключение и включение системного переключателя Bluetooth

Очистка данных приложения (в ручном режиме в настройках телефона)

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

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

Читайте также:  Удаление фейсбук с андроид

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

Непрерывное сканирование?

Вообще хорошая практика – избегать непрерывного сканирования потому что, это очень энергоемкая операция, а пользователи любят, когда батарея их смартфона работает долго. Если вам действительно нужно постоянное сканирование, например при поиске BLE-маячков, выберите настройки сканирования с низким потреблением и ограничивайте время сканирования, например когда приложение находится только на переднем плане (foreground), либо сканируйте с перерывами.

Плохая новость в том, что Google в последнее время ограничивает (неофициально) непрерывное сканирование:

c Android 8.1 сканирование без фильтров блокируется при выключенном экране. Если у вас нет никаких ScanFilters , Android приостановит сканирование, когда экран выключен и продолжит, когда экран снова будет включен. Комментарии от Google. Это очевидно очередной способ энергосбережения от Google.

c Android 7 вы можете сканировать только в течение 30 минут, после чего Android меняет параметры на SCAN_MODE_OPPORTUNISTIC . Очевидное решение, перезапускать сканирование с периодом менее, чем 30 мин. Посмотрите commit в исходном коде.

с Android 7 запуск и останов сканирования более 5 раз за 30 секунд временно отключает сканирование.

Непрерывное сканирование в фоне

Google значительно усложнил сканирование на переднем плане. Для фонового режима вы столкнетесь с еще большими трудностями! Новые версии Android имеют лимиты на работу служб в фоновом режиме, обычно после 10 минут работы, фоновый сервис прекращает свою работу принудительно. Посмотрите возможные решения этой проблемы:

Проверка разрешений (permissions)

Есть еще несколько важных моментов, прежде чем мы закончим статью. Для начала сканирования нужны системные разрешения (permissions):

Убедитесь, что все разрешения одобрены, или запросите их у пользователя. Разрешение ACCESS_COARSE_LOCATION Google считает «опасным» и для него требуется обязательное согласие пользователя.

Прим. переводчика, в моем проекте для корректной работы с BLE потребовалось еще 2 разрешения: ACCESS_FINE_LOCATION (для API ACCESS_BACKGROUND_LOCATION обсуждение на Stackoverflow.

В итоге полный список разрешений включая версию Android10:

После получения всех нужный разрешений, нужно проверить включен Bluetooth, если нет — используйте Intent для запуска запроса на включение:

Заключение

Мы научились запускать сканирование BLE устройств с учетом жизненного цикла Activity (Fragment / Service), использовать фильтры и различные настройки сканирования, также узнали все нужные разрешения (permissions) для удачного запуска сканирования и особенности работы Android-Bluetooth кеша. В следующей статье мы погрузимся глубже в процесс подключения и отключения к устройствам.

Источник

Background BLE scan in DOZE mode on Android devices

Hi there! We are the Navigine team. For 8 years we have been providing integrated positioning mobile technologies that enable advanced indoor navigation and proximity solutions. Today we decided to open the doors to our technology and talk about how to scan BLE devices when the Android application is killed and in background mode.

Background BLE scans for Android devices could be divided into two different groups depending on the mechanisms used for scheduling background tasks. During the last few years, Android changed background processing and added Doze mode, limited implicit broadcasts, and limited background behavior and. But most of the new solutions will not work for old Android versions and all the old solutions will be killed by Android in new versions. So in this article, we will review two different strategies for scanning in the background — for versions before API 26 (Oreo) and after.

Background tasks in Android devices with an API level less than 26 (Oreo)

For all old android versions up to Android Oreo, there is a working method for scanning in the background using alarm manager and service with the broadcast receiver. The broadcast receiver will get wake up alarm and start service where Navigine SDK will be initialized and started for 10 seconds. After this time SDK will be finished and service will be killed. This alarm could be set up with at least a one-minute intervals and will work without any restrictions in old devices.

NavigineReceiverExample.java for devices with an API level less than 26

Inside the service class, you can declare handling intent from the broadcast receiver and starting Navigine SDK or other manipulations you want to do. This solution will not work in Android devices with the version starting from 26 because Android will not allow starting the service when your app is killed so here we need better solutions.

Background tasks starting from Android Oreo

Starting from API 26 Android started blocking services running when the device is killed and provided few solutions for running your tasks in the background. But the main difficulty is, these solutions also will not work in Doze mode (If a user leaves a device unplugged and stationary for a period of time, with the screen off, the device enters Doze mode). Here is the restrictions list when app entered Doze mode:

Screenshot from Google developers website

Further, we will talk about each solution that we found and will provide code examples. Each of them could be used if you will decide not to fight with Doze mode. As we need to scan BLE devices every few minutes we need to run periodic background tasks. Also, we will provide two more solutions that will ignore Doze mode but will need more resources or will have extra notifications.

Periodic Job Scheduler.

Job Scheduler could be used starting from Android API level 23 and replaced Service + Alarm Manager for running background tasks. Periodic Job Scheduler could be waked up at least every 15 minutes and has a lot of opportunities if you need some conditions for your job scheduler like requiring to run only if the device has an internet connection, should be charging, etc. For using this method you should extend the JobService class and override onStartJob method for starting your task. And start Job Scheduler to run a periodic job. Do not forget to add your service to your manifest file. Here is the code example of how to start periodic job scheduler:

Periodic job scheduler

This solution runs perfectly while the device is not in the Doze mode, otherwise, it will just skip jobs and start new periods. In the screenshot below you can see how well it works. In this particular case, we added flex-time to the periodic job and it starts earlier than it should.

Logcat screenshot for periodic job scheduler.

Alarm with .setAndAllowWhileIdle() and JobScheduler.
Common Alarm Managers with .set() or .setRepeating() will work well only when the device is not in Doze mode because Android will ignore them and reschedule for next time. Using .setAndAllowWhileIdle() will allow the alarm to be called, but the job scheduler will be ignored even in this case.
However, you can add . setOverrideDeadline(time in milliseconds) method to your job scheduler and it will run this scheduled job when the device exits Doze mode. Here is the code example for the onReceive method of our BroadcastReceiver extension:

Broadcast Receiver example using Alarm Manager and Job Scheduler

In our case, we decided to kill these jobs if more than 30 minutes passed after they have been scheduled. This solution also will not solve the problem with running background tasks in Doze mode, but jobs will not be skipped like in a periodic job scheduler. Keep in mind, that even in Doze mode these alarms will be ignored if you try to call them more then once every 9 minutes.

Waking up every 5 minutes with an Alarm Manager and starting Job Scheduler. (Not in Doze mode)

Unfortunately, none of these methods will allow you to execute background tasks in Doze mode, even WorkManager will not help you with this task. But there are few other solutions you can use.

Running foreground service.
Foreground services will run without any cancels and without entering Doze mode, so your background tasks will run infinitely. But this solution has one disadvantage, your app will always have pinned notification in the devices’ status bar.

Screenshot of foreground service running after the app has been killed.

As you can see in this screenshot, the user will always see this notification which could not be canceled. Using foreground service will increase the chances that your application will be removed faster than it could be. Also, it’s worth noting that your application will use more power and device resources.
Here is the foreground service code example:

Foreground Service example

Using push notifications.
Another way to run background tasks even in Doze mode to send notifications to your application from the server. These notifications will force your application to exit Doze mode and yourи job could be done. But in this case, you will need to spend more resources on running and maintaining your service. You can find more info about using Firebase Cloud Messaging to run your background services by this link.

Conclusion

Over time Google hardens the ability to run background tasks, so developers need to come up with new solutions and tricks, like sending push notifications for waking the device up from Doze mode. In defense of the android, we can say that they are using their actions to protect users from unwanted and unnecessary actions in the background that will use the phone’s resources and perform unwanted, and sometimes malicious tasks. By the way, the above solutions will help you to work respecting the requirements of Android or bypass the Doze mode.

Источник

Оцените статью