Implementing GCM Client の日本語訳
https://developer.android.com/google/gcm/client.html
GCMクライアントとは、GCMが内蔵されたアプリのことです。クライアントコードでは、GoogleCloudMessaging APIを使うことを推奨します。このヘルパライブラリは、古いバージョンのGCMが動くことを保証していますが、その代替かつ、より有益なGoogleCloudMessaging APIを提供しています。
完全なGCMの実装には、クライアントとサーバーサイド両方の実装が必要です。サーバーサイドの実装に関してはImplementing GCM Serverを参照して下さい。
本節では、GCMのクライアントサイドアプリケーションを書くために必要な手順を紹介します。作成するクライアントアプリケーションは、登録IDを記述したコードと、GCMからのメッセージを受け取る、ブロードキャストレシーバーが必要になります。
このクライアントアプリケーションを書くためには、Implementing GCM Server APIを利用して下さい。このAPIを使う時は、Google Play services SDKが必要です。詳細はSetup Google Play Services SDKを参照して下さい。
Play Servicesライブラリをプロジェクトに追加する時は、リソースの追加も必要になります(Setup Google Play Services SDKに説明があります)。ここで重要なポイントは、Eclipseのプロジェクトに.jarを単に追加するだけではなく、ライブラリへのリファレンスが必要であるということです。ライブラリへのリファレンスがなければ、アプリはライブラリのリソースへアクセスできず、正しく動きません。もし、Android Studioを使っているなら、build.gradleファイルのdependency節に以下の記述を追加して下さい。
dependencies {
compile "com.google.android.gms:play-services:3.1.+"
}
以下の内容をアプリケーションのマニフェストに追加して下さい
com.google.android.c2dm.permission.RECEIVE
- メッセージの登録・受信の許可
android.permission.INTERNET
- アプリケーションが登録IDをサードパーティのサーバーに送るため
android.permission.GET_ACCOUNTS
- GCMはグーグルアカウントが必要なため(デバイスのOSバージョンが4.0.4未満の時に必要)
android.permission.WAKE_LOCK
- メッセージを受信した時に、画面をONにする(この設定はオプションです)
applicationPackage + ".permission.C2D_MESSAGE"
- 他のAndroidアプリケーションがメッセージを登録してしまったり、受信してしまわないための設定
- このパーミッション名が間違っているとメッセージを受け取れません
com.google.android.c2dm.intent.RECEIVE
com.google.android.c2dm.SEND
も必要になります。- もしアプリがIntentServiceを使っている場合は、このレシーバーはWakefulBroadcastReceiverのインスタンスでなければいけません。WakefulBroadcastReceiverがアプリのWakefulBroadcastReceiverを作成・管理します。
- Service(特にIntentService)はWakefulBroadcastReceiverがGCMメッセージをハンドリングして、スリープ状態にならないようにします。IntentServiceは必須ではないため、メッセージ受信をBroadcastReceiverにすることも出来ます。ただ、実際はほとんどのアプリケーションはIntentServiceを使うでしょう。
- もしGCMの部分が、アプリケーションのロジックにクリティカルな影響を与える場合、マニフェストに
android:minSdkVersion="8"
かそれ以上のバージョン指定を行って下さい。これによって、GCMが実行できないようなOSバージョンではインストールできなくなります。
以下がGCMをサポートしているサンプルのマニフェストです。
<manifest package="com.example.gcm" ...>
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.example.gcm.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
<application ...>
<receiver
android:name=".GcmBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.example.gcm" />
</intent-filter>
</receiver>
<service android:name=".GcmIntentService" />
</application>
</manifest>
最後に、アプリケーションのコードを書きます。この説では、GoogleCloudMessaging APIをどのように使うかという事を紹介します。 このサンプルは、main activity(DemoActivity)とWakefulBroadcastReceiver(GcmBroadcastReceiver)とIntentService(GcmIntentService)で構成されています。ソースコードはここからダウンロードできます。
- 注意点
- 本サンプルではデバイスの登録とupstream(デバイスからクラウドへの)メッセージング例を示します。upstreamメッセージングはCCS(XMPP)サーバーに関係なく、アプリに適用されます。HTTPベースのサーバーではupstreamメッセージングはサポートされません。
- GoogleCloudMessaging登録APIは、非推奨のクライアントヘルパーライブラリで使っていた登録プロセスから置き変えたものです。
Play Services SDKを使っているアプリは、Google Play servicesの機能にアクセスする前に、必ずGoogle Play servicesに対応したAPKが入っている端末かチェックして下さい。Setup Google Play Services SDKに説明があるとおりです。サンプルアプリでは、2箇所で行っています。
1つめは、main activityのonCreate()メソッド、2つめはonResume()メソッドです。onCreate()でのチェックではチェクが成功しない限り、アプリが利用できない事を保証できます。onResume()のチェックでは、レジュームされた時にチェックを行います。例えば、ユーザーがバックボタンを押してアプリを一旦バックグラウンドにして、またアプリを起動させた時です。もし、Google Play servicesとの互換性がない場合、GooglePlayServicesUtil.getErrorDialog()をコールして、ユーザーにGoogle Play Storeからapkをダウンロードするように促したり、デバイス設定でONにできます。
以下、例です。
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mDisplay = (TextView) findViewById(R.id.display);
context = getApplicationContext();
// Check device for Play Services APK.
if (checkPlayServices()) {
// If this check succeeds, proceed with normal processing.
// Otherwise, prompt user to get valid Play Services APK.
...
}
}
// You need to do the Play Services APK check here too.
@Override
protected void onResume() {
super.onResume();
checkPlayServices();
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i(TAG, "This device is not supported.");
finish();
}
return false;
}
return true;
}
アプリケーションはメッセージを受け取る前に、GCMサーバーに登録されている必要があります。アプリが登録された時、登録IDを取得します。そのIDは将来的にも使うので、保存して下さい(ただし、公開しないように保持しておくべき事に注意して下さい)。以下の例では、onCreate()メソッドが、そのアプリがすでにGCMに登録されているかどうかチェックしています。
/**
* Main UI for the demo app.
*/
public class DemoActivity extends Activity {
public static final String EXTRA_MESSAGE = "message";
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
/**
* Substitute you own sender ID here. This is the project number you got
* from the API Console, as described in "Getting Started."
*/
String SENDER_ID = "Your-Sender-ID";
/**
* Tag used on log messages.
*/
static final String TAG = "GCMDemo";
TextView mDisplay;
GoogleCloudMessaging gcm;
AtomicInteger msgId = new AtomicInteger();
SharedPreferences prefs;
Context context;
String regid;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mDisplay = (TextView) findViewById(R.id.display);
context = getApplicationContext();
// Check device for Play Services APK. If check succeeds, proceed with
// GCM registration.
if (checkPlayServices()) {
gcm = GoogleCloudMessaging.getInstance(this);
regid = getRegistrationId(context);
if (regid.isEmpty()) {
registerInBackground();
}
} else {
Log.i(TAG, "No valid Google Play Services APK found.");
}
}
...
}
この例では、getRegistrationId()を呼んで、shared設定に登録IDがすでに保存されていないかチェックしています。
/**
* Gets the current registration ID for application on GCM service.
* <p>
* If result is empty, the app needs to register.
*
* @return registration ID, or empty string if there is no existing
* registration ID.
*/
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
...
/**
* @return Application's {@code SharedPreferences}.
*/
private SharedPreferences getGCMPreferences(Context context) {
// This sample app persists the registration ID in shared preferences, but
// how you store the regID in your app is up to you.
return getSharedPreferences(DemoActivity.class.getSimpleName(),
Context.MODE_PRIVATE);
}
もし、登録IDが存在しなかったり、更新されていた場合、getRegistrationId()は空stringを返します。これは新しいregIDを取得する必要があるということです。 getRegistrationId()はアプリのバージョンを確認するため、内部で以下のメソッドを呼んでいます。
/**
* @return Application's version code from the {@code PackageManager}.
*/
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
もし有効な登録IDがなかった時、DemoActivityは以下の例のように、registerInBackground()をコールしてIDを登録します。注意すべき点は、register()とunregister()はブロッキングなメソッドのため、バックグラウンドスレッドで行うべきということです。そのため、サンプルではAsyncTaskを利用しています。
/**
* Registers the application with GCM servers asynchronously.
* <p>
* Stores the registration ID and app versionCode in the application's
* shared preferences.
*/
private void registerInBackground() {
new AsyncTask() {
@Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
// The request to your server should be authenticated if your app
// is using accounts.
sendRegistrationIdToBackend();
// For this demo: we don't need to send it because the device
// will send upstream messages to a server that echo back the
// message using the 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
@Override
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
...
}
登録IDを受け取ったら、サーバーに送信します。
/**
* Sends the registration ID to your server over HTTP, so it can use GCM/HTTP
* or CCS to send messages to your app. Not needed for this demo since the
* device sends upstream messages to a server that echoes back the message
* using the 'from' address in the message.
*/
private void sendRegistrationIdToBackend() {
// Your implementation here.
}
登録後、アプリはstoreRegistrationId()を呼んで、shared preferencesに登録IDを保存します。この値を今後も使いまわします。上記の方法はregIDを使うまでの1つの方法に過ぎません。アプリごとに取得の仕方は異なるでしょう。
/**
* Stores the registration ID and app versionCode in the application's
* {@code SharedPreferences}.
*
* @param context application's context.
* @param regId registration ID
*/
private void storeRegistrationId(Context context, String regId) {
final SharedPreferences prefs = getGCMPreferences(context);
int appVersion = getAppVersion(context);
Log.i(TAG, "Saving regId on app version " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
editor.commit();
}
ユーザーがアプリの送信ボタンを押した時、アプリはGoogleCloudMessaging APIを使ってupstreamメッセージを送信します。upstreamメッセージを取得するためには、サーバーがCCSに接続しなければなりません。デモサーバーはImplementing an XMPP-based App Serverにあります。このサンプルを使えばCCSに接続する事ができます。
public void onClick(final View view) {
if (view == findViewById(R.id.send)) {
new AsyncTask() {
@Override
protected String doInBackground(Void... params) {
String msg = "";
try {
Bundle data = new Bundle();
data.putString("my_message", "Hello World");
data.putString("my_action",
"com.google.android.gcm.demo.app.ECHO_NOW");
String id = Integer.toString(msgId.incrementAndGet());
gcm.send(SENDER_ID + "@gcm.googleapis.com", id, data);
msg = "Sent message";
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
@Override
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
} else if (view == findViewById(R.id.clear)) {
mDisplay.setText("");
}
}
上記Step 2で説明したように、このアプリではcom.google.android.c2dm.intent.RECEIVEインテントを使うため、WakefulBroadcastReceiverを含んでいます。broadcastレシーバーはメッセージ送信を使うための、GCMの機構です。onClick()がコールされた時にgcm.send()がコールされ、ブロードキャストレシーバーのonReceive()メソッドがトリガーされます。onReceive()メソッドがGCMメッセージのハンドリングを保証します。
WakefulBroadcastReceiverはpartial wake lockを作成したり管理するための特別なブロードキャストレシーバーです。端末がスリープ状態に再度戻らないように保証している間、GCMメッセージをService(特にIntentService)に送信完了します。もし、serviceへの送信トランザクション中に画面がONになるように設定していなかった場合、処理が完了する前にデバイスがスリープ状態になり得ます。結果として、アプリがGCMメッセージの処理を完了するのは、その時の状況によって変わり、開発者が意図しない時となります。
- 注意点
- WakefulBroadcastReceiverは必須ではありません。もし、serviceを使わないようなアプリを作っていた場合、GCMメッセージは通常のBroadcastReceiverで受信して処理する事ができます。GCMから、アプリのonReceive()で受信されたメッセージを受け取ったら、あとはアプリごとに行いたい処理を行って下さい。
メッセージを受信する部分は、GcmIntentServiceのstartWakefulService()メソッドです。このメソッドはstartService()に当たります。ただし、serviceが開始した時に、WakefulBroadcastReceiverが画面ON状態にする点が違います。
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Explicitly specify that GcmIntentService will handle the intent.
ComponentName comp = new ComponentName(context.getPackageName(),
GcmIntentService.class.getName());
// Start the service, keeping the device awake while it is launching.
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
以下の例で示す、インテントサービスはGCMメッセージをハンドリングしています。serviceが終わった時に、画面ONの状態を解除するためにGcmBroadcastReceiver.completeWakefulIntent()を呼びます。 このcompleteWakefulIntent()メソッドは、WakefulBroadcastReceiverを通ったインテントと同じパラメータを持っています。
この処理はGCMメッセージを処理して、通知として結果を表示します。しかし、GCMメッセージをどうするかは、開発者に委ねられています。例えば、pingのようなメッセージであったり、サーバーから新しいコンテンツを同期するための通知であったり、画面に表示するチャットメッセージなどです。
public class GcmIntentService extends IntentService {
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
NotificationCompat.Builder builder;
public GcmIntentService() {
super("GcmIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
// The getMessageType() intent parameter must be the intent you received
// in your BroadcastReceiver.
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) { // has effect of unparcelling Bundle
/*
* Filter messages based on message type. Since it is likely that GCM
* will be extended in the future with new message types, just ignore
* any message types you're not interested in, or that you don't
* recognize.
*/
if (GoogleCloudMessaging.
MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.
MESSAGE_TYPE_DELETED.equals(messageType)) {
sendNotification("Deleted messages on server: " +
extras.toString());
// If it's a regular GCM message, do some work.
} else if (GoogleCloudMessaging.
MESSAGE_TYPE_MESSAGE.equals(messageType)) {
// This loop represents the service doing some work.
for (int i=0; i<5; i++) {
Log.i(TAG, "Working... " + (i+1)
+ "/5 @ " + SystemClock.elapsedRealtime());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
// Post notification of received message.
sendNotification("Received: " + extras.toString());
Log.i(TAG, "Received: " + extras.toString());
}
}
// Release the wake lock provided by the WakefulBroadcastReceiver.
GcmBroadcastReceiver.completeWakefulIntent(intent);
}
// Put the message into a notification and post it.
// This is just one simple example of what you might choose to do with
// a GCM message.
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager)
this.getSystemService(Context.NOTIFICATION_SERVICE);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, DemoActivity.class), 0);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_gcm)
.setContentTitle("GCM Notification")
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
}
サンプルを実行するためには、以下の手順を踏んで下さい。
- Getting Startedを参考に、sender IDとAPI keyの取得
- (本ドキュメントに記載があるように、)クライアントアプリケーションの実装。プロジェクトのコードここにあります。
- Implementing an XMPP-based App Serverのように、デモサーバー(JavaかPython)を動かす。
- どちらのデモコードでも構いませんが、取得したsender IDとAPI keyを設定する必要に注意して下さい
統計情報やエラーメッセージを見るためには、以下を行って下さい。
- Developer consoleにアクセスする
- デベロッパーアカウントでログインする
- 作成したアプリの一覧が表示されるはずです
- GCMのステータスを確認したいアプリの、"statistics"リンクをクリックする
- ドロップダウンメニューから、確認したいGCMの情報を選択する
Google API Console上ではGCMが確認できません。必ずDeveloper consoleを利用して下さい