Edited at

Android から Amazon SNS を使ってみる

More than 1 year has passed since last update.


0.はじめに

以前、こちらの記事を投稿したんですが、

書いた以上はやらないとな…、

と思い、

Android 全く使ったことないですが…、

頑張って Android から Amazon SNS を使ってみました。


1.Android Studio で、新規プロジェクトを作成する


  1. Android Studio を起動し、「Start a new Android Studio project」リンクをクリックします。



    • 1001.png






  2. 「Create Android Project」画面が表示されるので、以下の項目を入力し「Next」ボタンを押下します。


    • Application name : ※任意

    • Company domain : ※任意





    • 1002.png






  3. 「Target Android Devices」画面が表示されるので、とりあえずデフォルトの値のままで「Next」ボタンを押下します。



    • 1003.png






  4. 「Add an Activity to Mobile」画面が表示されるので、「Empty Activity」を選択し「Next」ボタンを押下します。



    • 1004.png






  5. 「Configure Activity」画面が表示されるので、とりあえずデフォルトの値のままで「Next」ボタンを押下します。



    • 1005.png






  6. そのまま待っていると、プロジェクトが作成されます。


    • 1006.png




2.Android Studio で、作成したプロジェクトを実機で動かす


  1. 実機を接続して、以下のサイトを参考に開発モードの設定をします。



  2. Android Studio からプロジェクトを開き、「Run」アイコンをクリックします。



    • 2001.png






  3. 「Select Deployment Target」ダイアログが表示されるので、接続した実機が表示されていることを確認し、選択後、「OK」ボタンを押下します。



    • 2002.png






  4. 実機にアプリがインストール後、実行され、以下の「Hello World!」の画面が表示されます。


    • 2003.jpg




3.Firebase プロジェクトを作成する


  1. 以下のサイトから、FireBase のコンソールを開き、「プロジェクトを追加」ボタンを押下します。



    • 3001.png






  2. 「プロジェクトを追加」ダイアログが表示されるので、以下の項目を設定し、「プロジェクトを作成」ボタンを押下します。


    • プロジェクト名 : ※任意

    • 国 / 地域 : ※任意





    • 3002.png






  3. 以下の画面が表示され、Firebase プロジェクトが作成されます。


    • 3003.png




4.Firebase プロジェクトにアプリを登録する。


  1. 作成した Firebase プロジェクトのオーバービューから、「Android アプリに Firebase を追加」ボタンを押下します。



    • 4001.png






  2. 「Android アプリに Firebase を追加」ダイアログが表示されるので、以下の項目を設定し、「アプリの登録」ボタンを押下します。


    • Android パッケージ名 : ※作成した Android アプリのパッケージ名を入力


      • ※ MainActivity.java に記載。







    • 4002.png






  3. 「Android アプリに Firebase を追加」ダイアログが更新され、「Android Studio の使用手順」が表示されるので、手順に従って google-services.json をダウンロードし、所定の場所に移動後、「続行」ボタンを押下します。



    • 4003.png






  4. 「Android アプリに Firebase を追加」ダイアログが更新され、「Gradle の使用手順」が表示されるので、手順に従って2つの build.gradle を変更し、「Sync now」リンククリック後、「終了」ボタンを押下します。



    • 4004.png






  5. 「Android アプリに Firebase を追加」ダイアログが閉じ、Firebase プロジェクトの「概要」画面が表示されるので、アプリが登録されたことを確認します。


    • 4005.png




5.Firebase プロジェクトに登録したアプリのサーバーキーを確認する


  1. Firebase プロジェクトの「概要」画面に表示されている登録したアプリエリアの右上のメニューを開き、「設定」を選択します。



    • 5001.png






  2. Firebase プロジェクトの「設定」画面が表示されるので、「クラウドメッセージング」タブを選択します。



    • 5002.png






  3. Firebase プロジェクトの「設定」画面に「クラウドメッセージング」の情報が表示されるので、「プロジェクト認証情報」エリアの「サーバーキー」を確認します。


    • 5003.png




