Статьи

Сервисы Google Play: использование API ближайших подключений

Одним из многих API, доступных в Службах Google Play, является API ближайших подключений . Представленная в начале 2015 года, эта платформа позволяет вам установить одно устройство, на котором выполняется ваше приложение, в качестве хоста и подключить к нему несколько других устройств для связи по локальной сети (LAN).

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

Как только ваша первоначальная программа будет создана в Android Studio, вам нужно будет импортировать библиотеку Play Services в ваше приложение. Для этого поместите следующую строку кода в узел зависимости файла build.gradle . На момент написания этой статьи Play Services 7.5.0 была самой последней версией для разработки.

1
compile ‘com.google.android.gms:play-services:7.5.0’

Как только Play Services будет включен в ваше приложение, вы можете закрыть build.gradle и открыть AndroidManifest.xml . Поскольку эта функция использует для связи локальную сеть, вам необходимо включить разрешение ACCESS_NETWORK_STATE в манифест.

1
<uses-permission android:name=»android.permission.ACCESS_NETWORK_STATE» />

Затем вам нужно будет добавить часть meta-data в узел application который определяет идентификатор службы, который будет использоваться вашим приложением, чтобы оно могло обнаружить хост-рекламу с таким же идентификатором. В этом примере наш идентификатор службы определен в tutsplus_service_id как tutsplus_service_id .

1
<meta-data android:name=»com.google.android.gms.nearby.connection.SERVICE_ID» android:value=»@string/service_id» />

Когда вы закончите с манифестом, вы можете перейти к MainActivity.java . Это класс, в котором мы будем реализовывать как рекламу, так и открытие. В MainActivity вы также будете управлять отправкой сообщений между различными устройствами.

Чтобы начать использовать API-интерфейс Nearby Connections, необходимо настроить и подключиться к клиенту Google API. Начните с реализации ConnectionCallbacks и OnConnectionFailedListener в верхней части вашего класса. Пока мы добавляем наши интерфейсы, давайте также включим три, которые необходимы для API, и OnClickListener .

