今関わっているプロジェクトのバックエンドはAWSです
そんなAWSからユーザが操作するAndroidにプッシュ通知がしたいと思いました
しかしFirebase(FCM)が便利すぎて逆にハマってしまったのでメモしておきます
プッシュ通知なんてどうせみんなオフするからいらん!という漢気も私は好きです
参考にした素晴らしい記事様はこちら
Android から Amazon SNS を使ってみる - Qiita
Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita
目指すもの
まずは最小限、プッシュ通知をすることだけを目標にします
細かい設定や他の人の方が詳しい!
用意するもの
- 適当なAndroidプロジェクト
- Firebaseアカウント ※無料プランでOK
- AWSアカウント
FirebaseからAndroidに通知するまで
適当にAndroidプロジェクトを作成する
Firebaseにアカウントを作成、適当なプロジェクトを作る
「プロジェクトを追加」から適当な名前でプロジェクトを作成します
ちなみにFCM(Firebase Cloud Messaging)はだけなら無料プランでもよさそうです。素敵。
Firebase Cloud MessagingにAndroidアプリを登録する
公式がとても丁寧に案内してくれるのでそれに従います
Androidパッケージを登録する
Androidパッケージ名に、先ほど作成したAndroidプロジェクトのパッケージ名を入力します
署名はよりセキュリティを高めるなら入れた方がいいんでしょうね。今回は省略!
appディレクトリにJSONファイルを追加
firebaseからダウンロードしたgoogle-services.jsonをapp以下に追加します
Androidプロジェクトに依存関係を追加する
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
~
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をログ出力するようにしてみます
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からプッシュ通知を送信してみる
FCMのダッシュボードから「通知の作成」へ行き、早速メッセージを送ってみます
FCM登録トークンに、先ほどAndroidから生成されたトークンを登録して、テストメッセージを送信します
すると無事届きました!簡単ですねー
さあFirebase > Android間の連携ができたので、次はAWS SNSとの連携だ
注意:アプリがバックグラウンドでないと通知が届かない
デフォルトの状態だと、アプリがフォアグラウンド中(前面表示)には届かないようです
「君もうアプリ開いてるから通知せんでええやろ?」ってことでしょうか
AWS SNSからAndroidに通知するまで
AWS SNSとFirebaseを紐付ける
AWS公式が非常に親切に手順を説明してくれていますので、基本はこの通りに
Firebaseのサーバーキーを取得する
プロジェクトの設定→「Cloud Messaging」からサーバーキーを取得します
FirebaseサーバーキーをAWS SNSに登録する
- AWSコンソールからSimple Notification Serviceを開く
- 左バーの「プッシュ通知」からモバイルプッシュ通知画面を開き、「プラットフォームアプリケーションの作成」
- プッシュ通知プラットフォームは「FCM」
- APIキーに先ほど取得したFirebaseサーバーキーを入力する
- そのほかは空欄でOK
アプリケーションエンドポイントを作成する
- デバイストークンにAndroidから取得したトークンを入力する
- そのほかは空欄
いざSNSメッセージ送信!しかし全く反応なし
なにせFirebaseからAndroidへのプッシュ通知は成功しているので、AWS SNSとFirebase間の紐付けがうまくいってない?
と思ってサーバーキー周りをめちゃくちゃ確認しましたが、原因は別のところにありました
それでは、トラブルシュート編に続きます
AWS SNSからAndroidに通知するために追加でやるべきこと
送信するメッセージにはフォーマットがある
AWS SNSをちょっと知っている人には当たり前だと思いますが...
Firebaseで受け取れるようなメッセージを送るにはフォーマットがあります
しかもAWS側がテンプレートを用意してくれています(カスタムペイロード)
しかし、これでもAndroidにはプッシュ通知されませんでした
Android側でレシーバを実装する必要がある
Firebase公式にはしっかりとこんな説明があります
フォアグラウンド アプリで通知メッセージまたはデータ メッセージを受信する場合は、onMessageReceived コールバックを処理するコードを記述する必要があります。
FirebaseからAndroidへの通知は成功していたので、動くと思っていましたが甘かったようです
Androidマニフェストに通知受信用のサービスを追加
マニフェストファイルのタグ以下にサービスを追加します
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
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}")
}
}
}
しかしこれでもプッシュ通知が飛んでこないなーと、何気なくログを見てたら
来てたァ!!
カスタム通知を作成する
公式のFCMメッセージについてを参照する限り、AWS SNSからのメッセージはデータメッセージとして扱われていて、
その場合はクライアント側で処理をする必要があるとのこと
(SNSから送信するメッセージにdataって付けてますしね)
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)
}
}
}
正直ここの実装はかなりみようみまねで適当なので、ご使用の際は十分に注意してください
ちなみにこのとき、アイコンなど一部の設定を省略するとランタイムでクラッシュしたりします
無事プッシュ通知ができました!
ということで無事通知が動きました!
まだ通知周りは詰められてないところもありますが、なんとかなりましたね