くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Androidでプッシュ通知(FCM)を使う

今回はAndroidでプッシュ通知を使う方法です。プッシュ通知はFCM(Firebase Cloud Messaging)を使います。

FCMの設定に関しては今回は割愛しますが大まかにいうとFirebaseのプロジェクトを作成してパッケージ名を設定すると、google-service.jsonがダウンロードできるようになっているのでAndroid Studioのプロジェクトに追加します

ここまでを前提としてこれからプッシュ通知を受け取るための実装や受け取った後の実装について記載しようと思います。

SDKの設定

まずはFirebase Cloud Messagingを利用できるようにライブラリの追加を行います。ルートレベルのbuild.gradleに下記設定を追加します

buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.3.10'
    }
}

そしてモジュールレベルのbuild.gradleに下記を追加します(Firebaseのバージョンをbomで管理しています)

dependencies {
    implementation platform('com.google.firebase:firebase-bom:28.3.1')
    implementation 'com.google.firebase:firebase-messaging'
}

サービスクラスの作成

次にFirebaseMessagingServiceを継承したサービスを作成します。これは、バックグラウンド時にアプリで通知を受け取る処理よりも高度なメッセージ処理を行う場合に必要になります。 例えば、フォアグラウンド時のアプリで通知を受け取る、プロジェクトとして独自のペイロードを受信するなどの場合はサービスを継承します。

class PushNotificationService : FirebaseMessagingService() {
    override fun onNewToken(p0: String) {
        super.onNewToken(p0)
    }

    override fun onMessageReceived(p0: RemoteMessage) {
        super.onMessageReceived(p0)
    }
}

サービスクラスの処理について説明は後述します。そして作成したクラスをマニフェストファイルに追加します

<application>
        <service
            android:name=".service.PushNotificationService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
</application>

channelの作成

Android 8.0以降、通知はすべてチャネルに割り当てる必要があります。 チャネルごとに、そのチャネルのすべての通知に適用される表示と音声の動作を設定することができます。 チャネルはユーザーがアプリのどの通知チャネルを実際に表示するか、または不要とするかを決めることができます

class PushNotificationService : FirebaseMessagingService() {
    override fun onNewToken(p0: String) {
        super.onNewToken(p0)
    }

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

        sendNotification(p0)
    }

    private fun sendNotification(remoteMessage: RemoteMessage) {
        // 受け取った後の遷移先の設定
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

        // Android12でクラッシュするのでPendingIntent.FLAG_IMMUTABLEを使う
        val pendingIntent = PendingIntent.getActivity(this, requestCode, intent,
            PendingIntent.FLAG_IMMUTABLE)

        // channelIdを設定(変更可能)
        val channelId = getString(R.string.channel_id)
        // チャネル名を設定(変更可能)
        val channelName = getString(R.string.channel_name)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_launcher)
            .setColor(resources.getColor(R.color.appTransparent, null))
            .setContentTitle(remoteMessage.notification?.title)
            .setContentText(remoteMessage.notification?.body)
            .setShowWhen(true)
            .setWhen(System.currentTimeMillis())
            .setAutoCancel(true)
            .setDefaults(NotificationCompat.DEFAULT_ALL)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setContentIntent(pendingIntent)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Android8以降設定が必要
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
            channel.setShowBadge(true)
            channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            notificationManager.createNotificationChannel(channel)
        }

        // 通知処理(PendingIntent.getActivityで指定したリクエストコードを通知に設定する)
        notificationManager.notify(requestCode, notificationBuilder.build())
    }

    companion object {
        val requestCode = 0
    }
}

少し補足します。override fun onNewToken(p0: String)ではデバイス登録トークンが登録されたときに呼ばれます。override fun onMessageReceived(p0: RemoteMessage)はプッシュ通知が届いた時に呼ばれます

チャネルの設定については公式ドキュメントに細かく記載されています

intentは遷移先になるので、ペイロードのデータによって遷移先を変えたい場合には適時変更してください

もし、自分で設定した覚えのない「その他」(Miscellaneous)というChannelに割り振られていて、スマホで追加したChannelのOn/Offを切り替えても通知が届かない場合にはAndroidManifest.xmlに以下のようにmeta-dataを追加して自分でChannelIdを指定してください

また、通知でアイコンが表示されない場合にはStackOverFlowに方法が載っていたので参考にしてみてください

<application>
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/channel_name"/>
</application>

ペイロードを受け取る

最後にペイロードを受け取る処理についてです

ペイロードが含まれている時はoverride fun onMessageReceived(p0: RemoteMessage)の引数のp0に含まれています

例えば下記のようなペイロードがあるとします

{
  "to":"deviceid","priority":"high",
  "notification":{
    "title": "通知タイトル",
    "body": "通知メッセージ"
  },
  "data":{
    "title": "title",
    "body": "message",
    "type": "type1"
  }
}

dataのtypeを取得する場合には下記のようにして取得します

  if (remoteMessage.data.size > 0) { 
    for( key in remoteMessage.data.keys ) {
      // typeのデータを取得
      if( key == "type" ) {
      }
    }
  }

プッシュ通知はほとんどのアプリで使う機能だと思うので、参考にしてみてください!!

※補足

ペイロードを受け取る際にアプリがフォアグラウンド状態か起動していないかで受け取るクラスが異なるため注意してください。

フォアグラウンドでプッシュ通知をタップした場合にはonMessageReceived(p0: RemoteMessage)のp0で受け取ります。 プッシュ通知をタップしてアプリを起動したりバックグラウンドの場合にはAndroidManifest.xml<action android:name="android.intent.action.MAIN" />の設定のあるクラスで intent.getStringExtra("")などのintentの処理でペイロードを取得できます。 プッシュ通知をタップしてアプリを起動する場合にはFirebaseMessagingService()を継承したクラスは通らないためご注意ください!

参照

firebase.google.com

developers.goalist.co.jp

qiita.com

trueman-developer.blogspot.com