2012年10月23日火曜日

Google Cloud Messaging for Android(GCM)を試してみる

モバイルアプリも流行ってきたので、さらにサーバプッシュの必要性が高くなってくると思い、メンバーと一緒にいろいろ試していってみようと思います。(Google Cloud Messaging、Apple Push Notification Service、WebSocketなど)

まずは、Google Cloud Messaging for Android(以下GCM)について試してみました。(ちょっと前までは、C2DMと呼ばれていたものです。)

動作は以下の図のように、Androidデバイスからregister idというものを生成及び登録し、その後サーバなどから登録されたデバイスに対してプッシュすることで、メッセージをリアルタイムに通知するという単純なものです。



実装については、基本的にはこちらのページ通りにやっていけばできると思いますが、ポイントとハマったところをまとめていければと思っています。また、サーバサイドのプッシュをPHPでやっている記事は多いですが、チュートリアルにあるサーブレットでやっている人が少なそうなので、今回はJavaサーブレットを使ってみたいと思います。

環境
Apache Tomcat 7
Android 4.1

API KeyとSENDER ID取得
https://code.google.com/apis/consoleからAPI KeySENDER IDを取得します。簡単なので詳しくは、http://developer.android.com/guide/google/gcm/gs.htmlを参照ください。

サーブレット作成
まずは、サーバサイドの仕組みを作るため、プロジェクト(Eclipse for JavaEEを使った場合は、Dynamic Web Project)を作成します。

AndroidをEclipseで開発するために、Android Development Tools(ADT)pluginをインストールして、Android SDK Managerで「Extras」⇒「Google Cloud Messaging for Android」を選択しGCMをダウンロードします。そのダウンロードしたディレクトリの中のgcm/gcm-server/dist/gcm-server.jarをプロジェクトにコピーします。(WEB-INF/libなどへ)
ただ、このjarファイルだけですと、java.lang.ClassNotFoundExceptionが発生します。
java.lang.ClassNotFoundException: org.json.simple.parser.ParseException
これは、gcm/gcm-server/lib/json_simple-1.1.jarがないため発生してるようなので、同様にコピーします。


では、早速サーバサイドのコードですが、3つのサーブレットを作ることとします。1つはregister IDの登録、2つ目はregister IDの登録解除、最後はメッセージ送信。

①. register ID登録サーブレット
Androidから受け取ったregister IDを適当なデータベースなどに登録しておきます。ここはデータベースではなくてもファイルでもキャッシュでも何でもいいです。

②. register ID登録解除サーブレット
単純に①で登録したものを削除します。

③. メッセージ送信サーブレット(サーバからクライアントへプッシュ)
Message.Builder builder = new Message.Builder().delayWhileIdle(true);
builder.addData("message", "Hello"); // 送信するデータ
Message message = builder.build();

String myAppKey = <API Key>
List<String> devices =<①で登録されたregister IDのList<String>>
Sender sender = new Sender(myAppKey);
MulticastResult result = sender.sendNoRetry(message, devices);


これでdevicesに対して、メッセージを送信できます。しかし、メッセージ送信時に以下のエラーが発生しました・・・。

javax.net.ssl.SSLHandshakeException:
   sun.security.validator.ValidatorException: PKIX path building failed:
   sun.security.provider.certpath.SunCertPathBuilderException:
   unable to find valid certification path to requested target

Caused by: sun.security.validator.ValidatorException:
   PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
   unable to find valid certification path to requested target

Caused by: sun.security.provider.certpath.SunCertPathBuilderException:
   unable to find valid certification path to requested target

これはSSL通信を行うために証明書が必要みたいなので、jssecacertsファイルを作成し、$JAVA_HOME/lib/securityにコピーするで解決できました。(keytoolを使うのもいいですが、私は簡単そうだったのでInstallCert.javaを使いました。)

ちなみに以下のコマンドでjssecacertsファイルを作成。
> java InstallCert android.googleapis.com

Androidプロジェクト作成
Androidプロジェクトを作成し、上記でダウンロードしてきたgcm/gcm-client/dist/gcm.jarをAndroidプロジェクトにコピーします。

次にActivityとServiceを作成します。