6.Amazon SNS にプラットフォームアプリケーションを作成する


  1. 以下のサイトから、Amazon SNS のコンソールを開きます。



  2. Amazon SNS のコンソールの左側ペインから、「アプリケーション」を選択します。



    • 6001.png






  3. Amazon SNS の「アプリケーション」画面が表示されるので、「プラットフォームアプリケーションの作成」ボタンを押下します。



    • 6002.png






  4. 「プラットフォームアプリケーションの作成」ダイアログが表示されるので、以下の項目を設定し、「プラットフォームアプリケーションの作成」ボタンを押下します。


    • アプリケーション名 : ※任意

    • プッシュ通知プラットフォーム : 「Google Cloud Messaging (GCM)」

    • API キー : ※確認しておいた Firebase プロジェクトのクラウドメッセージングの「サーバーキー」



      • ※「以前のサーバーキー」ではないので、注意!!







    • 6003.png






  5. Amazon SNS のプラットフォームアプリケーションが作成されます。


    • 6004.png




7.Amazon Cognito に ID プールを作成する


  1. 以下のサイトから、Amazon Cognito フェデレーテッドアイデンティティのコンソールを開きます。



  2. 「新しい ID プールの作成」ボタンを押下します。



    • 7001.png






  3. 「使用開始ウィザード - ステップ 1:ID プールを作成する」画面が表示され、以下の項目を設定し、「プールの作成」ボタンを押下します。


    • ID プール名 : ※任意

    • 認識されていない ID


      • 認識されていない ID に対してアクセスを有効にする : ☑︎







    • 7002.png






  4. 画面が更新されるので、新しく作成される2つ(Auth と Unauth)の IAM ロールのロール名を確認し、「許可」ボタンを押下します。



    • 7003.png






  5. 「サンプルコード」画面が表示され、ID プールが作成されます。


    • 7004.png




8.作成した Amazon Cognito ID プールの IAM ロール に Amazon SNS 用のインラインポリシーを追加する


  1. 以下のサイトから、AWS Identity and Access Management (IAM) のコンソールを開きます。



  2. AWS Identity and Access Management (IAM) のコンソールの左側ペインから、「ロール」を選択します。



    • 8001.png






  3. ロールの一覧が表示されるので、作成した Amazon Cognito ID プールの IAM ロール (Unauth) の「ロール名」リンクをクリックします。



    • 8002.png






  4. 作成した Amazon Cognito ID プールの IAM ロール (Unauth) の概要の画面が表示されるので、「アクセス権限」のタブを選択し、「インラインポリシーの追加」リンクをクリックします。



    • 8003.png






  5. 「ポリシーの作成」画面が表示されるので、「ビジュアルエディタ」のタブを選択後、以下の項目を設定し、「Review policy 」ボタンを押下します。


    • サービス : SNS

    • アクション :


      • 書き込み :


        • ☑︎ CreatePlatformEndpoint

        • ☑︎ SetEndpointAttributes









    • 8004.png






  6. 「ポリシーの作成 - ポリシーの確認」画面が表示されるので、以下の項目を設定し、「Create policy 」ボタンを押下します。


    • 名前 : ※任意





    • 8005.png






  7. 作成したインラインポリシーの内容を確認します。


    • 8006.png




