今回はGoogleが新たに提供を開始したNeaby Messages APIについて書いていきたいと思います。
Neaby Messages APIとは
Neaby Messages APIは、Googleが提供しているAPIの一つで、近くにあるスマートフォンやタブレット端末同士でのメッセージのやり取りを可能にするAPIです。近距離での通信といえばNFCやBluetoothでのデータ送受信が頭に浮かびますが、Neaby Messages APIの場合はそうではなく、あくまでデータの送受信はサーバーを介して行われます。ただ、その送受信の際にBluetooth、BLE、WiFi、超音波などを用いることで相手を特定しています。
上記のような仕組みになりますのでネットワーク接続は必須となりますが、送信側の端末と受信側の端末が同じネットワークに接続している必要はありません。
ちなみにNearbyのもう一つのAPI、Nearby Connections APIは同じネットワークに接続している端末間でのデータの送受信を可能にするAPIですので注意が必要です。
事前準備
それではNeaby Messages APIを使うための準備を進めていきましょう。まずGoogle Developers ConsoleにてAPIキーを取得します。GoogleMapsなどのAPIキーを取得する場合と手順は同じです。詳しい手順はこちらに記載してあります。
次にプロジェクトにGoogle Play Servicesを追加します。
build.gradleに以下の記述を追加します。Nearby Messages APIはGoogle Play Services 7.8.0以降でのみ扱えます。今回は8.1.0を使用します。
apply plugin: 'android'
...
dependencies {
compile 'com.google.android.gms:play-services:8.1.0'
// Nearbyだけ追加したい場合は以下の記述でも問題ない
// compile 'com.google.android.gms:play-services-nearby:8.1.0'
}
AndroidManifestに以下の記述を追加します。
<application ...>
<meta-data
android:name="com.google.android.nearby.messages.API_KEY"
android:value="先ほど作成したAPIキー" />
<activity>
...
</activity>
</application>
これで準備は完了です。それでは以下に簡単な実装の流れを書いていきます。Google API Clientに接続
Nearby Messages APIを使うには、まずGoogle API Clientを作成する必要があります。この際、接続に成功した場合と失敗した場合のコールバックをそれぞれ追加することができます。
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Nearby.MESSAGES_API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
Google API Clientへの接続に成功した場合の処理を以下のように記述します。ここではNearby Messages APIを使うにあたってユーザーからパーミッションの許可を得ているかどうかを確認しています。そして許可を得ていることが確認できた場合、メッセージを受信できる状態に移行します。
@Override
public void onConnected(Bundle bundle) {
Nearby.Messages.getPermissionStatus(mGoogleApiClient)
.setResultCallback(new ErrorCheckingCallback("getPermissionStatus", new Runnable() {
@Override
public void run() {
// メッセージの受信
subscribe();
}
}));
}
こちらを参考にNearby Messages APIを使用した際の共通のコールバックとして以下のように定義しています。パーミッションエラーが出た場合のみユーザーにパーミッションの許可を促すダイアログを表示します。
private class ErrorCheckingCallback implements ResultCallback<Status> {
private final String method;
private final Runnable runOnSuccess;
private ErrorCheckingCallback(String method) {
this(method, null);
}
private ErrorCheckingCallback(String method, @Nullable Runnable runOnSuccess) {
this.method = method;
this.runOnSuccess = runOnSuccess;
}
@Override
public void onResult(@NonNull Status status) {
if (status.isSuccess()) {
Log.i(TAG, method + " succeeded.");
if (runOnSuccess != null) {
runOnSuccess.run();
}
} else {
// Currently, the only resolvable error is that the device is not opted
// in to Nearby. Starting the resolution displays an opt-in dialog.
if (status.hasResolution()) {
if (!mResolvingError) {
try {
// パーミッション確認ダイアログの表示
status.startResolutionForResult(MainActivity.this,
REQUEST_RESOLVE_ERROR);
mResolvingError = true;
} catch (IntentSender.SendIntentException e) {
Log.d(TAG, method + " failed with exception: " + e);
}
} else {
// This will be encountered on initial startup because we do
// both publish and subscribe together. So having a toast while
// resolving dialog is in progress is confusing, so just log it.
Log.i(TAG, method + " failed with status: " + status
+ " while resolving error.");
}
} else {
Log.d(TAG, method + " failed with : " + status
+ " resolving error: " + mResolvingError);
}
}
}
}
パーミッション確認ダイアログの結果はonActivityResultで受け取ります。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_RESOLVE_ERROR) {
mResolvingError = false;
// User was presented with the Nearby opt-in dialog and pressed "Allow".
if (resultCode == Activity.RESULT_OK) {
// メッセージの受信
subscribe();
} else {
Toast.makeText(this, "Failed to resolve error with code " + resultCode,
Toast.LENGTH_LONG).show();
}
}
}
またNearby Messages APIはバッテリーの消耗が激しいため、必ずライフサイクルに応じて接続を解除するようにしてください。
@Override
protected void onStart() {
super.onStart();
// 復帰時に再接続
if (!mGoogleApiClient.isConnected()) {
mGoogleApiClient.connect();
}
}
@Override
protected void onStop() {
// バッテリー消費を抑えるために必ず接続を解除する
if (mGoogleApiClient.isConnected() && !isChangingConfigurations()) {
unsubscribe();
unpublish();
mGoogleApiClient.disconnect();
}
super.onStop();
}
メッセージの送受信
まず送受信するメッセージはバイト配列でやり取りされます。データサイズは3KB以内が推奨とされていて、画像や動画などの大きなファイルのやり取りは想定されていません。 送信の場合、PublishOptionsでメッセージの送受信が有効な距離や送信状態を維持する時間などをStrategyにて設定することができます。またGoogle Play Services8.1.0以降から送信状態が終了した際のコールバックを設定することができます。
/**
* メッセージの送信
*/
private void publish() {
// GoogleApiClientに接続しているか確認
if (!isApiClientConnected()) {
return;
}
PublishOptions options = new PublishOptions.Builder()
.setStrategy(PUB_SUB_STRATEGY)
.setCallback(new PublishCallback() {
@Override
public void onExpired() {
super.onExpired();
// 送信状態が終了した時のコールバック
Log.d(TAG, "publish onExpired");
}
})
.build();
Nearby.Messages.publish(mGoogleApiClient, mMessage, options)
.setResultCallback(new ErrorCheckingCallback("publish"));
}
/**
* メッセージの送信解除
*/
private void unpublish() {
// GoogleApiClientに接続しているか確認
if (!isApiClientConnected()) {
return;
}
Nearby.Messages.unpublish(mGoogleApiClient, mMessage)
.setResultCallback(new ErrorCheckingCallback("unpublish"));
}
受信の場合は、あらかじめメッセージを受信した時の処理としてMessageListenerを定義しておきます。
mMessageListener = new MessageListener() {
@Override
public void onFound(final Message message) {
// メッセージを受信した時の処理
runOnUiThread(new Runnable() {
@Override
public void run() {
SimpleMessage simpleMessage = SimpleMessage.fromMessage(message);
Toast.makeText(getApplicationContext(), simpleMessage.getId(), Toast.LENGTH_LONG).show();
}
});
}
};
SubscribeOptionsではPublishOptionsと同様の設定に加えて、Strategyにて受信するメッセージのフィルターを設定することができます。
/**
* メッセージの受信
*/
private void subscribe() {
// GoogleApiClientに接続しているか確認
if (!isApiClientConnected()) {
return;
}
SubscribeOptions options = new SubscribeOptions.Builder()
.setStrategy(PUB_SUB_STRATEGY)
.setCallback(new SubscribeCallback() {
@Override
public void onExpired() {
super.onExpired();
// 受信状態が終了した時のコールバック
Log.d(TAG, "subscribe onExpired");
}
})
.build();
Nearby.Messages.subscribe(mGoogleApiClient, mMessageListener, options)
.setResultCallback(new ErrorCheckingCallback("subscribe"));
}
/**
* メッセージの受信解除
*/
private void unsubscribe() {
// GoogleApiClientに接続しているか確認
if (!isApiClientConnected()) {
return;
}
Nearby.Messages.unsubscribe(mGoogleApiClient, mMessageListener)
.setResultCallback(new ErrorCheckingCallback("unsubscribe"));
}
以上です。ソースコードはこちらに置いておきます。こちらのサンプルでは受信が成功すると相手の端末上のUUIDがToastで表示されます。
所感
アプリへ組み込む場面がかなり限定される機能ではありますが、今までQRコードを撮影したり、振った端末同士の位置情報を介していたID交換などがよりスムーズに行えるようになるかもしれないですね。実装自体はかなりお手軽というわけではなく、少しクセのあるAPIだとは思いますが、AndroidだけではなくiOSでも利用可能ということなので、そのあたりも含めて今後の開発に期待していきたいです。参考
Nearby | Google DevelopersNearby Messages APIでチャットみたいなのを作ってみる