1
2
3
4
5
6
7
8
public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener,
       Connections.ConnectionRequestListener,
       Connections.MessageListener,
       Connections.EndpointDiscoveryListener,
       View.OnClickListener {
        
       …

Давайте теперь создадим переменные-члены, которые нам понадобятся для этого урока, в верхней части класса MainActivity . Для краткости я просто упомяну, что макет для этого класса состоит из ListView для отображения сообщений, поля EditText с Button для отправки сообщений после подключения, Button для рекламы, подключения или отключения в зависимости от роль устройства и TextView для отображения базовой информации о состоянии.

Вы заметите, что есть два логических флага для обозначения, подключено ли устройство или нет, и если это хост соединения, GoogleApiClient , необходимый для использования API-интерфейса GoogleApiClient Connections, и массив целых чисел для отслеживания сети. типы соединений, которые мы будем поддерживать для этого API.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private GoogleApiClient mGoogleApiClient;
 
private Spinner mTypeSpinner;
private TextView mStatusText;
private Button mConnectionButton;
private Button mSendButton;
private ListView mListView;
private ViewGroup mSendTextContainer;
private EditText mSendEditText;
 
private ArrayAdapter<String> mMessageAdapter;
 
private boolean mIsHost;
private boolean mIsConnected;
 
private String mRemoteHostEndpoint;
private List<String> mRemotePeerEndpoints = new ArrayList<String>();
 
private static final long CONNECTION_TIME_OUT = 10000L;
 
private static int[] NETWORK_TYPES = {ConnectivityManager.TYPE_WIFI,
        ConnectivityManager.TYPE_ETHERNET };

Если вы уже работали с какими-либо классами API Google Android, последний этап настройки должен выглядеть довольно знакомым. Вам нужно инициализировать GoogleApiClient и подключиться к нему в onCreate .

1
2
3
4
5
mGoogleApiClient = new GoogleApiClient.Builder( this )
       .addConnectionCallbacks( this )
       .addOnConnectionFailedListener( this )
       .addApi( Nearby.CONNECTIONS_API )
       .build();

В onStart и onStop мы обрабатываем подключение и отключение.

01
02
03
04
05
06
07
08
09
10
11
12
13
@Override
protected void onStart() {
    super.onStart();
    mGoogleApiClient.connect();
}
 
@Override
protected void onStop() {
    super.onStop();
    if( mGoogleApiClient != null && mGoogleApiClient.isConnected() ) {
        mGoogleApiClient.disconnect();
    }
}

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

Сама реклама довольно проста. Вам просто нужно проверить, что устройство имеет приемлемый тип соединения, а затем вызвать Nearby.Connections.StartAdvertising с соответствующими параметрами. Это заставит устройство объявлять через вашу локальную сеть, что оно доступно для приема подключений от других приложений.

В этом примере мы пропускаем десять секунд на рекламу. Тем не менее, вы можете передать значение 0 для рекламы на неопределенный срок. В следующем коде isConnectedToNetwork является вспомогательным методом, предназначенным для проверки необходимости рекламы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void advertise() {
    if( !isConnectedToNetwork() )
        return;
 
    String name = «Nearby Advertising»;
 
    Nearby.Connections.startAdvertising( mGoogleApiClient, name, null,
    CONNECTION_TIME_OUT, this )
      .setResultCallback( new ResultCallback<Connections.StartAdvertisingResult>() {
        @Override
        public void onResult( Connections.StartAdvertisingResult result ) {
            if( result.getStatus().isSuccess() ) {
                mStatusText.setText(«Advertising»);
            }
        }
    });
}
 
private boolean isConnectedToNetwork() {
    ConnectivityManager connManager =
            (ConnectivityManager) getSystemService( Context.CONNECTIVITY_SERVICE );
    for( int networkType : NETWORK_TYPES ) {
        NetworkInfo info = connManager.getNetworkInfo( networkType );
        if( info != null && info.isConnectedOrConnecting() ) {
            return true;
        }
    }
    return false;
}
Пример приложения в режиме рекламы

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

1
public void onConnectionRequest(final String remoteEndpointId, final String remoteDeviceId, final String remoteEndpointName, byte[] payload)

Используя этот метод, вы можете принять или отклонить соединение. Чтобы принять запрос, вы вызываете Nearby.Connections.acceptConnectionRequest с помощью ResultsCallback . Затем вы можете выполнять действия в зависимости от того, успешно ли установлено соединение или нет.

В этом примере мы просто добавим удаленную конечную точку в список, чтобы отслеживать ее и транслировать всем подключенным узлам, подключенным этим новым устройством. Если по какой-либо причине вы решите, что устройство не должно подключаться к вашему приложению, вы можете отклонить его, вызвав Nearby.Connections.rejectConnectionRequest .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public void onConnectionRequest(final String remoteEndpointId,
 final String remoteDeviceId,
 final String remoteEndpointName, byte[] payload) {
    if( mIsHost ) {
        Nearby.Connections.acceptConnectionRequest( mGoogleApiClient,
        remoteEndpointId, payload, this ).setResultCallback
        (new ResultCallback<Status>() {
            @Override
            public void onResult(Status status) {
                if( status.isSuccess() ) {
                    if( !mRemotePeerEndpoints.contains( remoteEndpointId ) ) {
                        mRemotePeerEndpoints.add( remoteEndpointId );
                    }
 
                    mMessageAdapter.add(remoteDeviceId + » connected!»);
                    mMessageAdapter.notifyDataSetChanged();
                    sendMessage(remoteDeviceId + » connected!»);
 
                    mSendTextContainer.setVisibility( View.VISIBLE );
                }
            }
        });
    } else {
        Nearby.Connections.rejectConnectionRequest(mGoogleApiClient, remoteEndpointId );
    }
}
Пример хоста, принимающего соединения от пиров

Как и в случае с рекламой, обнаружение зависит от подключения к GoogleApiClient и наличия приемлемого сетевого подключения. Вы можете запустить обнаружение, передав идентификатор службы приложения в метод Nearby.Connections.startDiscovery , который Nearby.Connections.startDiscovery устройство вашего пользователя в режим обнаружения.

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

В этом примере мы предполагаем, что одновременно размещается только одна реклама хоста, поэтому мы немедленно Nearby.Connections.sendConnectionRequest подключение, вызвав Nearby.Connections.sendConnectionRequest . Если хост принимает или отклоняет соединение, будет вызван обратный вызов результата sendConnectionRequest . Если соединение принято, статус будет установлен как успешный, и мы можем сохранить идентификатор конечной точки хоста и подготовиться к отправке сообщений через канал соединения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private void discover() {
    if( !isConnectedToNetwork() )
        return;
 
    String serviceId = getString( R.string.service_id );
    Nearby.Connections.startDiscovery(mGoogleApiClient, serviceId, 10000L, this)
        .setResultCallback(new ResultCallback<Status>() {
                @Override
                public void onResult(Status status) {
                    if (status.isSuccess()) {
                        mStatusText.setText( «Discovering» );
                    } else {
                        Log.e( «TutsPlus», «Discovering failed: » + status.getStatusMessage() );
                    }
                }
            });
}
 
@Override
public void onEndpointFound(String endpointId, String deviceId,
  final String serviceId, String endpointName) {
    byte[] payload = null;
 
    Nearby.Connections.sendConnectionRequest( mGoogleApiClient, deviceId,
        endpointId, payload, new Connections.ConnectionResponseCallback() {
 
        @Override
        public void onConnectionResponse(String endpointId, Status status, byte[] bytes) {
            if( status.isSuccess() ) {
                mStatusText.setText( «Connected to: » + endpointId );
                Nearby.Connections.stopDiscovery(mGoogleApiClient, serviceId);
                mRemoteHostEndpoint = endpointId;
                mSendTextContainer.setVisibility(View.VISIBLE);
 
                if( !mIsHost ) {
                    mIsConnected = true;
                }
            } else {
                mStatusText.setText( «Connection to » + endpointId + » failed» );
                if( !mIsHost ) {
                    mIsConnected = false;
                }
            }
        }
    }, this );
}
Обнаружение хозяина

В ситуации, когда вы прослушиваете несколько конечных точек, чтобы предоставить своим пользователям onEndpointLost метод onEndpointLost сообщит вам, прекратил ли хост рекламу, прежде чем ваш пользователь попытался подключиться к нему. onDisconnected вызов onDisconnected также доступен для клиентских устройств и может использоваться для повторного подключения к рекламным хостам в случае неожиданного отключения.

Как только ваши устройства соединились, пришло время начать общение. Существует два вида сообщений, которые можно отправлять: надежные и ненадежные . Если вы знакомы с сетевыми технологиями, вы можете рассматривать их с точки зрения TCP (протокол управления передачей) и UDP (протокол пользовательских дейтаграмм). Упрощенное объяснение состоит в том, что надежные сообщения будут повторять попытки отправить сообщение, если они потерпят неудачу, тогда как ненадежные сообщения просто отбрасывают данные, если они не были успешно отправлены и получены.

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

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

1
2
3
4
5
6
7
8
9
@Override
public void onMessageReceived(String endpointId, byte[] payload, boolean isReliable) {
    mMessageAdapter.add( new String( payload ) );
    mMessageAdapter.notifyDataSetChanged();
 
    if( mIsHost ) {
        sendMessage( new String( payload ) );
    }
}

Метод sendMessage — это вспомогательный метод, который демонстрирует две версии Nearby.Connections.sendReliableMessage . Для хост-приложений sendReliableMessage примет список конечных точек для отправки сообщения. Это позволяет вам общаться с несколькими устройствами с помощью одной строки кода. Для клиентов сообщения должны отправляться только на хост, поэтому в качестве параметра необходимо GoogleApiClient только имя хоста с GoogleApiClient байтов GoogleApiClient и сообщения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private void sendMessage( String message ) {
    if( mIsHost ) {
        Nearby.Connections.sendReliableMessage( mGoogleApiClient,
          mRemotePeerEndpoints,
          message.getBytes() );
         
        mMessageAdapter.add( message );
        mMessageAdapter.notifyDataSetChanged();
    } else {
        Nearby.Connections.sendReliableMessage( mGoogleApiClient,
          mRemoteHostEndpoint,
          ( Nearby.Connections.getLocalDeviceId( mGoogleApiClient ) + » says: » + message ).getBytes() );
    }
}
Отправка сообщений

Когда вы будете готовы отключить приложение на стороне хоста или на стороне клиента, необходимо выполнить небольшую очистку. Для хостов вы должны прекратить рекламу и отключить все ваши конечные точки.

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

Если вы пытаетесь отключиться от клиента, который еще не подключился к хосту, вам просто нужно остановить обнаружение. Если вы уже подключились к хосту, вы вызываете disconnectFromEndpoint и API будет обрабатывать разрыв соединения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void disconnect() {
    if( !isConnectedToNetwork() )
        return;
 
    if( mIsHost ) {
        sendMessage( «Shutting down host» );
        Nearby.Connections.stopAdvertising( mGoogleApiClient );
        Nearby.Connections.stopAllEndpoints( mGoogleApiClient );
        mIsHost = false;
        mStatusText.setText( «Not connected» );
        mRemotePeerEndpoints.clear();
    } else {
        if( !mIsConnected || TextUtils.isEmpty( mRemoteHostEndpoint ) ) {
            Nearby.Connections.stopDiscovery( mGoogleApiClient, getString( R.string.service_id ) );
            return;
        }
 
        sendMessage( «Disconnecting» );
        Nearby.Connections.disconnectFromEndpoint( mGoogleApiClient, mRemoteHostEndpoint );
        mRemoteHostEndpoint = null;
        mStatusText.setText( «Disconnected» );
    }
 
    mIsConnected = false;
}
Очистить связь

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

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

Поскольку API ближних подключений продолжает расти с добавлением маяков Eddystone и Bluetooth, это станет бесценным навыком при разработке приложений для Android.