Android端末で、FCM経由でAWSSNSを受け取るまで

  • 3
    いいね
  • 0
    コメント

はじめに

GoogleがFirebaseを吸収した結果、GCM(Google Cloud Messaging)がFCM(Firebase Cloud Massaging)に切り替わった。
そのため、諸々の設定の方法がGCM時代の手順から変わっている。
ここでは、ゼロからFCMの環境とAWS、FCM、Androidの設定や実装を書いていく。
なお、Server側の実装に関しては記載してない

前提条件

任意のAndroidプロジェクトが作られている
AWSのAccess KeyとSecret Keyが生成されていること

Firebaseの設定

プロジェクトを新規追加し、Androidアプリとの紐づけを行う
1. Firebase ConsoleにGoogleアカウントでログインする。ログインするアカウントにサービスが紐づけられるので、注意すること
2. 「新規プロジェクトを作成」ボタンを押す
3. プロジェクト名を任意で入力。国/地域を「日本」に。
4. プロジェクト作成ボタンを押す。しばらくすると画面が切り替わる
5. 「AndroidアプリにFireabaseを追加」を選択すると、ダイアログが表示されるので情報を入力していく
6. Android package nameにSNSをアプリのパッケージ名を入力する
7. アプリのニックネームはコンソールの表示用なので任意。デバッグ署名は空欄でOK
8. REGISTER APPをクリック
9. Download google-services.jsonをダウンロードして、ここに配置してね、と言われるので言われた通りに配置する
10. 終わったら続行ボタンを押す
11. Gradleの設定してね、と言われるので、言われた通りに設定する。その後、終了をクリック
12. 登録されたアプリの設定を開く(右上の赤丸のやつをクリックするとメニューが出てくる)
スクリーンショット 2017-04-10 17.13.47.png
13. クラウドメッセージングタブを選択する
14. Legacy server key をコピーする。AWS側の設定で使う。

AWS側の設定

AWSSNSサービスとFCMを紐づける
1. AWSコンソールにサインインする
2. 検索窓に「SNS」と入力し、「Simple Notification Service」を選択する
スクリーンショット 2017-04-10 16.52.20.png
3. メニューから「Application」を選択
4. 画面が切り替わるので、「Create platform application」ボタンをクリック
5. Application Nameを入力する。任意
6. push Platform NotificationでGoogle Cloud Messaging(GCM)を選択する
7. API Keyにコピーした「Legacy server key」を貼り付け、Create platform applicationをクリックする
8. 登録されると一覧に情報が追加されるので「ARN」をクリックし、詳細を開く
9. 画面上部にある、「Application ARN」をコピーする

Android Studio側の実装

Gradleの設定

公式の説明が結構わかりやすいのでそちらを見てもOK。特にバージョン情報は最新のものを参照したほうが良い

設定はGradleに以下を追加(Fireabase設定手順でclasspath等の設定は終わっている前提)

build.gradle
dependencies {
  // ...
  compile 'com.google.firebase:firebase-core:10.0.1'
  compile 'com.google.firebase:firebase-messaging:10.0.1'
  // Getting a "Could not find" error? Make sure you have
  // the latest Google Repository in the Android SDK manager
}

メッセージを受け取る用のサービスを追加

先のGradleの設定の公式説明の続きに書いてあるのでそちらを参考したほうがわかりやすいかも。また、メッセージの細かな仕様も同一ページに書いてあるので一読推奨。

やることはAndrdoidManifestの追加とServiceの実装を追加

AndroidManifest.xml
<service android:name=".MyFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
MyFirebaseMessagingService.java
public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private static final String TAG = "MyFirebaseMsgService";

    /**
     * Called when message is received.
     *
     * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
     */
    // [START receive_message]
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // TODO(developer): Handle FCM messages here.
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Log.d(TAG, "From: " + remoteMessage.getFrom());

        // Check if message contains a data payload.
        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
        }
    }
}

AWSにEndpointを登録するための実装を追加

これだけがAWSとFCMの合わせ技になる。
AWS側の公式の説明FCM側の公式の説明を参照。

AWS SDKを追加する

build.gradleに以下を追加

build.gradle
dependencies {
  // ...
    compile 'com.amazonaws:aws-android-sdk-core:2.4.+'
    compile 'com.amazonaws:aws-android-sdk-sns:2.4.+'
}

AndroidManifestにサービスを追加する

AndroidManifest.xmlに以下を追加

AndroidManifest.xml
<service android:name=".MyFirebaseInstanceIDService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>

MyFirebaseInstanceIDServiceクラスを実装する