9.Android アプリへ実装する



  1. <project>/<app-module>/build.gradle へ、以下のライブラリの登録の設定を追加する。

  2. build.gradle


    dependencies {

    // [2018.01.12] ADD - BGN
    // 「Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita」
    // <https://qiita.com/k_keisuke/items/24afa62955808721033e>
    // ...
    compile 'com.google.firebase:firebase-core:10.2.1'
    compile 'com.google.firebase:firebase-messaging:10.2.1'
    // ...
    compile 'com.amazonaws:aws-android-sdk-core:2.4.+'
    compile 'com.amazonaws:aws-android-sdk-sns:2.4.+'
    // [2018.01.12] ADD - END
    }




  3. AndroidManifest.xml へ、追加するクラスの設定を追加する。

  4. AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kusokamayarou.android_sns_sample0001">

    <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <!-- [2018.01.12] ADD - BGN -->
    <!--「マニフェストの設定」-->
    <!-- https://docs.kii.com/ja/samples/push-notifications/push-notifications-android-fcm/modify-manifest/ -->
    <service android:name=".MyFirebaseMessagingService">
    <intent-filter>
    <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
    </service>
    <service android:name=".MyFirebaseInstanceIDService">
    <intent-filter>
    <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
    </service>
    <!-- [2018.01.12] ADD - END -->

    </application>

    </manifest>



  5. 以下の MyFirebaseInstanceIDService.java を追加する。

  6. MyFirebaseInstanceIDService.java

    package com.kusokamayarou.android_sns_sample0001;

    import android.annotation.SuppressLint;
    import android.content.SharedPreferences;
    import android.util.Log;

    import com.google.firebase.iid.FirebaseInstanceId;
    import com.google.firebase.iid.FirebaseInstanceIdService;

    import java.util.HashMap;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    // 「Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita」
    // <https://qiita.com/k_keisuke/items/24afa62955808721033e>

    import com.amazonaws.regions.Regions;
    import com.amazonaws.auth.CognitoCachingCredentialsProvider;
    import com.amazonaws.services.sns.AmazonSNSClient;
    import com.amazonaws.services.sns.model.CreatePlatformEndpointRequest;
    import com.amazonaws.services.sns.model.CreatePlatformEndpointResult;
    import com.amazonaws.services.sns.model.InvalidParameterException;
    import com.amazonaws.services.sns.model.SetEndpointAttributesRequest;

    public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {

    private final static String TAG = MyFirebaseInstanceIDService.class.getSimpleName();

    private static final String APPLICATION_ARN = "[プラットフォームアプリケーションの 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);
    }

    @SuppressLint("LongLogTag")
    private void sendRegistrationToServer(String token) {
    //AmazonSNSClient client = new AmazonSNSClient(generateAWSCredentials());
    CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
    getApplicationContext(),
    "[Amazon Cognito の ID プールの ID]", // ID プールの ID
    Regions.AP_NORTHEAST_1 // リージョン
    );
    AmazonSNSClient client = new AmazonSNSClient(credentialsProvider);
    client.setEndpoint(ENDPOINT);
    //SharedPreferenceに保存したendpointArnが存在したらそちらから取得するようにしてもOK
    String endpointArn = createEndpointArn(token, client);
    // 「[Android] データを保存し Android Studio で確認 SharedPreferences | nyanのアプリ開発」
    // <https://akira-watson.com/android/sharedpreferences.html>
    SharedPreferences dataStore = getSharedPreferences("DataStore", MODE_PRIVATE);
    SharedPreferences.Editor editor = dataStore.edit();
    editor.putString("DeviceToken", token);
    editor.putString("EndpointArn", endpointArn);
    editor.apply();
    Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | DeviceTokenArray: " + token);
    Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | EndpointArnArray: " + endpointArn);
    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);
    }

    @SuppressLint("LongLogTag")
    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の処理を省略しても良い(公式はその方式になってる)
    }
    */

    }



  7. 以下の MyFirebaseMessagingService.java を追加する。

  8. MyFirebaseMessagingService.java

    package com.kusokamayarou.android_sns_sample0001;

    import android.annotation.SuppressLint;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.support.v4.app.NotificationCompat;
    import android.util.Log;

    import com.google.firebase.messaging.FirebaseMessagingService;
    import com.google.firebase.messaging.RemoteMessage;

    public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private final static String TAG = MyFirebaseMessagingService.class.getSimpleName();

    /**
    * Called when message is received.
    *
    * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
    */

    // [START receive_message]
    @SuppressLint("LongLogTag")
    @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.i(TAG, "From: " + remoteMessage.getFrom());

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

    // 「【Android】NotificationからIntentでデータを渡してアプリ起動するときのIntent.setFlagsメモ - Qiita」
    // <https://qiita.com/SnowMonkey/items/7cdc52c83bb9490d97a5>
    NotificationManager notificationManager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
    Intent intent = new Intent(this, MainActivity.class);
    intent.putExtra("hoge", "fuga");
    intent.setFlags(
    Intent.FLAG_ACTIVITY_CLEAR_TOP // 起動中のアプリがあってもこちらを優先する
    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED // 起動中のアプリがあってもこちらを優先する
    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS // 「最近利用したアプリ」に表示させない
    );
    PendingIntent contentIntent =
    PendingIntent.getActivity(
    this,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);
    NotificationCompat.Builder mBuilder =
    new NotificationCompat.Builder(this)
    .setSmallIcon(android.R.drawable.sym_def_app_icon)
    .setContentTitle(remoteMessage.getData().toString())
    .setContentText("Notification Message")
    .setStyle(new NotificationCompat.BigTextStyle().bigText("Notification Message"));
    mBuilder.setContentIntent(contentIntent);
    notificationManager.notify(0, mBuilder.build());
    }
    }



  9. MainActivity.java へ、以下のコードを追加する。

  10. MainActivity.java

    package com.kusokamayarou.android_sns_sample0001;

    import android.content.SharedPreferences;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;

    public class MainActivity extends AppCompatActivity {

    // [2018.01.12] ADD - BGN
    private final static String TAG = MainActivity.class.getSimpleName();
    // [2018.01.12] ADD - END

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // [2018.01.12] ADD - BGN
    // 「[Android] データを保存し Android Studio で確認 SharedPreferences | nyanのアプリ開発」
    // <https://akira-watson.com/android/sharedpreferences.html>
    SharedPreferences dataStore = getSharedPreferences("DataStore", MODE_PRIVATE);
    String DeviceTokenArray = dataStore.getString("DeviceToken", "");
    Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | DeviceTokenArray: " + DeviceTokenArray);
    String EndpointArnArray = dataStore.getString("EndpointArn", "");
    Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | EndpointArnArray: " + EndpointArnArray);
    // [2018.01.12] ADD - END
    }
    }



    10.アプリを実行し、Amazon SNS からの通知を確認する。


    1. こちらのサイト ( Android に Firebase Cloud Messaging クライアント アプリを設定する  |  Firebase ) にも記載がありますが、FCM の登録トークンを改めて取得したい場合は、アプリをアンインストールしたりすればいい様なので、とりあえず一度アプリをアンインストールします。アプリのアンインストールは、ググれば出て来ると思います。



    2. Android Studio からデバッグモードで RUN 実行します。



      • 10-01.png






    3. Android Studio の Logcat に登録トークンがログ出力されます。



      • 10-02.png






    4. Amazon SNS のプラットフォームアプリケーションの詳細画面を開き、登録トークンからエンドポイントが作成されたか確認します。



      • 10-03.png






    5. 作成されたエンドポイントを選択し、「エンドポイントの発行」ボタンを押下します。



      • 10-04.png






    6. 「メッセージを発行」画面が表示されるので、以下の項目を設定し、「メッセージの発行」ボタンを押下します。


      • メッセージ形式 : JSON

      • メッセージ



        • { "GCM": "{ \"data\": { \"message\": \"Test\" } }" }







      • 10-05.png






    7. 実機に通知が届いたか確認します。


      • 10-06.jpg




    99.ハマりポイント


  • 通知の確認のところでも確認しましたが、登録トークンがどうやったら改めて取得できるのかよくわからなく、ちょっと戸惑いました。




  • 今回、Firebase プロジェクトのアプリの登録を手動で行いましたが、以下のサイトを見ると Firebase Assistant を使用して登録する手順を推奨している様にも思いました。



  • 後は単純に Android 開発自体ほぼ初めてだったので、Android Studio の使い方からよくわからなかったので、ちょっと苦労しました。




  • っていうか、Android も触ったことなかったので、アプリのアンインストールのやり方とか、通知の確認の仕方とか、逐一わからなくて苦労しました。


XX.まとめ

コードにもコメントで記載していますが、今回も多くのサイトに助けられました。

特に、こちらのサイトはお世話になりました。

っていうか、

ほぼ丸コピに近い様な…。

本当にありがとうございました。

他にも、

* [Android]プッシュ通知実装[Firebase][FCM] – チェンぴログ

* マニフェストの設定

* [Android] データを保存し Android Studio で確認 SharedPreferences | nyanのアプリ開発

* 【Android】NotificationからIntentでデータを渡してアプリ起動するときのIntent.setFlagsメモ - Qiita

など、

参考にさせて頂きました。

ありがとうございました。

次は、iOS だな…。