/home/tnishinaga/TechMEMO

日々行ったこと、面白かったことを書き留めます。

Windows Azureの通知ハブ(notification hub)を使ってAndroid端末から通知を送る

あらすじ

先日、Sony Wearable Hackason 金沢というハッカソンに参加し、土日の2日間Smart WatchとSmartBand を用いたAndroidアプリを作っていました。

ハッカソン中、アプリに他の端末と通信する機能をつけるために何を使えばいいか悩んでいたところ、講師野方に「Windows Azureにメッセージ交換サービスがあるのでれそれを使えばいいのでは」とアドバイスをいただいたので、使うことにしました。

調べたところ、正確には通知ハブ(notifiation hub)という名前の機能で、Azureを通して携帯端末等に簡単にメッセージ通知を送ることのできるサービスのようです。

以下のチュートリアルを参考にサンプルを作ったところ(少々わかりづらい点があり苦戦しましたが)C#で作ったアプリから、Android端末に通知を送ることができました。

問題発生

f:id:tnishinaga:20140807172126p:plain

さて、サンプルを動かしたことで、C#……つまりパソコンやサーバー側からAndroidにメッセージを送る通信、つまりAndroidが受けとなる通信方法はわかりました。 しかし私がやりたいのはAndroid端末同士での通信です。 そこでそのためのサンプルを探したのですが……先ほどのページを含め、Androidから通知を送るサンプルを見つけることはできませんでした。

仕方がないのでC#のコードを参考にして、AndroidのAzureライブラリ(通知ハブ Android SDK)にも SendGcmNativeNotificationAsync メソッドのようなものがあるはずと思って探し始めたのですが……そもそもNotificationHubのJavaリファレンスが404でリンク切れだったり……javadocを以下のコマンドで開いて……

% jar xvf *-javadoc.jar

直接リファレンスを引っ張り出して読んで見たりしましたが……どうやら送信に使えるメソッドは無いみたいです。んなアホな……。

他の方法を考えてみる

ないものは仕方がないので、他の方法を探しました。

REST APIを使う

チュートリアルのページにちょこっと書かれているように、RESTを用いて通知ハブをいじることができるみたいです。 しかし私がRESTのことを全く知らないため、ハッカソンの時間的に実装が難しそうです。

GoogleCloudMessage(GCM)を直接叩く

うんうん悩んでいると講師の方に「GCMを直接叩けばいいのでは」とアドバイスをいただきました。 チュートリアルをやればわかるように、AzureのNotification Hubは結局裏でGCMを使ってAndroidに通知を送っているので、GCMを直接叩いても同じように通知が送れます。

f:id:tnishinaga:20140807172207p:plain

以下のサイトにサンプルもあるみたいなので、この方法でやってみることにします。

GCMを直接叩いてAndroidからAndroidに通知を送ってみる

先ほどのサイトを見ながらGCMを直接叩いて、通知を送ってみます。

とりあえずパソコンから送ってみる

このサイトで行われてるように、GCMはHTTPでjsonを投げることでも通知が送れます。

% curl --header "Authorization: key=<API KEY>” \
--header Content-Type:"application/json” \
https://android.googleapis.com/gcm/send \
-d "{\"registration_ids\":[\"<RegistrationID>\"],\"data\":{\"message\":\"Hello\"}}"

このコマンドは、GCMのURL(https://android.googleapis.com/gcm/send)に対して、以下のJSONデータを送りつけます。

Authorization: key=<API KEY>

Content-Type:"application/json”

{"registration_ids”:[\"<RegistrationID>\"],\"data\":{\”msg\":\"Hello\"}}"

API KEY>の部分には、チュートリアルでAzure notification hubに登録したGCMのAPIキーを入力します。

<RegistrationID>は、送信先の端末の認証IDを入力します。 この認証IDは端末でGCMに認証する際、チュートリアルのコードで言うと、以下の部分で取得されます。

String regid = gcm.register(SENDER_ID);

なのでこのregidを以下の様にログに出せば、取得することができます。

Log.d("regid", "regid: " + regid);

後の部分は、Helloという文字列を、msgとdataで包んでカプセル化しているだけです。

このコマンドを実行すれば、Android端末上に通知が送られるはずです。

Androidから送ってみる

実際に通知を送れることがわかったので、Androidからも送ってみましょう。

ここでは本来、GoogleCloudMessagingクラスのsendメソッドを使うのが正しい方法のはずですが、前述の通りそのときはハッカソン中で、しかも制限時間まで後2時間という状況だったので、既知の方法で早く実装するという手を取りました。 つまり、先ほどcurlを使ったHTTP通信でメッセージを送りつける手順を、そのままAndroidのプログラム上で行います。 そのコードを以下に示します。

// HTTP POSTを行う
    public void doPost()
    {
         AsyncTask<String, Void, String> task = new AsyncTask<String, Void, String>(){

               @Override
               protected String doInBackground(String... p) {

                    try
                  {
                      HttpPost method = new HttpPost("https://android.googleapis.com/gcm/send");
                      DefaultHttpClient client = new DefaultHttpClient();

                      // POST データの設定
                      // 以下の2つを書き換えてください
                      String Auth_Key = "key=<API KEY>";
                       String registration_ids = "<RegistrationID>";

                       String data_key = "msg";
                       String msg = "Hello";
                       String strJson = "{\"registration_ids\":[\""+ registration_ids + "\"],\"data\":{\"" + data_key + "\":\"" + msg + "\"}}";

                      StringEntity paramEntity = new StringEntity( strJson );
                      method.setHeader("Authorization", Auth_Key);
                      paramEntity.setContentType( "application/json" );
                      method.setEntity( paramEntity );

                      HttpResponse response = client.execute( method );
                      int status = response.getStatusLine().getStatusCode();
                      if ( status != HttpStatus.SC_OK )
                          throw new Exception( "" );

                      return EntityUtils.toString( response.getEntity(), "UTF-8" );
                  }
                  catch ( Exception e )
                  {
                      return null;
                  }
               }

         };
         task.execute();
    }

これを送信側のコードに埋め込んで、doPostを呼べば通知が送られます。 なお、このときAndroidMaifest.xml

<uses-permission android:name="android.permission.INTERNET" />

を書き忘れると動かないので注意。

コードの公開場所

今回作ったコードはすべてgithubで公開しておきます。

Azureのnotificationライブラリは入ってないので、チュートリアルを参考にインストールしてください。

どはまりポイント

今回のどはまりポイントだったのですが、ひさしぶり(Android 2.3の頃以来)にAndroidのコードを作ったら、HTTPでPOSTするときはAsyncTaskを使って非同期処理にしないと動かなくなってたんですね。 しかもコンパイルエラーは一切出ず、ただ沈黙するだけ……なかなかのドS。

一緒に作業していた高専生の男の子がいなかったらわからなかったです。ありがとう!

おわりに

泥臭いですが、とりあえずこれでプログラムは完成し、ハッカソンの成果発表をすることはできました。

今回作ったアプリはいろいろ改善したいところが沢山あるのですが、SmartWatchと対応スマートフォンを持っていないのでひとまずこれにて終了です。

2日間寝る間を惜しんでコードを書くのは大変でしたが、とても楽しかったです。 イベント実行委員の方々、本当にありがとうございました。

参考文献orサイト