AndroidManifest.xml

    <permission android:name="com.scratch.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
    <uses-permission android:name="com.scratch.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        <activity
            android:name=".GCMActivity"
            android:configChanges="orientation|keyboardHidden"
            android:windowSoftInputMode="adjustPan" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver
                android:name="com.google.android.gcm.GCMBroadcastReceiver"
                android:permission="com.google.android.c2dm.permission.SEND" >
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                    <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                    <category android:name="com.scratch" />
                </intent-filter>
        </receiver>
        <service android:name=".GCMIntentService" />
    </application>

Serviceクラス(GCMIntentService)
package com.scratch;

import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.google.android.gcm.GCMBaseIntentService;

public class GCMIntentService extends GCMBaseIntentService {

        static String SENDER_ID = <SENDER ID>;
        final private String TAG = getClass().getName();

        @Override
        protected void onError(Context context, String regId) {
                Log.v(TAG, "error " + regId);
        }

        @Override
        protected void onMessage(Context context, Intent intent) {
                Log.v(TAG, "Received message");
                Log.v(TAG, intent.getStringExtra("message")); //サーバからプッシュされたデータの受け取り
        }

        @Override
        protected void onRegistered(Context context, String regId) {
                Log.v(TAG, "registered regId=" + regId);
                // ここでサーブレット①にリクエストしてregister idを登録。
        }

        @Override
        protected void onUnregistered(Context context, String regId) {
                Log.v(TAG, "unregistered " + regId);
                // ここでサーブレット②にリクエストしてregister idを削除。
        }

        public GCMIntentService() {
                super(SENDER_ID); // superでSENDER IDを指定するようです。
                Log.v(TAG, "create GCMIntentService()");
        }
}

Activityクラス(GCMActivity)
package com.scratch;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

import com.google.android.gcm.GCMRegistrar;

public class GCMActivity extends Activity {

        final private String TAG = getClass().getName();

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);

                // check if this device supports GCM.
                GCMRegistrar.checkDevice(this);

                // check if it's right manifest.
                GCMRegistrar.checkManifest(this);
                final String regId = GCMRegistrar.getRegistrationId(this);
                Log.v(TAG, "regId=" + regId);
                if (regId.equals("")) {
                       // GCMIntentService.onRegisteredがコールされる。
                       GCMRegistrar.register(this, GCMIntentService.SENDER_ID); 
                } else {
                        Log.v(TAG, "Already exists " + regId);
                }
        }
}

ちなみに、unregisterはをするにはGCMRegister.unregisterメソッドを使い、これが実行されるとGCMIntentService.onUnregisteredが呼ばれます。

これでAndroidでサーバプッシュができるはずです。一度お試しを!(詰まったらコメントください。)

次回は、AppleのApple Push Notification Service(APNS)についても試していこうと思います。

2012年10月19日金曜日

koalaを使ってRuby on RailsでFacebookアプリ開発 ②

koalaを使ってRuby on RailsでFacebookアプリ開発 ①で紹介したものにいくつか補足をしていきたいと思います。(特につまずいたところを中心に!)

モバイル版Facebook
モバイル版のFacebookサイトを作る場合は、ログインや承認画面もモバイル用にするべきだと思いますが、その場合は、パート①で紹介したURL「https://graph.facebook.com/oauth/authorize」にdisplay=touchというパラメータを追加することで可能です。

koalaでウォールに書き込む
ウィールに書き込むには、put_wall_postメソッドを使います。これを使えば簡単に投稿出来てしまいます。以下はサンプルです。

graph = Koala::Facebook::API.new(<アクセストークン>)
graph.put_wall_post(
  "本文(HTMLで。)",
  {
    :name => "リンクのタイトル",
    :link => "リンクのURL",
    :picture => "写真のパス"
  }
)

ちなみに、本当に投稿されてしまうので、実施するときはFacebookのテストユーザでやりましょう!テストユーザは、developers.facebook.comから"Apps"を選択し、"Edit Rolls"の中で作れます。

アクセストークンを取得するときに使ったHTTP API
rubyのいろいろなAPIを試してみたのですが、いろいろトラブって、最終的にはhttpclientを使いました。

Gemfileに以下の行を足し、
gem 'httpclient'

