📜 ⬆️ ⬇️

Android: Networking with Nearby (PlayServices API)

More recently, Google has provided mobile developers Android new technology of network data exchange - Nearby. It became immediately interesting to me, as it allows you to establish a local connection between Android devices without any problems! There is no need to force the user to enter the IP address and port, he simply initiates the connection, and clients simply connect to it. On the page describing the technology the following uses are indicated:
- multiplayer games on individual screens - players play network games, each from their device, which are networked (classics of the genre);
- multiplayer games on the general screen - in this case, GoogleTV can act as a server, the main gameplay will take place on it, and all connected will use their phone / tablet as a game controller (as in the photo!);
- and of course for any data exchange between various Android devices.



Already, you can try out this technology in the Beach Buggy Racing game:

After the main material of the article was prepared, I wondered how well the system controls the sequence of delivered packages. Especially for these purposes, I have prepared a small application for sending photos as text. Tens of thousands of packets of 2048 characters each were sent from one device to another. The order was not broken, not a single packet was lost. For control of the delivery sequence, we had to pay the delivery time, it increased.

Consider the principles of working with Nearby.
In order not to create a bike, I took the original example and reviewed it with the translation of all comments.
First of all, make sure that your phone has the latest version of GooglePlay services - https://play.google.com/store/apps/details?id=com.google.android.gms .
We now turn to the main points of the project:
The PlayServices library has been added to the project (to the file “build.gradle”), it allows you to work with Nearby:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile 'com.google.android.gms:play-services:7.0.0' } 

Working with Nearby can be divided into the following steps:
1) Creating the main access object - GoogleApiClient. Run client. Stop the client
2) Launching a complaint of intent to become an access point
3) Start searching for points to connect
4) Attach to point
5) Processing applications for accession
6) Connection control
7) Acceptance and processing of messages from the opponent
8) Sending a message
Consider everything in order.
')
Creating the main access object is GoogleApiClient. Run client. Stop the client. It's simple. In the activity constructor, we create the main access object for Nearby. When an activity starts, we start it; when an activity is stopped, we disconnect from the network.

 @Override protected void onCreate(Bundle savedInstanceState) { … mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(Nearby.CONNECTIONS_API) .build(); … } @Override public void onStart() { super.onStart(); Log.d(TAG, "onStart"); mGoogleApiClient.connect(); } @Override public void onStop() { super.onStop(); Log.d(TAG, "onStop"); if (mGoogleApiClient != null) { mGoogleApiClient.disconnect(); } } 

The next stage is the launch of a complaint of intent to become an access point , the startAdvertising method:
 private void startAdvertising() { debugLog("startAdvertising"); if (!isConnectedToNetwork()) { debugLog("startAdvertising: not connected to WiFi network."); return; } //           . List<AppIdentifier> appIdentifierList = new ArrayList<>(); appIdentifierList.add(new AppIdentifier(getPackageName())); AppMetadata appMetadata = new AppMetadata(appIdentifierList); //  .    .    ,       ,  "LGE Nexus 5" String name = null; Nearby.Connections.startAdvertising(mGoogleApiClient, name, appMetadata, TIMEOUT_ADVERTISE, this).setResultCallback(new ResultCallback<Connections.StartAdvertisingResult>() { @Override public void onResult(Connections.StartAdvertisingResult result) { Log.d(TAG, "startAdvertising:onResult:" + result); if (result.getStatus().isSuccess()) { debugLog("startAdvertising:onResult: SUCCESS"); updateViewVisibility(STATE_ADVERTISING); } else { debugLog("startAdvertising:onResult: FAILURE "); //      'Advertise'    ,    'STATUS_ALREADY_ADVERTISING' int statusCode = result.getStatus().getStatusCode(); if (statusCode == ConnectionsStatusCodes.STATUS_ALREADY_ADVERTISING) { debugLog("STATUS_ALREADY_ADVERTISING"); } else { updateViewVisibility(STATE_READY); } } } }); } 

If the user constantly “clicks” on the “Advertise” button, he will receive a message saying that everything is working fine, relax :) - STATUS_ALREADY_ADVERTISING

