Google недавно анонсировал новый API для подключений к сети, который значительно улучшает первую версию за счет поддержки автономного режима, операций с более высокой пропускной способностью и более низкой задержки. Этот новый API использует комбинацию точек доступа Wi-Fi, Bluetooth с низким энергопотреблением и классический Bluetooth для обнаружения и рекламы на ближайших устройствах. Но разработчикам не нужно беспокоиться о сложностях управления соединениями Bluetooth и Wi-Fi, поскольку все это скрыто под капотом. В этом посте я хотел показать, как будет выглядеть создание приложения с новым API.

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

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

Первым шагом была установка Google Play Services 11 и компиляция play-services-near в build.gradle.

compile 'com.google.android.gms:play-services-nearby:11.0.4'

Затем приложение должно запросить необходимые разрешения для использования API Nearby Connections. Поскольку система обрабатывает все детали того, как устанавливаются соединения, пользователю необходимо предоставить все разрешения, чтобы это работало. Обратите внимание, что ACCESS_COARSE_LOCATION необходимо дополнительно запросить во время выполнения, поскольку оно классифицируется как опасное разрешение.

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses ... "android.permission.ACCESS_WIFI_STATE" />
<uses ... "android.permission.CHANGE_WIFI_STATE" />
<uses ... "android.permission.ACCESS_COARSE_LOCATION" />

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

public class AdvertiserActivity extends Activity implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
...
@Override
public void onConnected(@Nullable Bundle bundle) {
    // GoogleApiClient is now connected, we can start advertising
    startAdvertising();
}
// client's name that's visible to other devices when connecting
public static final String CLIENT_NAME = "Teacher";
/**
 *    service id. discoverer and advertiser can use this id to 
 *    verify each other before connecting
 * /
public static final String SERVICE_ID = "Class302";
/**
 *    The connection strategy we'll use.
 *    P2P_STAR denotes there is 1 advertiser and N discoverers
 * 
public static final String STRATEGY = Strategy.P2P_STAR;
/**
 *    Set device to advertising mode by broadcasting to other 
 *    devices that are currently in discovery mode.
 * /
private void startAdvertising() {
    Nearby.Connections.startAdvertising(
            mGoogleApiClient,
            CLIENT_NAME, 
            SERVICE_ID,
            mConnectionLifecycleCallback,
            new AdvertisingOptions(STRATEGY))
            .setResultCallback(
                    new ResultCallback<Connections.StartAdvertisingResult>() {
                        @Override
                        public void onResult(@NonNull Connections.StartAdvertisingResult result) {
                 if (result.getStatus().isSuccess()) {
                    Log.i(TAG, "Advertising endpoint);
                 } else {
                    Log.i(TAG, "unable to start advertising);
                 }
         }
    });
}
/** 
 *    These callbacks are made when other devices:
 *    1. tries to initiate a connection
 *    2. completes a connection attempt
 *    3. disconnects from the connection
 * /
private final ConnectionLifecycleCallback mConnectionLifecycleCallback =
        new ConnectionLifecycleCallback() {
            @Override
            public void onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
                Log.i(TAG, endpointId + " connection initiated");
                establishConnection(endpointId);
            }

            @Override
            public void onConnectionResult(String endpointId, ConnectionResolution result) {
                markStudentAsPresent(endpointId);
            }

            @Override
            public void onDisconnected(String endpointId) {
                Log.i(TAG, endpointId + " disconnected");
            }
        };

В этом случае я решил начать рекламу со стратегии P2P_STAR. Это позволяет каждому устройству быть концентратором (который может принимать соединения) или лучом (который может инициировать соединение), но не обоими одновременно. Другими словами, это стратегия «1 к N». Другой вариант был бы P2P_CLUSTER, где каждое устройство может быть одновременно концентратором или лучом, создавая соединение в форме кластера.

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

private void startDiscovery() {
    Nearby.Connections.startDiscovery(
            mGoogleApiClient,
            SERVICE_ID,
            mEndpointDiscoveryCallback,
            new DiscoveryOptions(STRATEGY))
            .setResultCallback(
                    new ResultCallback<Status>() {
                    @Override
                    public void onResult(@NonNull Status status {
                       if (status.isSuccess()) {
                        Log.i(TAG, "Now looking for advertiser");
                       } else {
                        Log.i(TAG, "Unable to start discovery");
                       }
                  }
                    });
}
/** 
 *    These callbacks are made when :
 *    1. an endpoint that we can connect to is found
 *    2. completes a connection attempt
 * /
private final EndpointDiscoveryCallback mEndpointDiscoveryCallback =
        new EndpointDiscoveryCallback() {
            @Override
            public void onEndpointFound(
                    String endpointId, DiscoveredEndpointInfo dei) {
                requestConnection();
            }

            @Override
            public void onEndpointLost(String endpointId) {
                // A previously discovered endpoint has gone away,           
               // perhaps we might want to do some cleanup here
                Log.i(TAG, endpointId + " endpoint lost):
          }
        };

После установления соединения между двумя устройствами они могут начать отправку друг другу полезной нагрузки в виде байтов, файлов или потока (для аудио / видео).

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

В будущих публикациях я надеюсь изучить, как учитель может отправлять своим ученикам задания через это приложение с помощью API Nearby Connections.