連載
» 2013年02月12日 18時00分 公開

AndroidビームとPush通知で最強のO2Oアプリを作るAndroidで使えるO2O技術まとめ解説(終)(2/3 ページ)

[金岡徹,TIS コーポレート本部 戦略技術センター]

Push型通知の機能をアプリに組み込む

 実装に入る前にシナリオとGCMを利用してアプリを作成する手順を確認しておきます。シナリオは以下のようなものを想定します。

  1. Android端末Xを持っているユーザーAとAndroid端末Yを持っているユーザーBが同じ部屋に入る
  2. Android端末X、Yにあいさつ可能な(同じ部屋にいる)友達リストが表示される
  3. ユーザーAがあいさつを送りたい友達(ユーザーB)を選択し、あいさつを送る
  4. ユーザーBに通知される
あいさつ(GCM)

 1および2は前回の記事で説明済みなので割愛して、あいさつ可能なユーザーのリストが表示された状態から説明を始めます。3、4の部分をGCMを利用して実現します。

 GCMを利用してアプリを作成するには、以下の3つのステップを実行すればOKです。

  1. API Key、Sender IDの取得
  2. Androidアプリの作成
  3. サードパーティアプリケーションサーバの構築

 では、ここからアプリを実装していきます。

【1】API Key、Sender IDの取得

 GCMを利用するためには「API Key」「Sender ID」を事前に取得しておく必要があります。これらの取得方法は「ソフトウェア技術ドキュメントを勝手に翻訳」の記事が詳しいので参考にしてみてください。本稿ではAPI KeyおよびSenderIDを取得しているものとして話を進めます。

【2】Androidアプリの作成

 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>
AndroidManifest.xml

 最初に、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();
                }
            }
        });
    }
}
DeviceRegistrationActivity.java

 登録が完了すると、GCMサーバから端末を一意に特定できるIDを含んだIntentが発行されます。このIDをサードパーティアプリケーションサーバに保存するように依頼します。

 GCMサーバから送信されるインテントをハンドリングするためにGCMBaseIntentServiceを継承したクラス(GCMIntentService)を新規に作成します。

 GCMサーバから送信されたインテントを端末側で受け取るタイミングとしては、以下の2つがあります。

  • GCMサーバからメッセージが送信されたとき
  • GCMサーバへの端末登録が完了したとき

 「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) {
    〜(省略)〜
    }
}
GCMIntentService.java

 友達リストを表示する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();
                }
            }
        }
FriendListActivity.java
public class User {
    public String greet(String recieverFacebookId, String type, String ServerUrl) {
〜(省略)〜
    }
}
User.java

 これでアプリ側の実装は完成です。

【3】サードパーティアプリケーションサーバの構築

 ここからはサードパーティアプリケーションサーバについて説明します。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
device_registrations_controller.rb

 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
pecori_controller.rb

 実際には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_access.rb

 実際に端末にメッセージを送る部分に関しては、GCMサーバがやってくれます。Androidなどのモバイル端末では、モバイル独自の状態によるメッセージ不通(電源が入っていない、ネットワーク途絶など)を考慮する必要がありますが、その辺りはGCMサーバがうまくハンドリングしてくれるので、キューイングや再送処理といった点は特に気にする必要はありません。

 高度なオプションに関しては、「ソフトウェア技術ドキュメントを勝手に翻訳」の記事を参照してください。

アプリを動かすと……

 このようにGCMを利用すれば、任意のタイミングで任意の端末に対してメッセージを送信できます。実際のアプリの挙動は以下の図のようになります。

Android端末XおよびYでデバイスの登録を行う
Android端末Xで「ぺこりする」ボタンを押してあいさつを送信
Android端末Yに通知される

 今回は「あいさつ」をメッセージとして送信しましたが、あいさつ以外でも送信するメッセージによって、オフラインの行動につながるきっかけを与えることができると思います。

 また、本稿では特定のアプリのソースコードを抜粋して紹介していますので、全体の流れがつかみづらいところがあるかもしれません。Android側とサードパーティアプリケーションサーバ側のサンプル実装をしている例を、ブログでも紹介しているので、参考にしてみてください。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。