Перед прочтением этой статьи я рекомендую ознакомиться с концепцией Physical Web о которой я рассказывал в своей прошлой статье: Концепция Physical web. Bluetooth маячки. Сравнение стандартов iBeacon, AltBeacon и Eddystone.
Google's beacon platform. Часть 1 — Proximity beacon API
Google's beacon platform. Часть 2 — Nearby meassages API
Основным средством, в рамках Google's beacon platform, для работы на клиентской стороне с bluetooth маячками, является Nearby Messages API. В этой статье я расскажу как настроить проекты на а платформах Android и iOS, и добавить в приложение возможность получать и разбирать сообщения от ble-маячков.
Nearby Messages API
Nearby Messages API — это API, которое реализует парадигму publish-subscribe и позволяет разным устройствам публиковать, и подписываться на сообщения, таким образом обмениваться данными. Nearby Messages API является частью Nearby. Для обмена сообщениями устройства не обязательно должны находиться в одной сети, но должны быть подключены к интернету. В нашем случае подключение к интернету должно быть у того смартфона или планшета на котором мы хотим получать сообщения. Маячкам подключение к интернету не нужно! Nearby Messages API позволяет обмениваться сообщениями с помощью Bluetooth, Bluetooth Low Energy, Wi-Fi и даже ультразвука, но мы будем использовать только Bluetooth Low Energy что бы минимизировать потребление энергии.
Nearby Messages API на Android
Nearby Messages API доступен на устройствах с Android в библиотеке Google Play services версии 7.8.0 или выше.
Убедитесть что у вас установлена последняя версия клиентской библиотеки для Google Play на вашем хосте для разработки:
- Откройте Android SDK Manager.
- Перейдите в A ppearance & Behavior > System Settings > Android SDK > SDK Tools и убедитесь что установлены следующие пакеты:
- Google Play services
- Google Repository
Для использования Nearby Messages API, конечно же понадобится Google Account. Так же необходимо получить API ключ. Ключи для Android, iOS и Proximity Beacon API должны быть созданы в рамках одно проекта Google Developers Console. Очень советую ознакомиться с Best practices for securely using API keys
Настраиваем проект:
Открываем или создаём новый проект, открываем
build.gradle
файл и добавляем в него Google Play services client library как зависимость.
apply plugin: 'android'
...
dependencies {
compile 'com.google.android.gms:play-services-nearby:8.4.0'
}
<manifest xmlns:android="http://ift.tt/nIICcg"
package="com.google.sample.app" >
<application ...>
<meta-data
android:name="com.google.android.nearby.messages.API_KEY"
android:value="API_KEY" />
<activity>
...
</activity>
</application>
</manifest>
Создаём в нашем приложении GoogleApiClient и добавляем Nearby Messages API
Пример кода который показывает как добавить Nearby Messages API:
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Nearby.MESSAGES_API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
Получение сообщений
Для получения сообщений от маячков нам необходимо сперва на них подписаться, есть два способа как наше приложение может это сделать:
- В активном режиме приложения, в ответ на действия пользователя или события.
- В фоновом режиме, т.е. когда приложение неактивно
Nearby Messages API требует разрешения пользователя на запросы
publish()
и subscribe()
. Поэтому приложение должно проверять на что пользователь уже дал свое согласие и если такое отсутствует, то вызывать диалог запроса разрешений.
Что бы реализовать запрос разрешения у пользователя во время исполнения вашего приложения, вы можете:
- Присоединить
result callback
к вызовамpublish()
andsubscribe()
. - Использовать
Nearby.Messages.getPermissionStatus()
что бы проверить статус разрешения непосредственно перед вызовомpublish()
илиsubscribe()
Реализация result callback
Для проверки статус кода ошибки в
result callback
необходимо вызвать status.getStatusCode()
. Если получен статус код APP_NOT_OPTED_IN
отобразите диалог запроса разрешений вызовом status.startResolutionForResult()
и используйте onActivityResult()
что бы повторно реинициировать какие-либо невыполненные запросы подписки или публикации.
Следующий пример демонстрирует простой result callback, который проверяет, какие пользователь предоставил разрешения. Если пользователь не предоставил разрешений, вызывается status.startResolutionForResult()
, чтобы предложить пользователю разрешить Nearby Messages. В этом примере boolean mResolvingError
используется чтобы избежать многократного запуска таких предложений:
private void handleUnsuccessfulNearbyResult(Status status) {
Log.i(TAG, "Processing error, status = " + status);
if (mResolvingError) {
// Already attempting to resolve an error.
return;
} else if (status.hasResolution()) {
try {
mResolvingError = true;
status.startResolutionForResult(getActivity(),
Constants.REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
mResolvingError = false;
Log.i(TAG, "Failed to resolve error status.", e);
}
} else {
if (status.getStatusCode() == CommonStatusCodes.NETWORK_ERROR) {
Toast.makeText(getActivity().getApplicationContext(),
"No connectivity, cannot proceed. Fix in 'Settings' and try again.",
Toast.LENGTH_LONG).show();
} else {
// To keep things simple, pop a toast for all other error messages.
Toast.makeText(getActivity().getApplicationContext(), "Unsuccessful: " +
status.getStatusMessage(), Toast.LENGTH_LONG).show();
}
}
}
Вы также можете использовать этот обработчик(handler) для обработки любых других
NearbyMessageStatusCodes
полученных от операций или, например, статус кода CommonStatusCodes.NETWORK_ERROR.
Реиницализация невыполненных запросов
Следующий пример показывает реализацию метода
onActivityResult()
, который вызывается после того как пользователь отреагирует на диалог запроса разрешений. В случае если пользователь ответит согласием, любые ожидающие запросы подписки или публикации будут выполнены. А данном примере вызывается метод executePendingTasks()
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == Constants.REQUEST_RESOLVE_ERROR) {
// User was presented with the Nearby opt-in dialog and pressed "Allow".
mResolvingError = false;
if (resultCode == Activity.RESULT_OK) {
// Execute the pending subscription and publication tasks here.
mMainFragment.executePendingTasks();
} else if (resultCode == Activity.RESULT_CANCELED) {
// User declined to opt-in. Reset application state here.
} else {
Toast.makeText(this, "Failed to resolve error with code " + resultCode,
Toast.LENGTH_LONG).show();
}
}
Так же, чтобы уменьшить задержку при сканировании маячков, рекомендуется использовать Strategy.BLE_ONLY при вызове Nearby.Messages.subscribe(). Когда эта опция установлена, Nearby Messages API не задействует Wi-Fi сканирование или классические сканирования Bluetooth. Это уменьшает задержку обнаружения маячков, поскольку система не циклирует через все возможные типы сканирования, а так же уменьшает потреблении энергии.
Подписка в активном режиме:
Когда ваше приложение подписывается на сообщения от маячков будучи в активном режиме, сканирование производится непрерывно пока приложение не отпишется. Такую подписку рекомендуется использовать только когда ваше приложение активно, обычно в ответ на какие то действия пользователя.
Приложение может инициировать подписку в активном режиме вызвав метод Nearby.Messages.subscribe(GoogleApiClient, MessageListener, SubscribeOptions)
и установив для параметра Strategy
значение BLE_ONLY
.
// Create a new message listener.
mMessageListener = new MessageListener() {
@Override
public void onFound(Message message) {
// Do something with the message.
Log.i(TAG, "Found message: " + message);
}
// Called when a message is no longer detectable nearby.
public void onLost(Message message) {
// Take appropriate action here (update UI, etc.)
}
}
// Subscribe to receive messages.
Log.i(TAG, "Trying to subscribe.");
// Connect the GoogleApiClient.
if (!mGoogleApiClient.isConnected()) {
if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
} else {
SubscribeOptions options = new SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.setCallback(new SubscribeCallback() {
@Override
public void onExpired() {
Log.i(TAG, "No longer subscribing.");
}
}).build();
Nearby.Messages.subscribe(mGoogleApiClient, mMessageListener, options)
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Subscribed successfully.");
} else {
Log.i(TAG, "Could not subscribe.");
// Check whether consent was given;
// if not, prompt the user for consent.
handleUnsuccessfulNearbyResult(status);
}
}
});
}
Для уменьшения потребления энергии и соответственно продления срока работы аккумулятора, вызывайте
Nearby.Messages.unsubscribe() в методе
OnStop()вашего приложения. Когда подписка на сообщения больше не нужна, приложение должно отписаться вызвав метод
Nearby.Messages.unsubscribe(GoogleApiClient, MessageListener)`.
Подписка в фоновом режиме
Когда приложение подписывается на сообщения в фоновом режиме, срабатывает low-power сканирование при событиях включения экрана, даже когда приложение в настоящее время не активно. Вы можете использовать эти фоновые события low-power сканирования, чтобы "разбудить" приложение в ответ на конкретное сообщение. Фоновые подписки потребляют меньше энергии, чем подписка в активном режиме, но имеют более высокую задержку и низкую надежность.
Подписка в фоновом режиме инициируется вызовом метода Nearby.Messages.subscribe(GoogleApiClient, PendingIntent,SubscribeOptions)
и заданием для параметра Strategy
значения BLE_ONLY
.
// Subscribe to messages in the background.
private void backgroundSubscribe() {
// Connect the GoogleApiClient.
if (!mGoogleApiClient.isConnected()) {
if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
} else {
Log.i(TAG, "Subscribing for background updates.");
SubscribeOptions options = new SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.build();
Nearby.Messages.subscribe(mGoogleApiClient, getPendingIntent(), options)
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Subscribed successfully.");
} else {
Log.i(TAG, "Could not subscribe.");
handleUnsuccessfulNearbyResult(status);
}
}
});
}
}
private PendingIntent getPendingIntent() {
return PendingIntent.getService(getApplicationContext(), 0,
getBackgroundSubscribeServiceIntent(), PendingIntent.FLAG_UPDATE_CURRENT);
}
private Intent getBackgroundSubscribeServiceIntent() {
return new Intent(getApplicationContext(), BackgroundSubscribeIntentService.class);
}
protected void onHandleIntent(Intent intent) {
Nearby.Messages.handleIntent(intent, new MessageListener() {
@Override
public void onFound(Message message) {
Log.i(TAG, "Found message via PendingIntent: " + message);
}
@Override
public void onLost(Message message) {
Log.i(TAG, "Lost message via PendingIntent: " + message);
}
});
}
Ну и конечно же, если подписка нам больше не нужна, мы должны отписаться вызвав
Nearby.Messages.unsubscribe(GoogleApiClient, PendingIntent)
.
Разбор сообщений
Как мы уже знаем из первой части, каждое вложения состоит из следующих частей:
-Namespace: Идентификатор пространства имен.
-Type: Тип данных.
-Data: Значение данных вложения.
mMessageListener = new MessageListener() {
@Override
public void onFound(Message message) {
// Do something with the message here.
Log.i(TAG, "Message found: " + message);
Log.i(TAG, "Message string: " + new String(message.getContent()));
Log.i(TAG, "Message namespaced type: " + message.getNamespace() +
"/" + message.getType());
}
...
};
Стоит учесть что разбор содержания зависит от формата байт. Этот пример предполагает, что сообщения закодированы в UTF-8 строку, но ваши сообщения от маячков могут быть закодированы в другой формат.
Чтобы узнать какие пространства имен связаны с вашим проектом, можно вызвать namespaces.list.
Nearby Nearby meassages API на iOS
Для создания проекта использующего Nearby Messages API for iOS нам понадобиться Xcode версии 6.3 или старше.
Google Nearby Messages API для iOS доступен как пакет(pod) CocoaPods. CocoaPods — это менеджер зависимостей с открытым исходным кодом для Swift и Objective-C Cocoa проектов. Для получения дополнительной информации советую ознакомиться с CocoaPods Getting Started guide. Если он у вас не установлен, вы можете сделать это выполнив в терминале следующую комманду:
$ sudo gem install cocoapods
Устанавливаем Nearby messages API использую CocoaPods:
- Открываем проект или создаём новый, следует убедится что опция Use Automatic Reference Counting включена.
- Создаём файл с именем Podfile в директории проекта. Этот файл будет определять зависимости проекта.
- Добавляем зависимости в Podfile. Пример простого Podspec содержащего имя пакета которое для установки
source 'http://ift.tt/1czpBWo' platform :ios, '7.0' pod 'NearbyMessages'
- В терминале переходим в директорю в котрой находится Podfile
- Для установки вместе с зависимости API указанных в Podfile необходимо выполнить комманду:
$ pod install
После этого закрываем Xcode и затем двойным кликом по .xcworkspace проекта запускаем Xcode. Начиная с этого момента, вы должны использовать файл .xcworkspace для открытия проекта.
Как и в случае с Android, нам необходимо получить API ключ. Ключи для Android, iOS и Proximity Beacon API должны быть созданы в рамках одно проекта Google Developers Console. Очень советую ознакомиться с Best practices for securely using API keys
Теперь, когда всё настроено, мы можем создать объект
messageManager
и использовать API ключ созданный ранее
#import <GNSMessages.h>
GNSMessageManager *messageManager =
[[GNSMessageManager alloc] initWithAPIKey:@"API_KEY"];
Подписка в iOS
В iOS сканирование маячков происходит только когда приложение активно. Сканирование маячков в фоновом режиме недоступно для iOS. Что бы подписаться только на BLE маячки нужно задать
deviceTypesToDiscover
в параметрах подписки kGNSDeviceBLEBeacon
.
_beaconSubscription = [_messageManager
subscriptionWithMessageFoundHandler:myMessageFoundHandler
messageLostHandler:myMessageLostHandler
paramsBlock:^(GNSSubscriptionParams *params) {
params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
}];
Этот пример кода подписывается только на маячки нашего проекта и получает все сообщения от них.
Если мы хотим получать сообщения от маячков зарегистрированных с другим пространством имён, мы можем передать namespace в параметры подписки. Аналогично мы можем сделать и с типом сообщений которые хотим получать передав конкретный тип сообщений для фильтрации. Для это необходимо задействовать сканирование устройств в GNSStrategy
и пробросить значения пространства имен и типа подписки в параметры подписки.
_beaconSubscription = [_messageManager
subscriptionWithMessageFoundHandler:myMessageFoundHandler
messageLostHandler:myMessageLostHandler
paramsBlock:^(GNSSubscriptionParams *params) {
params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
params.messageNamespace = @"com.mycompany.mybeaconservice";
params.type = @"mybeacontype";
}];
По умолчанию, при подписке мы сканируем сразу оба типа маячков, Eddystone и iBeacon. Если у нас задействовано сканирование маячков iBeacon, пользователь получит запрос на использование геолокации. Info.plist приложения должен включать ключ
NSLocationAlwaysUsageDescription
с крткаим обьяcнением того, почему используеься геолокация. Советую ознакомиться с документацией Apple для деталей.
Если мы хотим сканировать только маячки Eddystone, мы можем отключить сканирование маячков iBeacon в GNSBeaconStrategy
. В таком случае iOS не будет запрашивать у пользователя разрешения на использование геолокации.
_beaconSubscription = [_messageManager
subscriptionWithMessageFoundHandler:myMessageFoundHandler
messageLostHandler:myMessageLostHandler
paramsBlock:^(GNSSubscriptionParams *params) {
params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
params.messageNamespace = @"com.mycompany.mybeaconservice";
params.type = @"mybeacontype";
params.beaconStrategy = [GNSBeaconStrategy strategyWithParamsBlock:^(GNSBeaconStrategyParams *params) {
params.includeIBeacons = NO;
};
}];
При сканировании маячков iBeacon, диалог запроса разрешений на геолокацию предшествует диалогу разрешений Nearby. Мы можем переопределить этот диалог, например, для того что бы обяьснить пользователю для чего запрашивается разрешение на геолокацию. Для этого необходимо задать
permissionRequestHandler
в отдельном блоке в параметрах подписки.
_beaconSubscription = [_messageManager
subscriptionWithMessageFoundHandler:myMessageFoundHandler
messageLostHandler:myMessageLostHandler
paramsBlock:^(GNSSubscriptionParams *params) {
params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
params.messageNamespace = @"com.mycompany.mybeaconservice";
params.type = @"mybeacontype";
params.beaconStrategy = [GNSBeaconStrategy strategyWithParamsBlock:^(GNSBeaconStrategyParams *params) {
params.includeIBeacons = NO;
};
params.permissionRequestHandler = ^(GNSPermissionHandler permissionHandler) {
// Show your custom dialog here, and don't forget to call permissionHandler after it is dismissed
permissionHandler(userGavePermission);
};
}];
Заключение
Я надеюсь что данный материал будет кому то полезен, сэкономит время и силы.
Комментарии (0)