bundle installを実行!その後以下のコードで取得dけいました。
access_token = client.get(
    "https://graph.facebook.com/oauth/access_token", 
    :query => {
      :client_id => <App ID>, 
      :redirect_uri => <最終的なリダイレクト先>,
      :client_secret => <アプリのシークレットキー>, 
      :code => <バリデーションコード>
    }).body

さらに問題になったのは戻ってきたアクセストークンですが、いつの日から&expires=XXX(XXXは秒数)というようなものもくっついてきたので、これを切り取る必要があると思います。(これをもとにアクセストークンをキャッシュしておけば、毎回Facebookからアクセストークンを取得する処理を実行する必要はなくなります。特にこの処理はあまり速くないと思いますので、キャッシュしておいた方がいいと思います。)

InvalidAuthenticityTokenエラーが発生する
Web API風にJSONでJavaScriptとデータをやり取りする場合にInvalidAuthenticityTokenエラーが発生しまいました。これはCSRF対策としてRails2.0から導入された機能で、アプリが返した画面からしかリクエストを受けないようにする機能みたいです。これを回避するために以下のようにしてverify_authenticity_tokenをスキップしました。

skip_before_filter :verify_authenticity_token

AndroidやiOSなどでインストールするアプリ(.apkや.ipa)の場合には、Viewを利用せず、Web API風にする必要があるため、この対応が必要になってくる


とりあえず、結構つまずいたところはこんな感じですね。また、つまずいたら追記します!


2012年10月16日火曜日

koalaを使ってRuby on RailsでFacebookアプリ開発 ①

Ruby on RailsでFacebookアプリを開発しようといろいろを情報を探していて、最終的にはKoalaというライブラリがFacebook Graph APIを扱うのに一番使いやすそうなので、試してみました。

まずは、インストールですが、Gemfileに以下の行を追加。

 gem 'koala'

そして、bundle installを実行して完了。

次に利用方法です。以下のようにKoala:Facebook::API.newにアクセストークンを渡すことでOKです。戻り値のgraphに対してget_objectなどのメソッドをコールして、Facebookのデータにアクセスします。以下の例は自分の情報を取得しています。

 # koalaでFacebook Graph APIにアクセス
 graph = Koala::Facebook::API.new(アクセストークン)
 me = graph.get_object("me")

こんな簡単に利用可能です。ただし、一番ひっかかったのはこのアクセストークンをどうやって取得するかに悩まされましたので、メモっておきます。(結構面倒くさい・・・)

まずは、developers.facebook.comで新規アプリを作成して、アクセストークンを取得するためのURLを登録します。(テスト時はlocalhostでもOKです。)

次に以下のクライアントから以下のようにブラウザでアクセスしますと、Facebookのログインやアプリ承認画面が表示され、承諾すると登録したURLにリダイレクトされます。
https://graph.facebook.com/oauth/authorize?client_id=<ここにFacebookのapp ID>&redirect_uri=<上記登録したURL>&scope=<アプリがアクセスできる範囲>
このとき、リダイレクトされたサイトに"code=XXXX"というパラメータ("XXXX"はバリデーションコード)が渡ってきますので、このコントローラ内でバリデーションコードを取得し、さらに以下のURLにコントローラ内からアクセスするとアクセストークンがテキストで取得できます。(access_token=ZZZZという形式。)
https://graph.facebook.com/oauth/access_token?client_id=<Facebookのapp ID>&redirect_uri=<自分のFacebookアプリのトップヘージ>&client_secret=<アプリのシークレットキー>&code=<バリデーションコード>
これでやっとアクセストークンが取得できますので、一番最初のkoalaのAPIに渡すことでFacebook Graph APIにアクセスできるようになります。

なんとか、私はこれで成功することができました。あとは、友達情報取るのも、ウォールに書き込むのも簡単です。

# 友達の情報を取得
friends = graph.get_connections("me", "friends")

ふぅ〜。。

2012年10月15日月曜日

HTML5をサポートする「Project Avatar」

JavaOneで発表されているProject Avatarというものに興味があり、いろいろ調べてみました。

来年にリリース予定だそうですが、次のOracleのクラウド・デバイスに対してのクロスプラットフォームアプリケーションを開発するための解決策であるフレームワークだと思います。