The third stage - Starting the search for points for the connection :
 private void startDiscovery() { debugLog("startDiscovery"); if (!isConnectedToNetwork()) { debugLog("startDiscovery: not connected to WiFi network."); return; } //       Nearby    . String serviceId = getString(R.string.service_id); Nearby.Connections.startDiscovery(mGoogleApiClient, serviceId, TIMEOUT_DISCOVER, this) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { if (status.isSuccess()) { debugLog("startDiscovery:onResult: SUCCESS"); updateViewVisibility(STATE_DISCOVERING); } else { debugLog("startDiscovery:onResult: FAILURE"); //      'Discover'    ,     'STATUS_ALREADY_DISCOVERING' int statusCode = status.getStatusCode(); if (statusCode == ConnectionsStatusCodes.STATUS_ALREADY_DISCOVERING) { debugLog("STATUS_ALREADY_DISCOVERING"); } else { updateViewVisibility(STATE_READY); } } } }); } 

Everything is very transparent and clear. Just start searching for access points.

Now consider - Attaching to a data exchange point . To do this, first you need to find available access points, and then join the right one. The onEndpointFound method is specifically designed to report a new point found:
  @Override public void onEndpointFound(final String endpointId, String deviceId, String serviceId, final String endpointName) { Log.d(TAG, "onEndpointFound:" + endpointId + ":" + endpointName); //    .    ,      . if (mMyListDialog == null) { // Configure the AlertDialog that the MyListDialog wraps AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle("Endpoint(s) Found") .setCancelable(true) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mMyListDialog.dismiss(); } }); //     mMyListDialog = new MyListDialog(this, builder, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String selectedEndpointName = mMyListDialog.getItemKey(which); String selectedEndpointId = mMyListDialog.getItemValue(which); MainActivity.this.connectTo(selectedEndpointId, selectedEndpointName); mMyListDialog.dismiss(); } }); } mMyListDialog.addItem(endpointName, endpointId); mMyListDialog.show(); } 

The “connectTo” method implements the dialog for selecting a point to which it is possible to connect . When choosing one of the options, go to the direct connection:
  /** *       . * @param endpointId -       * @param endpointName -   ,    .       . * */ private void connectTo(String endpointId, final String endpointName) { debugLog("connectTo:" + endpointId + ":" + endpointName); //       . String myName = null; byte[] myPayload = null; Nearby.Connections.sendConnectionRequest(mGoogleApiClient, myName, endpointId, myPayload, new Connections.ConnectionResponseCallback() { @Override public void onConnectionResponse(String endpointId, Status status, byte[] bytes) { Log.d(TAG, "onConnectionResponse:" + endpointId + ":" + status); if (status.isSuccess()) { debugLog("onConnectionResponse: " + endpointName + " SUCCESS"); Toast.makeText(MainActivity.this, "Connected to " + endpointName, Toast.LENGTH_SHORT).show(); mOtherEndpointId = endpointId; updateViewVisibility(STATE_CONNECTED); } else { debugLog("onConnectionResponse: " + endpointName + " FAILURE"); } } }, this); } 

If everything went well, you can start messaging.

The onConnectionRequest method is used for processing applications for connection :
  @Override public void onConnectionRequest(final String endpointId, String deviceId, String endpointName, byte[] payload) { debugLog("onConnectionRequest:" + endpointId + ":" + endpointName); //          .            . mConnectionRequestDialog = new AlertDialog.Builder(this) .setTitle("Connection Request") .setMessage("Do you want to connect to " + endpointName + "?") .setCancelable(false) .setPositiveButton("Connect", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { byte[] payload = null; Nearby.Connections.acceptConnectionRequest(mGoogleApiClient, endpointId, payload, MainActivity.this) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { if (status.isSuccess()) { debugLog("acceptConnectionRequest: SUCCESS"); mOtherEndpointId = endpointId; updateViewVisibility(STATE_CONNECTED); } else { debugLog("acceptConnectionRequest: FAILURE"); } } }); } }) .setNegativeButton("No", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Nearby.Connections.rejectConnectionRequest(mGoogleApiClient, endpointId); } }).create(); mConnectionRequestDialog.show(); } 

A number of methods are responsible for controlling the connection:
onDisconnected - processing a connection break;
onConnected - connection handling;
onEndpointLost - break connection handling;
onConnectionSuspended - handling connection interruption;
onConnectionFailed - handling failed connections.
Control over the reconnection of clients (for example, if the connection is broken when the user leaves the WiFi zone) is completely left to the developer.

To process incoming messages, you must rewrite the onMessageReceived method:
  @Override public void onMessageReceived(String endpointId, byte[] payload, boolean isReliable) { // ,     debugLog("onMessageReceived:" + endpointId + ":" + new String(payload)); } 

Sending messages is carried out using two methods:
1) Nearby.Connections.sendReliableMessage - sending reliable messages;
2) Nearby.Connections.sendUnreliableMessage - sending unreliable messages.
When using the first method, the system itself controls the correctness of the sequence of the delivered messages, in the second case the sequence can be broken, since there is no control. But the second method is faster, so it is better to use it when you need to send a large number of messages, for example, when you send the cursor position on the screen.

In the resources you need to specify the service identifier for which customers will be searched and connected.
 <?xml version="1.0" encoding="utf-8"?> <resources> ... <string name="service_id"><!--   ,  ..--></string> ... </resources> 

To resolve an application complaint, the following must be written in the manifest:
 <application> <meta-data android:name="com.google.android.gms.nearby.connection.SERVICE_ID" android:value="@string/service_id" /> <activity> ... </activity> </application> 

If you build this application and run it on your devices, you can watch the following:

At first glance it may seem that using the Nearby API is difficult and cumbersome, but this is only at first glance. As a result, the developer gets a ready, reliable, controlled tool for network data exchange. Personally, I really liked this solution, no longer need to control the order in which data packets arrive, ask users to enter ip address and socket number, make additional settings ... Beauty!

Sources with comments
Separately APK

Thanks for the help in preparing the inatale material!

Source: https://habr.com/ru/post/256071/


All Articles