Google Play Services 7.8.0がリリースされました。
今回のバージョンアップの目玉はなんといってもNearbyでしょう。
このNearbyですが、近くの端末にメッセージを送れるNearby Messagesと近くの端末と接続を行うNearby Connectionsの2つの機能があります。
この記事では、Nearby Messagesを使って近くの端末がメッセージを送り合える簡単なチャットのようなものを作ってみます。
Nearby APIの有効化
Nearby Messages APIを使うには、Google Developers ConsoleでAPIを有効にし、アプリ用のクライアントキーを作成する必要があります。
プロジェクトの作成
- Google Developers Consoleにアクセス
-
プロジェクトを作成
をクリック - プロジェクト名に適当な名前を入力してプロジェクトを作成
Nearby Messages APIの有効化
- Google Developers Consoleにアクセス
- 作成したプロジェクトを選択
- サイドメニューからAPIを選択
- 検索窓に
Nearby Messages API
と入力して検索結果をクリック -
APIを有効にする
をクリック
アプリ署名のSHA1を確認する
アプリの署名に使用しているkeystoreのSHA1を確認します。debug.keystoreでも構いません。
SHA1は以下のコマンドで確認できます。
確認したSHA1は次の手順で使用します。
keytool -exportcert -alias [使用するエイリアス] -keystore [使用するkeystore] -list -v
クライアントキーを作成する
この手順で作成したクライアントキーはアプリ側で使用します。
- Google Developers Consoleにアクセス
-
APIと認証 > 認証情報
を選択 -
新しいキーを作成
をクリック -
Android キー
をクリック - 入力欄に
前の手順で確認したSHA1;パッケージ名
の形式で入力 - キーが作成されるのでAPIキーの項目をメモ
Androidプロジェクトの準備
Google Play Servicesの導入
Google Play ServicesのNearbyをbuild.gradle
に追加
compile 'com.google.android.gms:play-services-nearby:7.8.0'
AndroidManifestに設定追加
application
タグ内にmeta-data
を追加します。android:value
には上の手順で作成したAPIキーを入力してください。
<application ...>
<meta-data
android:name="com.google.android.nearby.messages.API_KEY"
android:value="作成したAPIキー" />
<activity>
...
</activit>
</application>
ここまででNearby Messages APIを使用するための準備が整いました。
Nearby Messages APIを実装する
早速APIを使うための実装を行っていきます。
私の好みの問題でKotlinで実装していますがJavaでもやることは変わりません。
コードの全体はGithubに公開しているのでそちらを見てください。
Github: chibatching/NearbySample
Google API Clientの用意
APIに接続するためのクライアントを作成します。callbacks
とfailedListener
については後ほど。
var mGoogleApiClient = GoogleApiClient.Builder(this)
.addApi(Nearby.MESSAGES_API)
.addConnectionCallbacks(callbacks)
.addOnConnectionFailedListener(failedListener)
.build()
次にクライアントからAPIに接続します。
if (!mGoogleApiClient.isConnected()) {
mGoogleApiClient.connect()
}
ここで接続に成功した時にaddConnectionCallbacks
で登録したコールバックが、失敗した時にはaddOnConnectionFailedListener
で登録したリスナーが呼ばれます。
パーミッションの確認
Nearby Messages APIは実行時にユーザからの許可が必要です。
そのため、接続の成功時にNearby Messages APIを使用する許可があるか確認しています。
確認結果を受け取るコールバックがGoogleApiClient.ConnectionCallbacks
を実装したNearbyResultCallback
です。
// Nearby APIへの接続コールバック
private inner class NearbyConnectionCallbacks : GoogleApiClient.ConnectionCallbacks {
override fun onConnected(connectionHint: Bundle?) {
Log.d(TAG, "onConnected: $connectionHint")
// 接続成功したらパーミッションを確認する
Nearby.Messages.getPermissionStatus(googleApiClient)
.setResultCallback(NearbyResultCallback("getPermissionStatus", {
// パーミッション取得済みの場合はメッセージのsubscribeを始める
Log.d(TAG, "Start subscribe")
Nearby.Messages.subscribe(googleApiClient, messageListener)
}))
}
override fun onConnectionSuspended(p0: Int) {
}
}
// Nearby Messages APIとの通信結果コールバック
private inner class NearbyResultCallback(
val method: String, val runOnSuccess: () -> Unit) : ResultCallback<Status> {
override fun onResult(status: Status) {
if (status.isSuccess()) {
// 通信結果が成功だったときには登録した成功時の処理を実行する
Log.d(TAG, "$method succeeded.")
this@MainActivity.runOnUiThread { runOnSuccess() }
} else {
// ユーザに許可を求めることができるか
if (status.hasResolution()) {
if (!mResolvingError) {
try {
// ユーザに許可を求めるダイアログを表示する
status.startResolutionForResult(this@MainActivity, REQUEST_RESOLVE_ERROR)
// publishとsubscribeのタイミングで2度呼ばれることがあるのでフラグで管理
mResolvingError = true
} catch (e: IntentSender.SendIntentException) {
Toast.makeText(this@MainActivity, "$method failed with exception: " + e, Toast.LENGTH_SHORT).show()
}
} else {
// 二重で呼ばれたときにここに来る
Log.d(TAG, "$method failed with status: $status while resolving error.")
}
} else {
Log.d(TAG, "$method failed with: $status resolving error: $mResolvingError")
}
}
}
}
ユーザにパーミッションを要求するときはダイアログが表示されます。
このダイアログでユーザが許可したかどうか、onActivityResult
で取得します。
// Nearbyのパーミッションダイアログの結果を受け取る
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_RESOLVE_ERROR) {
mResolvingError = false
if (resultCode == Activity.RESULT_OK) {
// パーミッションを取得したらsubscribeを始める
Log.d(TAG, "Start subscribe")
Nearby.Messages.subscribe(googleApiClient, messageListener)
} else {
Log.d(TAG, "Failed to resolve error with code $resultCode")
}
}
}
メッセージの送受信
ここまで来れば送信、受信どちらも難しくありません。
送信
チャットぽいものを作るので、送信ボタンを押したらEditTextの内容を送ります。
メッセージのコンテンツはバイト配列形式にする必要があります。
Message.MAX_CONTENT_SIZE_BYTES
を見ると102400バイトまで送れるようなのでメタデータと一緒にJSONにして送信しています。
// 送信ボタンを押したらEditTextの内容を送信する
sendButton.setOnClickListener {
// メッセージデータのモデルを生成する
val chatMessage = ChatMessage(editText.getText().toString(), System.currentTimeMillis(), true)
// EditTextのクリアとhintにメッセージ表示
editText.setText("")
textInputLayout.setHint("Sending...")
if (getCurrentFocus() != null) {
// IMEを隠す
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0)
}
// Publish
Nearby.Messages.publish(googleApiClient, Message(chatMessage.toString().toByteArray("UTF-8")), strategy)
.setResultCallback(NearbyResultCallback("send", {
// When send succeeded, show my message
textInputLayout.setHint("Input message")
addNewMessage(chatMessage)
}))
}
// メッセージデータのモデル
public data class ChatMessage(
val text: String,
val timestamp: Long,
val self: Boolean,
val id: String = UUID.randomUUID().toString()) : Comparable<ChatMessage> {
companion object {
// JSONからメッセージデータを生成
fun fromJson(jsonString: String) : ChatMessage {
val json = JSONObject(jsonString)
return ChatMessage(json.getString("text"), json.getLong("timestamp"), json.getBoolean("self"), json.getString("id"))
}
}
// メッセージデータをJSONに変換
override fun toString(): String {
val json = JSONObject()
json.put("id", id)
json.put("text", text)
json.put("timestamp", timestamp)
json.put("self", self)
return json.toString()
}
...
}
受信
受信はいままでのコードにもちょくちょく出てきますが、以下のコードで待機を開始します。
メッセージが届けばリスナーの処理を実行します。今回はメッセージをリストに追加していきます。
Nearby.Messages.subscribe(googleApiClient, messageListener)
val messageListener = object : MessageListener() {
override fun onFound(message: Message?) {
Log.d(TAG, "onFound: ${message?.toString()}")
if (message != null) {
addNewMessage(ChatMessage.fromJson(String(message.getContent(), "UTF-8")))
}
}
}
// メッセージをリストに追加してタイムスタンプ順に並び替え
private fun addNewMessage(message: ChatMessage) {
if (!messageList.contains(message)) {
messageList.add(message)
Collections.sort(messageList)
this@MainActivity.runOnUiThread {
messageAdapter.notifyDataSetChanged()
}
}
}
接続解除
バッテリー持ちに大きく影響するので、アプリがバックグラウンドになった際や必要無くなったときには接続を解除します。
override fun onStart() {
super.onStart()
if (!googleApiClient.isConnected()) {
// バックグラウンドから復帰したら接続し直す
googleApiClient.connect()
}
}
override fun onStop() {
if (googleApiClient.isConnected()) {
Nearby.Messages.unsubscribe(googleApiClient, messageListener)
// 本当はpublishもunpublishしたほうがいい
}
googleApiClient.disconnect()
super.onStop()
}
注意点
(追記) 試していて軽くはまったところをメモ
- Google API ClientでNearbyに接続した際、publishされていてTTLの時間内(デフォルト300秒)のものがまとめて受信される
- アプリをバックグラウンドから復帰して再接続した時に重複チェックが必要
- TTLはpublish時に渡すstrategyで変更可能
- メッセージをunpublishすると、まだ受信していない端末にはメッセージが届かなくなる
- 今回のサンプルでunpublishをしていない理由
- TTLが過ぎて自然消滅するのを待つ
動かしてみる
複数の端末にアプリをインストール、すべての端末でNearbyの許可を出したら準備完了です。
一つの端末でメッセージを送信すると他のすべての端末でメッセージが表示されるはずです!
おわり
端末をAPIに接続して、ユーザからNearbyの使用許可を取得する部分は若干手間ですが、メッセージの送受信は非常に簡単に扱うことが出来ます。
メッセージも102400バイトと意外と大きなサイズが送れるみたいなので色々な使い方ができそうですね。
コード全体はこちら:Github