Avatarは、HTML5をベースとしたクライアント・フレームワークです。これまでのWebシステムではサーバサイドでMVCを持ち、Viewを生成してクライアントに返していましたが、モバイルアプリケーションはオフラインなどでも実行できるようになる必要があるため、クライアント側にMVCを持ち、これまでとはアーキテクチャが異なってきます。

クライアントでMVCを持つことに加え、サーバサイドのModel(データベース)とクライアントのModelをバインドできるように、REST、JSON、WebSocketsなどがサポートされるようです。これを「Thin-Server Architecture (TSA)」と呼んでいるようです。

(以下はJavaOne 2012のプレゼン資料から抜粋)



今後のアーキテクチャは、マルチプラットフォームに対応できるアーキテクチャが求められてくるため、こういったものが流行っていくんではないかと思います。でも、ちょっと前にAdobe FlexやMicrosoft Silverlightでもこういったフレームワークが提供されていた気がするので、大きな目新しさは少ないかもしれないですね。


また、JavaOne 2012では開発ツールであるProject Easelというものが発表されました。これは、NetBeansのpluginとChromeのExtensionから構成されており、HTML5ベースのプロジェクトを作成してコーディングし、デバッグ時にブレークポイントやステップ実行などを行えるというものです。また、コーディングはクライアントサイド・サーバサイド共にロジックはJavaScriptで書くことができるようです。そのため、クライアントからサーバまでトータルでNetBeansで開発できるようです。

期待度が高いですが、CoffeeScriptDartJSXなどといろんなJavaScriptの代替の言語が出てきている今、JavaScriptに対する不足感があるのでいろんなものが出てきているんではないかと考えています。そのため、Project Easelも開発言語はエンジニアが多いJava(GWT(Google Web Toolkit)のようにJava→JavaScriptのようなものを提供。)でよかったんではないかと個人的には思います。

2012年10月11日木曜日

JSON Query Language 「JSONiq」①

XQueryの拡張であるJSONiqに少し興味があり、とりあえず家のmacにインストールしてみました。インストール自体は簡単で、zorbaのダウンロードサイトからmac用インストーラをダウンロードし実行するだけです。私の場合はインストール後の実行時に以下のようなエラーが出ました。

  > zorba
  dyld: Library not loaded: /opt/local/lib/libxerces-c-3.1.dylib
    Referenced from: /opt/local/bin/zorba
    Reason: image not found
  Trace/BPT trap: 5

xercesが無いようなので、xercesからソースを落として解凍し、①.configure ②make ③sudo make installの流れで終了します。簡単なテストをすると、

  > zorba -t --print-query -q "let \$person := { 'name':'K.A.T', 'gender':'male' } return \$person('name')"

 Query number 1 :
 let $person := { 'name':'K.A.T', 'gender':'male' } return $person('name')
 
 K.A.T
 Number of executions = 1

この例はpersonにjsonを入れてnameを表示するだけのQueryですが、更新などいろいろとできそうなのでちょっと遊んでみようと思います。

2012年10月9日火曜日

新オープンWeb技術サイト「WebPlatform.org」

早速興味のあるネタで、アルファリリースですが、WebPlatform.orgというものが開設されたようです。このサイトはHTML5などのオープンなWeb技術のための開発者サイトみたいです。スチュワートとして名を上げているのがW3Cを中心として、Adobe、Apple、Facebook、Google、HP、Microsoft、Mozilla、Nokia、Operaとすごい。



早速、ユーザアカウントを作成してみました。このサイトには、ドキュメントとしてHTML5、SVG、CSSのリファレンスやチュートリアル、有名なStack Overflow風のフォーラムに加え、チャットルームなどもあるみたいです。

今後、ここで標準的なHTML5を定義し、各ブラウザ間での互換性を保ってほしいですし、ザッカーバーグもこの記事のように再度HTML5にチャレンジするようなときが早く来てくれるのを待っています。


2012年10月8日月曜日

技術ブログ始めました。

K.A.Tです。メンバーと暇を見つけて自由にアプリケーションを作っています。
その中で割りと新しい技術を使ったりしているのですが、どうせなら僕らが使っている環境やハマった内容、Tipsなどを共有していこうと思ってブログを始めました。

新しいことだけじゃなく、開発に必要な普通のトピックも書いていくのでチェックしてみて下さい!