Skip to content

Instantly share code, notes, and snippets.

@seamountain
Last active June 30, 2017 08:50
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seamountain/aaafe715ba61adbdf3fb to your computer and use it in GitHub Desktop.
Save seamountain/aaafe715ba61adbdf3fb to your computer and use it in GitHub Desktop.
Implementing GCM Client の日本語訳

Implementing GCM Client の日本語訳

https://developer.android.com/google/gcm/client.html

Implementing GCM client

GCMクライアントとは、GCMが内蔵されたアプリのことです。クライアントコードでは、GoogleCloudMessaging APIを使うことを推奨します。このヘルパライブラリは、古いバージョンのGCMが動くことを保証していますが、その代替かつ、より有益なGoogleCloudMessaging APIを提供しています。

完全なGCMの実装には、クライアントとサーバーサイド両方の実装が必要です。サーバーサイドの実装に関してはImplementing GCM Serverを参照して下さい。

本節では、GCMのクライアントサイドアプリケーションを書くために必要な手順を紹介します。作成するクライアントアプリケーションは、登録IDを記述したコードと、GCMからのメッセージを受け取る、ブロードキャストレシーバーが必要になります。

Step 1: Set Up Google Play Services

このクライアントアプリケーションを書くためには、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.+"
}

Step 2: Edit Your Application's Manifest

以下の内容をアプリケーションのマニフェストに追加して下さい

  • 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
  • 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>

Step 3: Write Your Application

最後に、アプリケーションのコードを書きます。この説では、GoogleCloudMessaging APIをどのように使うかという事を紹介します。 このサンプルは、main activity(DemoActivity)とWakefulBroadcastReceiver(GcmBroadcastReceiver)とIntentService(GcmIntentService)で構成されています。ソースコードはここからダウンロードできます。

  • 注意点
    • 本サンプルではデバイスの登録とupstream(デバイスからクラウドへの)メッセージング例を示します。upstreamメッセージングはCCS(XMPP)サーバーに関係なく、アプリに適用されます。HTTPベースのサーバーではupstreamメッセージングはサポートされません。
    • GoogleCloudMessaging登録APIは、非推奨のクライアントヘルパーライブラリで使っていた登録プロセスから置き変えたものです。

Check for Google Play Services APK

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;
}

Register for GCM

アプリケーションはメッセージを受け取る前に、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();
}

Send a message

ユーザーがアプリの送信ボタンを押した時、アプリは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("");
    }
}

Receive a message

上記Step 2で説明したように、このアプリではcom.google.android.c2dm.intent.RECEIVEインテントを使うため、WakefulBroadcastReceiverを含んでいます。broadcastレシーバーはメッセージ送信を使うための、GCMの機構です。onClick()がコールされた時にgcm.send()がコールされ、ブロードキャストレシーバーのonReceive()メソッドがトリガーされます。onReceive()メソッドがGCMメッセージのハンドリングを保証します。

WakefulBroadcastReceiverpartial 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());
    }
}

Running the Sample

サンプルを実行するためには、以下の手順を踏んで下さい。

  1. Getting Startedを参考に、sender IDとAPI keyの取得
  2. (本ドキュメントに記載があるように、)クライアントアプリケーションの実装。プロジェクトのコードここにあります。
  3. Implementing an XMPP-based App Serverのように、デモサーバー(JavaかPython)を動かす。
  • どちらのデモコードでも構いませんが、取得したsender IDとAPI keyを設定する必要に注意して下さい

Viewing Statistics

統計情報やエラーメッセージを見るためには、以下を行って下さい。

  1. Developer consoleにアクセスする
  2. デベロッパーアカウントでログインする
  • 作成したアプリの一覧が表示されるはずです
  1. GCMのステータスを確認したいアプリの、"statistics"リンクをクリックする
  2. ドロップダウンメニューから、確認したいGCMの情報を選択する

注意点

Google API Console上ではGCMが確認できません。必ずDeveloper consoleを利用して下さい

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment