LoginSignup
15
9

More than 3 years have passed since last update.

AWS SNSからAndroidにプッシュ通知するためにやったこと、ハマったこと

Last updated at Posted at 2020-12-19

今関わっているプロジェクトのバックエンドはAWSです
そんなAWSからユーザが操作するAndroidにプッシュ通知がしたいと思いました
しかしFirebase(FCM)が便利すぎて逆にハマってしまったのでメモしておきます

プッシュ通知なんてどうせみんなオフするからいらん!という漢気も私は好きです

参考にした素晴らしい記事様はこちら
Android から Amazon SNS を使ってみる - Qiita
Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita

目指すもの

スクリーンショット 2020-12-18 9.40.06.png
まずは最小限、プッシュ通知をすることだけを目標にします
細かい設定や他の人の方が詳しい!

用意するもの

  • 適当なAndroidプロジェクト
  • Firebaseアカウント ※無料プランでOK
  • AWSアカウント

FirebaseからAndroidに通知するまで

適当にAndroidプロジェクトを作成する

スクリーンショット 2020-12-18 9.00.37.png
パッケージ名は後ほど必要です

Firebaseにアカウントを作成、適当なプロジェクトを作る

スクリーンショット 2020-12-18 8.53.42.png
「プロジェクトを追加」から適当な名前でプロジェクトを作成します
ちなみにFCM(Firebase Cloud Messaging)はだけなら無料プランでもよさそうです。素敵。

Firebase Cloud MessagingにAndroidアプリを登録する

公式がとても丁寧に案内してくれるのでそれに従います

Androidパッケージを登録する

スクリーンショット 2020-12-18 9.09.23.png
Androidパッケージ名に、先ほど作成したAndroidプロジェクトのパッケージ名を入力します
署名はよりセキュリティを高めるなら入れた方がいいんでしょうね。今回は省略!

appディレクトリにJSONファイルを追加

スクリーンショット 2020-12-18 9.14.08.png
firebaseからダウンロードしたgoogle-services.jsonをapp以下に追加します

Androidプロジェクトに依存関係を追加する

/build.gradle
buildscript {
    ext.kotlin_version = "1.4.21"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.google.gms:google-services:4.3.4"  // <<<<< added
        ~
/app/build.gradle
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.google.gms.google-services' // <<< added
}
// ~略~
dependencies {
    // ~略~
    implementation platform('com.google.firebase:firebase-bom:26.1.1')  // <<< added
    implementation 'com.google.firebase:firebase-analytics-ktx'         // <<< added
    implementation 'com.google.firebase:firebase-messaging-ktx'         // <<< added
}

FCMにAndroidデバイスを登録する

通知用のデバイストークンを取得する

プッシュ通知するためには、Androidデバイスを一意に特定するためのトークンが必要になります
トークンの発行はAndroidプロジェクトに組み込まれたFirebaseがやってくれます
例えば下記のようにtokenをログ出力するようにしてみます

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ↓added
        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener {
            if (!it.isSuccessful) {
                Log.e("Firebase", "Fetching FCM registration token failed", it.exception)
                return@OnCompleteListener
            }

            val token = it.result
            Log.d("Firebase Token", token.toString())
        })

    }
}

この状態でアプリを動かすと、ログにトークンが吐かれますので、これをメモしておきます

D/Firebase Token: c3ilo ~~~

FCMからプッシュ通知を送信してみる

スクリーンショット 2020-12-18 9.25.20.png
FCMのダッシュボードから「通知の作成」へ行き、早速メッセージを送ってみます

スクリーンショット 2020-12-18 9.38.33.png
FCM登録トークンに、先ほどAndroidから生成されたトークンを登録して、テストメッセージを送信します

スクリーンショット 2020-12-18 9.40.06.png
すると無事届きました!簡単ですねー
さあFirebase > Android間の連携ができたので、次はAWS SNSとの連携だ

注意:アプリがバックグラウンドでないと通知が届かない

デフォルトの状態だと、アプリがフォアグラウンド中(前面表示)には届かないようです
「君もうアプリ開いてるから通知せんでええやろ?」ってことでしょうか

AWS SNSからAndroidに通知するまで

AWS SNSとFirebaseを紐付ける

AWS公式が非常に親切に手順を説明してくれていますので、基本はこの通りに

Firebaseのサーバーキーを取得する

スクリーンショット 2020-12-18 10.54.14.png
プロジェクトの設定→「Cloud Messaging」からサーバーキーを取得します

FirebaseサーバーキーをAWS SNSに登録する

image.png

  • AWSコンソールからSimple Notification Serviceを開く
  • 左バーの「プッシュ通知」からモバイルプッシュ通知画面を開き、「プラットフォームアプリケーションの作成」
  • プッシュ通知プラットフォームは「FCM」
  • APIキーに先ほど取得したFirebaseサーバーキーを入力する
  • そのほかは空欄でOK

アプリケーションエンドポイントを作成する

image (1).png

  • デバイストークンにAndroidから取得したトークンを入力する
  • そのほかは空欄

いざSNSメッセージ送信!しかし全く反応なし

image (2).png

なにせFirebaseからAndroidへのプッシュ通知は成功しているので、AWS SNSとFirebase間の紐付けがうまくいってない?
と思ってサーバーキー周りをめちゃくちゃ確認しましたが、原因は別のところにありました
それでは、トラブルシュート編に続きます

AWS SNSからAndroidに通知するために追加でやるべきこと

送信するメッセージにはフォーマットがある

AWS SNSをちょっと知っている人には当たり前だと思いますが...
Firebaseで受け取れるようなメッセージを送るにはフォーマットがあります
しかもAWS側がテンプレートを用意してくれています(カスタムペイロード)

image (3).png

しかし、これでもAndroidにはプッシュ通知されませんでした

Android側でレシーバを実装する必要がある

Firebase公式にはしっかりとこんな説明があります

フォアグラウンド アプリで通知メッセージまたはデータ メッセージを受信する場合は、onMessageReceived コールバックを処理するコードを記述する必要があります。

FirebaseからAndroidへの通知は成功していたので、動くと思っていましたが甘かったようです

Androidマニフェストに通知受信用のサービスを追加

マニフェストファイルのタグ以下にサービスを追加します

AndroidManifest.xml
<service
     android:name=".MyFirebaseMessagingService"
     android:exported="false">
     <intent-filter>
          <action android:name="com.google.firebase.MESSAGING_EVENT" />
     </intent-filter>
</service>
MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        Log.d("onMessageReceived", "From: ${message.from}")
        if (message.data.isNotEmpty()) {
            Log.d("onMessageReceived", "payload: ${message.data}")
        }
    }
}

しかしこれでもプッシュ通知が飛んでこないなーと、何気なくログを見てたら
log_image.png
来てたァ!!

カスタム通知を作成する

公式のFCMメッセージについてを参照する限り、AWS SNSからのメッセージはデータメッセージとして扱われていて、
その場合はクライアント側で処理をする必要があるとのこと
(SNSから送信するメッセージにdataって付けてますしね)

MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    val CHANNEL_ID = "MyNotification"

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        Log.d("onMessageReceived", "From: ${message.from}")
        if (message.data.isNotEmpty()) {
            Log.d("onMessageReceived", "payload: ${message.data}")
        }

        createNotificationChannel()
        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("通知がきたで")
            .setContentText("内容は「${message.data}」やで")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        with(NotificationManagerCompat.from(this)) {
            notify(UUID.randomUUID().hashCode(), notificationBuilder.build())
        }
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "MyNotificationChannelName"
            val descriptionText = "MyNotificationChannelDescription"
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
                description = descriptionText
            }
            // Register the channel with the system
            val notificationManager: NotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}

正直ここの実装はかなりみようみまねで適当なので、ご使用の際は十分に注意してください
ちなみにこのとき、アイコンなど一部の設定を省略するとランタイムでクラッシュしたりします

無事プッシュ通知ができました! :tada: :tada:

スクリーンショット 2020-12-18 12.01.21.png

ということで無事通知が動きました!
まだ通知周りは詰められてないところもありますが、なんとかなりましたね

15
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9