実装に入る前にシナリオとGCMを利用してアプリを作成する手順を確認しておきます。シナリオは以下のようなものを想定します。
1および2は前回の記事で説明済みなので割愛して、あいさつ可能なユーザーのリストが表示された状態から説明を始めます。3、4の部分をGCMを利用して実現します。
GCMを利用してアプリを作成するには、以下の3つのステップを実行すればOKです。
では、ここからアプリを実装していきます。
GCMを利用するためには「API Key」「Sender ID」を事前に取得しておく必要があります。これらの取得方法は「ソフトウェア技術ドキュメントを勝手に翻訳」の記事が詳しいので参考にしてみてください。本稿ではAPI KeyおよびSenderIDを取得しているものとして話を進めます。
Androidアプリのひな型は作成済みであるという前提で説明を進めます。Eclipseから新規プロジェクトを作成しておいてください。
GCMを利用する際には、Androidアプリ用のライブラリ「gcm.jar」を利用すると便利です。今回はライブラリを利用して実装していきます。ライブラリはAndroid SDK Managerを利用してダウンロードできます。
ダウンロードしたライブラリは、「YOUR_SDK_ROOT/extras/google/gcm/gcm-client/dist」に格納されていると思いますので、Androidアプリ内の「libs」フォルダ(なければフォルダを作成)以下に配置します。配置しただけでは読み込まれないので、jarファイルを読み込むようにBuild Pathは通しておいてください。
GCMを利用するには、Permissionの設定を行う必要があります。必要な部分のみを抜粋したので以下を参考に「AndroidManifest.xml」に追記してください。
- <uses-sdk android:minSdkVersion="14" />
- <uses-permission android:name="android.permission.INTERNET"/>
- <permission android:name="com.example.pecorin.permission.C2D_MESSAGE" android:protectionLevel="signature" />
- <uses-permission android:name="com.example.pecorin.permission.C2D_MESSAGE" />
- <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
- <uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
- <application
- <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.example.pecorin" />
- </intent-filter>
- </receiver>
- <service android:name=".GCMIntentService"></service>
- </application>
最初に、Activity(本稿では「DeviceRegistrationActivity」)に「Push型通知を送りたい端末からGCMサーバに対して端末の登録依頼を行う」処理を記述します。ライブラリが読み込まれていれば、GCMサーバに端末を登録するメソッド「GCMRegistrar.register(Context context, String... senderIds)」が利用可能です。基本的にこのメソッドを実行すればOKです。
DeviceRegistrationActivityでは、ボタンをタップするとメソッドを実行するように実装しています。
登録が完了するとGCMからインテントが送信されるので、IntentをハンドリングするServiceを作成し、適切にハンドリングします(ハンドリングはGCMBaseIntentServiceを継承したクラスで行います。この後、説明します)。
一度登録が成功すると、「GCMRegistrar.getRegistrationId(Context context)」メソッドで端末を一意に特定できるIDを取得できるので、IDを再取得したい場合は、このメソッドを実行します(端末の登録が完了していない場合は空文字を返します)。
- public class DeviceRegistrationActivity extends Activity {
- private static final String SENDER_ID = "**********";
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.device_registration);
- Button button = (Button) findViewById(R.id.registration_button);
- button.setText(getString(R.string.registration_button));
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Context context = getApplicationContext();
- try {
- // デバイスがGCMに対応しているかどうかをチェック
- GCMRegistrar.checkDevice(context);
- // GCMを利用するうえでマニフェストが適切に書かれているかどうかをチェック
- GCMRegistrar.checkManifest(context);
- } catch (UnsupportedOperationException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- // RegistrationIDを取得
- final String regId = GCMRegistrar.getRegistrationId(context);
- // GCMサーバ(Googleが管理するサーバ)にサインアップしていない場合
- if (regId.equals("")) {
- // 取得したSender IDを使ってGCMサーバにサインアップする
- GCMRegistrar.register(context, SENDER_ID);
- } else {
- // GCMサーバ(Googleが管理するサーバ)にサインアップしている場合、メッセージを出力する
- Toast.makeText(context, "Already registered", Toast.LENGTH_SHORT).show();
- }
- }
- });
- }
- }
登録が完了すると、GCMサーバから端末を一意に特定できるIDを含んだIntentが発行されます。このIDをサードパーティアプリケーションサーバに保存するように依頼します。
GCMサーバから送信されるインテントをハンドリングするためにGCMBaseIntentServiceを継承したクラス(GCMIntentService)を新規に作成します。
GCMサーバから送信されたインテントを端末側で受け取るタイミングとしては、以下の2つがあります。
「GCMサーバに端末登録が完了したとき」にはUpLoadRegistrationIdTaskを実行して、サードパーティアプリケーションサーバにIDをアップロードします。UpLoadRegistrationIdTaskでは、uploadRegistrationId(String facebook_id, String regId)というメソッドを実行し、「http://SERVER_URL/device_registrations」にHTTP POSTで非同期にアクセスしています。
サーバ側の実装については後述しますが、「http://SERVER_URL/device_registrations」にPOSTでアクセスするとfacebook_idとregIdを紐付けてサーバ側のDBに保存させています(regIdだけを保存してもいいのですが、事前に登録してあるユーザー情報と紐づけると管理がしやすくなるので、このような実装にしています)。
「GCMからメッセージ送信されたとき」にはメッセージを受信したことをユーザーに知らせるためにステータスバーに通知を表示させます。ステータスバーに通知を表示させるにはNotificationManagerを利用します。ここでは、通知をタップしたときにFriendListActivity(アプリを最初に起動したときに表示されるActivity)に遷移するように実装しています。
- public class GCMIntentService extends GCMBaseIntentService {
- private static final String SENDER_ID = "**********";
- private static final String PREFERENCE_KEY = "preference";
- private static final String SERVER_URL = "http://";
- public GCMIntentService() {
- super(SENDER_ID);
- }
- // GCMからメッセージが送信された場合、このメソッドが呼ばれる
- @Override
- protected void onMessage(Context context, Intent intent) {
- String message = intent.getStringExtra("message");
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
- Notification notification = new Notification(R.drawable.ic_launcher, message, System.currentTimeMillis());
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- Intent remoteIntent = new Intent(context.getApplicationContext(), FriendList.class);
- PendingIntent contentIntent = PendingIntent.getActivity(context.getApplicationContext(), 0, remoteIntent, 0);
- notification.setLatestEventInfo(context.getApplicationContext(), context.getString(R.string.app_name), message, contentIntent);
- notificationManager.notify(R.string.app_name, notification);
- }
- // GCMサーバに端末登録が完了した場合、このメソッドが呼ばれる
- @Override
- protected void onRegistered(Context context, String regId) {
- SharedPreferences sharedpref = context.getSharedPreferences(PREFERENCE_KEY, MODE_PRIVATE);
- String facebook_id = sharedpref.getString("facebook_id", null);
- UpLoadRegistrationIdTask task = new UpLoadRegistrationIdTask(facebook_id, regId, context);
- task.execute();
- }
- // 非同期で行うためにAsyncTaskを利用して、RegistrationIDをサードパーティアプリサーバにアップロード
- public class UpLoadRegistrationIdTask extends AsyncTask<Void, Void, Integer> {
- private Context context;
- private String facebook_id;
- private String regId;
- public UpLoadRegistrationIdTask(String facebookId, String regId, Context context) {
- this.facebook_id = facebookId;
- this.regId = regId;
- this.context = context;
- }
- @Override
- protected Integer doInBackground(Void... arg0) {
- int statusCode = 0;
- HttpResponse objResponse = uploadRegistrationId(facebook_id, regId);
- statusCode = objResponse.getStatusLine().getStatusCode();
- return statusCode;
- }
- @Override
- protected void onPostExecute(Integer statusCode) {
- if (statusCode == 201) {
- Intent newIntent = new Intent(context, FriendList.class);
- newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- context.startActivity(newIntent);
- }
- }
- }
- private HttpResponse uploadRegistrationId(String facebook_id, String regId) {
- ~(省略)~
- }
- }
友達リストを表示するActivity(FriendListActivity)に選択した友達に対してあいさつを送信できる機能を実装します。リストから友達を選択すると、ダイアログを開き、ダイアログ内に表示されているボタンをタップします(下記の実装ではボタンが1つですが、NFCを活用した機能を実装する際にボタンが増えるため、このような実装にしています)。
ボタンをタップすると、greetTaskの中で「sender.greet(String recieverFacebookId, String type, String ServerURL)」メソッドを実行し、「http://SERVER_URL/pecori」にHTTP POSTで非同期にアクセスしています。
サーバ側の実装については後述しますが、「http://SERVER_URL/pecori」にPOSTでアクセスすると、リストから選択した友達の端末のIDとメッセージを併せてGCMサーバに送信します。
- public class FriendListActivity extends Activity {
- private User mSender;
- private ListView mListView;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ~(省略)~
- LayoutInflater factory = LayoutInflater.from(this);
- final View inputView = factory.inflate(R.layout.custom_dialog, null);
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setIcon(R.drawable.ic_launcher).setTitle(R.string.greet_message).setCancelable(true).setView(inputView);
- final AlertDialog dialog = builder.create();
- mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- ListView listView = (ListView) parent;
- User item = (User) listView.getItemAtPosition(position);
- final String recieverFacebookId = item.getFacebookId();
- final String recieverName = item.getName();
- greetButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.dismiss();
- String type = "gcm";
- greetTask task = new greetTask(mSender, type);
- task.execute(recieverFacebookId);
- }
- });
- ~(省略)~
- }
- public class greetTask extends AsyncTask<String, Void, String> {
- private User sender;
- private String type;
- private String ServerURL = "http://";
- public greetTask(User sender, String type) {
- this.sender = sender;
- }
- @Override
- protected String doInBackground(String... ids) {
- String recieverFacebookId = ids[0];
- String message = sender.greet(recieverFacebookId, type, ServerURL);
- return message;
- }
- @Override
- protected void onPostExecute(String message) {
- Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
- }
- }
- }
- public class User {
- public String greet(String recieverFacebookId, String type, String ServerUrl) {
- ~(省略)~
- }
- }
これでアプリ側の実装は完成です。
ここからはサードパーティアプリケーションサーバについて説明します。GCMを使ってPush型通知を行うには、「サードパーティアプリケーションサーバ」と呼ばれるサーバ側のアプリケーションが必要です。サードパーティアプリケーションサーバはプラットフォームや言語による縛りはないので、今回はAmazon Web Services上にRuby on Railsを利用してアプリケーションサーバを構築します。
まず、Android端末から送信された端末のIDをDBに格納します。先の説明でもあったようにユーザー情報と紐づけて管理するため、Userモデルにregistartion_idというカラムを持たせて保存しています。
- class DeviceRegistrationsController < ApplicationController
- protect_from_forgery :except => :create
- def create
- facebook_id = params["facebook_id"]
- registration_id = params["registration_id"]
- if user = User.find_by_facebook_id(facebook_id)
- user.registration_id = registration_id
- if user.save
- render :text => "Completion of registration!", :status => 201
- else
- render :text => "Failure to register", :status => 500
- end
- else
- render :text => "Failure to register", :status => 500
- end
- end
- end
Andorid端末からあいさつを送った場合、あいさつを送りたい友達の端末IDが送られてくるので「誰からのあいさつ」かというメッセージを作成して、GCMサーバに指定した端末ID宛てにメッセージを送信するように依頼します。
- class PecoriController < ApplicationController
- protect_from_forgery :except => :create
- def create
- sender_facebook_id = params["sender_facebook_id"]
- receiver_facebook_id = params["receiver_facebook_id"]
- type = params["type"]
- receiver_user = User.find_by_facebook_id(receiver_facebook_id)
- sender_user = User.find_by_facebook_id(sender_facebook_id)
- if receiver_user and sender_user
- receiver_registration_id = receiver_user.registration_id
- sender_name = sender_user.name
- pecori_message = "Pecori from " + sender_name
- message = {:message => pecori_message }
- registration_ids = []
- registration_ids << receiver_registration_id
- gcm_result, gcm_result_message = GcmAccess.gcm_post_message(registration_ids, message)
- if gcm_result
- render :text => gcm_result_message, :status => 200
- else
- render :text => gcm_result_message, :status => 500
- end
- end
- end
- end
実際にはGCMサーバにメッセージを送信するように依頼する部分は、「GcmAccess.gcm_post_message(registration_ids, message)」メソッドで行っています。このメソッドでは、引数として渡ってきた端末のID(registration_ids)とメッセージをJSON形式で組み立て直し、指定されたURLに対してHTTPでPOSTしています。
- module GcmAccess
- require 'net/http'
- require 'uri'
- require 'json'
- API_KEY = "**********"
- def self.gcm_post_message(registration_ids, data={ })
- url = URI.parse("https://android.googleapis.com/gcm/send")
- message_data = {}
- message_data["registration_ids"] = registration_ids
- message_data["data"] = data
- headers = { "Content-Type" => "application/json", "Authorization" => "key=" + API_KEY }
- http = Net::HTTP.new(url.host, url.port)
- http.use_ssl = true
- # 動作確認のみ行いたいので、一時的にOpenSSL::SSL::VERIFY_NONE
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
- res = http.post(url.path, message_data.to_json, headers)
- if res.code == "200"
- res_message = "GCM Post Message Success."
- return true, res_message
- else
- res_message = "GCM Post Message Failed."
- end
- return false, res_message
- end
- end
実際に端末にメッセージを送る部分に関しては、GCMサーバがやってくれます。Androidなどのモバイル端末では、モバイル独自の状態によるメッセージ不通(電源が入っていない、ネットワーク途絶など)を考慮する必要がありますが、その辺りはGCMサーバがうまくハンドリングしてくれるので、キューイングや再送処理といった点は特に気にする必要はありません。
高度なオプションに関しては、「ソフトウェア技術ドキュメントを勝手に翻訳」の記事を参照してください。
このようにGCMを利用すれば、任意のタイミングで任意の端末に対してメッセージを送信できます。実際のアプリの挙動は以下の図のようになります。
今回は「あいさつ」をメッセージとして送信しましたが、あいさつ以外でも送信するメッセージによって、オフラインの行動につながるきっかけを与えることができると思います。
また、本稿では特定のアプリのソースコードを抜粋して紹介していますので、全体の流れがつかみづらいところがあるかもしれません。Android側とサードパーティアプリケーションサーバ側のサンプル実装をしている例を、ブログでも紹介しているので、参考にしてみてください。
Copyright © ITmedia, Inc. All Rights Reserved.
Smart & Social 險倅コ九Λ繝ウ繧ュ繝ウ繧ー