NearbyでiOSとAndroidで相互通信をする方法
iOSとAndroidでデバイス間通信をしようとすると、真っ先にBluetoothやWi-Fiなどが上げられると思います。
しかし、実際に作成しようとすると、それぞれのレシーバーの作成や連携に苦戦することになります。
そうした面倒なやり取りを1つのライブラリで解決できるとすればどうでしょうか?
今回はそんな便利なライブラリ Nearby を使ってiOSとAndroidで通信をする方法をご紹介します。
目次
そもそもNearbyって何?
Nearby とは、Googleが開発した近距離通信用のプラットフォーム/ライブラリです。
この Nearby を使用することで、インターネットを使用しなくてもデバイス間通信が可能になります。
また、Android同士だけの場合に限りますが、近距離接続ができるようになるため、マルチプレイヤーゲームやコンテンツの共有なども可能になります。
(Nearby Connections API というものです)
今回ご紹介するのは Nearby の中の機能の1つである Nearby Messaging API です。
これは、AndroidとiOSの両プラットフォームでのメッセージ通信を可能にするだけでなく、BLEビーコンとの通信も両立してくれる優れものです。
なお、Nearby ではBluetooth、BLE、Wi-Fi、超音波(非可聴音)の組み合わせでデバイス間通信を実現しているそうです。
環境
今回の環境はこんな感じです。
- 開発環境
-
Xcode 10.2.1 Android Studio 3.4.1 CocoaPods 1.7.0
なお、記事作成時点での Nearby の利用制限は下記のとおりです。
- 利用制限
-
Android Android 2.3以上
Google Playサービス 7.8.0以上iOS 7.0以上
開発
APIキーを取得する
Nearby を使うため、APIキーを取得しましょう。
Google Cloud Console から Nearby のAPIを有効にします。
Nearby のAPIが有効になったら、アプリで利用可能にするため、APIキーを控えておきましょう。
iOSでNearbyを使う
ライブラリをインストール
まずは CocoaPods で Nearby をインストールしましょう。
1 |
pod 'NearbyMessages' |
※Cocoapods のインストール方法や使い方については割愛します。
Bridging Headerを作る (Swiftのみ)
プロジェクト内に BridgingHeader.h を作成し、下記の内容を記載して保存します。
1 2 3 4 5 6 |
#ifndef BridgeHeader_h #define BridgeHeader_h #import #endif |
プロジェクトの TARGETS 内にある 「Objective-C Bridging Header」 に先ほど作成したファイルまでのパスをしています。
1 |
${PROJECT_NAME}/BridgingHeader.h |
info.plistにプライバシー設定を追加
NearbyではBluetoothやマイクなど、プライバシーに関する機能で通信を行います。
そのため info.plist にプライバシー設定を追加する必要があります。
1 2 3 4 |
NSBluetoothPeripheralUsageDescription 通信でBluetoothを使います NSMicrophoneUsageDescription 通信でマイクを使います |
レイアウトを設定してプログラムを書く
ではレイアウトの作成をし、プログラムを作っていきます。
まずは storyboard に下記のようにパラメータを設定します。
続いて ViewController.swift にプログラムを書いていきます。
1 2 3 4 5 6 7 8 9 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
import UIKit class ViewController: UIViewController { // チャット履歴ビュー @IBOutlet weak var chatView: UITextView! // テキスト入力エリア @IBOutlet weak var textField: UITextField! let messageType:String = "[他のアプリと被らないキーとなる文字列]" var messageManager:GNSMessageManager! var publication:GNSPublication! var subscription:GNSSubscription! override func viewDidLoad() { super.viewDidLoad() // メッセージマネージャーを作成 let apiKey:String = "[Google Cloud Console で作成したAPIキー]" self.messageManager = GNSMessageManager(apiKey: apiKey) // チャット領域を初期化 self.chatView.text = "" // メッセージ購読開始 self.startSubscribe() } override func viewWillDisappear(_ animated: Bool) { // 配信停止 self.stopPublish() super.viewWillDisappear(animated) } /// メッセージ購読 func startSubscribe() { // メッセージを購読する self.subscription = self.messageManager.subscription(messageFoundHandler: { (message:GNSMessage?) in guard let message = message else { return } // 自分のアプリが配信したメッセージのみ有効 if message.type.compare(self.messageType) == .orderedSame { // テキストデータを取得 let text = String(data: message.content, encoding: String.Encoding.utf8)! // メッセージをチャット画面に追加 self.addChatText(name: "Others", text: text) } }, messageLostHandler: { (message:GNSMessage?) in // メッセージが範囲外や配信停止になった場合 }) } /// 送信ボタン押下時 /// /// - parameter sender: ボタンオブジェクト @IBAction func touchUpSendButton(_ sender: UIButton) { if let value = self.textField.text { // メッセージを配信する self.startPublish(text: value) // メッセージをチャット画面に追加 self.addChatText(name: "Me", text: value) } // キーボードを閉じてテキストを初期化 self.textField.resignFirstResponder() self.textField.text = "" } /// メッセージ配信開始 /// /// - parameter text: 配信テキスト func startPublish(text:String) { // 配信中のデータが有る場合、配信を停止 self.stopPublish() // メッセージを公開する let gnsMessage = GNSMessage(content: text.data(using: .utf8), type: self.messageType) self.publication = messageManager.publication(with: gnsMessage) } /// メッセージ配信停止 func stopPublish() { if self.publication != nil { self.publication = nil } } /// チャットテキスト追加 /// /// - parameter name: 名前 /// - parameter text: テキスト func addChatText(name:String, text:String) { let logText:String = self.chatView.text! self.chatView.text = "【" + name + "】" + " " + text + "\r\n" + logText } } |
これで Nearby Messaging API によるテキストの送受信ができるようになりました。
※ここではプライバシーを意識した確認メッセージ等はすべて省略しています。
※このままコピペをしてもAppleの審査は通りませんので、Nearbyの公式ドキュメントをご確認ください。
※バックグラウンドでも受信をしたい場合 Capabilities の Background Modes を設定する必要があります。
AndroidでNearbyを使う
ライブラリのインストール
GradleでNearbyのライブラリをインストールします。
1 2 3 4 5 6 |
apply plugin: 'android' ... dependencies { implementation 'com.google.android.gms:play-services-nearby:16.0.0' } |
APIキーを設定
AndroidManifest.xml に APIキーを設定します。
1 |
... |
レイアウトを設定してプログラムを書く
続いて画面のレイアウトとプログラムを書いていきましょう。
1 2 3 4 5 6 |
<!--?xml version="1.0" encoding="utf-8"?--> <button> </button> |
1 2 3 4 5 6 7 8 9 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
import android.content.Context import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.ArrayAdapter import android.widget.Button import android.widget.EditText import android.widget.ListView import com.google.android.gms.nearby.Nearby import com.google.android.gms.nearby.messages.BleSignal import com.google.android.gms.nearby.messages.Distance import com.google.android.gms.nearby.messages.Message import com.google.android.gms.nearby.messages.MessageListener class MainActivity : AppCompatActivity() { lateinit var logListView: ListView lateinit var listViewAdapter: ArrayAdapter lateinit var sendButton: Button lateinit var textField: EditText lateinit var inputMethodManager: InputMethodManager private var message: Message? = null private val messageType = "[他のアプリと被らないキーとなる文字列]" lateinit var messageListener: MessageListener override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) this.inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager // チャットログの表示領域を設定 this.listViewAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1) this.logListView = findViewById(R.id.logListView) as ListView this.logListView.adapter = this.listViewAdapter // チャットテキストの入力領域と送信ボタンを設定 this.textField = findViewById(R.id.textField) as EditText this.sendButton = findViewById(R.id.sendButton) as Button this.sendButton.setOnClickListener { val text = this.textField.text.toString() // メッセージを配信 this.startPublish(text) // メッセージをチャット画面に追加 addChatText("Me", text) // キーボードを閉じてテキストを初期化 this.inputMethodManager.hideSoftInputFromWindow(currentFocus!!.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) this.textField.setText("") } // メッセージリスナーを作成 this.messageListener = object : MessageListener() { override fun onFound(message: Message?) { // アプリ用メッセージタイプの場合 if (message != null && message.type.compareTo(messageType) == 0) { // テキストデータを取得 val text = String(message.content) // メッセージをチャット画面に追加 addChatText("Others", text) } } override fun onLost(message: Message?) {} override fun onBleSignalChanged(message: Message?, bleSignal: BleSignal?) {} override fun onDistanceChanged(message: Message?, distance: Distance?) {} } } override fun onStart() { super.onStart() // メッセージ購読開始 this.startSubscribe() } override fun onStop() { // メッセージ購読解除 this.stopSubscribe() // メッセージ配信停止 this.stopPublish() super.onStop() } /** * メッセージ リスト追加 * * @param name 名前 * @param text テキスト */ fun addChatText(name:String, text:String) { listViewAdapter.insert("【" + name + "】 " + text, 0) } /** * メッセージ購読開始 */ fun startSubscribe() { Nearby.getMessagesClient(this).subscribe(this.messageListener) } /** * メッセージ購読解除 */ fun stopSubscribe() { Nearby.getMessagesClient(this).unsubscribe(this.messageListener) } /** * メッセージ配信開始 * * @param text 配信テキスト */ fun startPublish(text:String) { // 配信中のメッセージを停止 this.stopPublish() // メッセージを配信 this.message = Message(text.toByteArray(), "", messageType) Nearby.getMessagesClient(this).publish(this.message!!) } /** * メッセージ配信停止 */ fun stopPublish() { if(this.message != null) { Nearby.getMessagesClient(this).unpublish(this.message!!) } } } |
※ここではプライバシーを意識した権限のチェックや確認等はすべて省略しています。
※このままコピペをしてもGoogleの審査によりリジェクトされる可能性があります。
通信してみる
ここまでできれば相互通信ができます。
両端末ともBluetoothをONにして、テキストを打ってみましょう。
両方のデバイスを持ってないという方のために、下記に相互通信したときの結果を貼り付けておきます。
ただ、実際には経路や権限などの細かい設定をする必要がありますが、これでiPhoneとAndroidでの通信が可能になったのは大きなことだと思います。
また EddyStone や iBeacon との連携も細かく設定する必要がありますが、通信が可能となります。
実際に近距離通信を使って検証してみた
弊社では実際に近距離通信を利用したサービスを実装し、現実世界での実地検証を行いました。
その結果、ビーコンの通信範囲を100mに設定しても、人物や建物などに反射して複数回取得したり、逆に遮断されて届かなかったりと、様々な記事に記載されている内容通りには到達しませんでした。
また、10mなどのかなり近距離に設定しても、周辺の電波と干渉してしまい、到達までの時間が長くなってしまうなどの問題もありました。
BluetoothとGPSの両立で不安定さを解消しようと試みましたが、今度はバッテリー消費がとてつもないことになってしまうというジレンマに・・・。
さらに、デバイスのBluetoothチップによって、送受信の感度が異なることから、届いたり届かなかったりと不安定な結果となってしまいました。
さいごに
今回は作成方法だけではなく、実際に検証した結果も記載してみました。
こういう生きた記事を書いていきたいものです。
- おすすめ記事
-
-
のえる2017.10.26
-
POPULAR
のえる
Full-stack Developer