MyFirebaseInstanceIDService.java
public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {
    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the InstanceID token
     * is initially generated so this is where you would retrieve the token.
     */
    // [START refresh_token]
    @Override
    public void onTokenRefresh() {
        // Get updated InstanceID token.
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        Log.d(TAG, "Refreshed token: " + refreshedToken);

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // Instance ID token to your app server.
        sendRegistrationToServer(refreshedToken);
    }
    // [END refresh_token]

    /**
     * Persist token to third-party servers.
     *
     * Modify this method to associate the user's FCM InstanceID token with any server-side account
     * maintained by your application.
     *
     * @param token The new token.
     */
    private void sendRegistrationToServer(String token) {
        // TODO: Implement this method to send token to your app server.
    }
}

上記のsendRegistrationToServer内で、AWS側にtoken情報を送る。ここはAWS側の実装を参考にすればOK。最終的には以下のようになる

MyFirebaseInstanceIDService.java
public class NotificationInstanceIdServiceTemp extends FirebaseInstanceIdService {

    private static final String APPLICATION_ARN = "AWSSNSでコピーしたApplication ARN";
    private static final String ENDPOINT = "https://sns.ap-northeast-1.amazonaws.com";
    private static final String ACCESS_KEY = "AWSのアクセスキー";
    private static final String SECRET_KEY = "AWSのSecretKey";

    @Override
    public void onTokenRefresh() {
        super.onTokenRefresh();
        String token = FirebaseInstanceId.getInstance().getToken();
        sendRegistrationToServer(token);
    }

    private void sendRegistrationToServer(String token) {
        AmazonSNSClient client = new AmazonSNSClient(generateAWSCredentials());
        client.setEndpoint(ENDPOINT);
        //SharedPreferenceに保存したendpointArnが存在したらそちらから取得するようにしてもOK
        String endpointArn = createEndpointArn(token, client);
        HashMap<String, String> attr = new HashMap<>();
        attr.put("Token", token);
        attr.put("Enabled", "true");
        SetEndpointAttributesRequest req = new SetEndpointAttributesRequest().withEndpointArn(endpointArn).withAttributes(attr);
        client.setEndpointAttributes(req);
    }

    private String createEndpointArn(String token, AmazonSNSClient client) {
        String endpointArn;
        try {
            System.out.println("Creating platform endpoint with token " + token);
            CreatePlatformEndpointRequest cpeReq =
                    new CreatePlatformEndpointRequest()
                            .withPlatformApplicationArn(APPLICATION_ARN)
                            .withToken(token);
            CreatePlatformEndpointResult cpeRes = client
                    .createPlatformEndpoint(cpeReq);
            endpointArn = cpeRes.getEndpointArn();
        } catch (InvalidParameterException ipe) {
            String message = ipe.getErrorMessage();
            System.out.println("Exception message: " + message);
            Pattern p = Pattern
                    .compile(".*Endpoint (arn:aws:sns[^ ]+) already exists " +
                            "with the same token.*");
            Matcher m = p.matcher(message);
            if (m.matches()) {
                // The platform endpoint already exists for this token, but with
                // additional custom data that
                // createEndpoint doesn't want to overwrite. Just use the
                // existing platform endpoint.
                endpointArn = m.group(1);
            } else {
                // Rethrow the exception, the input is actually bad.
                throw ipe;
            }
        }
        storeEndpointArn(endpointArn);
        return endpointArn;
    }

    private AWSCredentials generateAWSCredentials() {
        return new AWSCredentials() {
            @Override
            public String getAWSAccessKeyId() {
                return ACCESS_KEY;
            }

            @Override
            public String getAWSSecretKey() {
                return SECRET_KEY;
            }
        };
    }

    private void storeEndpointArn(String endpointArn) {
        //SharedPreferenceにでもendpointArnを保存して、次回以降はcreateEndpointArnの処理を省略しても良い(公式はその方式になってる)
    }

    private String getEndPointArn() {
        //SharedPreferenceからendpointArnを取得して、次回以降はcreateEndpointArnの処理を省略しても良い(公式はその方式になってる)
    }
}

APPLICATION_ARNはAWSSNSの設定の過程でコピーしたやつを貼り付ける
ACCESS_KEYSECRET_KEYはそれぞれAWSに登録されているものを設定
END_POINTはAPPLICATION_ARNのパラメータと合致するものを記載する。
application ARNのリージョンが「arn:aws:sns:ap-northeast-1〜」となっているならこのページを参考に、該当するURLを記載する。日本のAWSSNSならhttps://sns.ap-northeast-1.amazonaws.comになる。

メッセージを送信/受信する

アプリを起動すると最初にEndpointArnの登録処理が行われる。正常に行われれば、AWSSNS側の「End points」にトークン情報が追加される。
End pointsの画面に行き方は、AWSにログイン→SNSサービスを開く→Applicationsメニューを選ぶ→Arnのリンクをクリック で開ける

メッセージを送信する場合は、Tokenの横のチェックボックスにチェックを入れると「Publish to Endpoint」ボタンが有効になるのでそれをクリックする。
送信メッセージ作成用の画面に切り替わるので任意のメッセージを入力して「Publish message」を押せばOK。正常に実装できていればMyFirebaseMessagingService.javaにメッセージが飛んでくる