Galapagos Tech Blog

株式会社ガラパゴスのメンバーによる技術ブログです。

【Android】Notification Channelのお話

こんにちは。Androidエンジニア(たまにiOSエンジニア)のほかりです。 今回は今更ながらAndroid 8から追加されたNotificationChannelについてお話しようと思います。

背景

ご存知の方も多いかと思いますが、

  • 新規アプリは2018/08から
  • 既存アプリは2018/11から

targetSdkVersionを26以上(2018/06時点では27が最新)にしないとGooglePlayにアップロードできなくなります。

※ 今後も新しいOSバージョンが出るたびにこの要求されるSDKバージョンは上がっていきます。

ソースはこちら

まぁこのこと自体はtargetSdkVersionを26以上にしてビルドすれば良いのですが、現在わかっていることで一つ問題になることがあります。 すでにタイトルでネタバレしているのですが、Notificationのことです。

Android 8 (Oreo)からNotificationにChannelという新たな概念が登場しました。 targetSdkVersionが26未満の場合は特に関係なかったのですが、26以上にする場合NotificationChannelを少なくとも一つは実装する必要があります。 なのでNotificationChannelの実装方法などを紹介していきます!!

ユーザー視点からの変更点

Android端末の「設定」→「アプリ」→「通知」で表示される項目が変わりました。 Android8端末ならアプリのラウンチャーアイコン長押しからアプリ設定に飛んでもいいですね。

ではどういう風に見た目が変わるのかをAndroid 7 と 8で比べてみます。 通知確認用に作ったアプリの設定画面でみてみましょう!

Android 7までの通知設定画面

f:id:glpgsinc:20180605160925p:plain

通知を表示するのか・表示しないのかという極端な設定項目しかありません。

Android 8からの通知設定画面

f:id:glpgsinc:20180605160918p:plain

カテゴリというグループで「お知らせ」と「重要」というチャンネルが追加されていて、 チャンネル毎に通知を表示するのか・表示しないのか設定することができるようになりました。

※ チャンネル名はアプリ側で実装しています。

グループのカスタマイズ

実装次第では「カテゴリ」となっていたグループを別の名前に変更することができます。 未指定だと「カテゴリ」になります。

f:id:glpgsinc:20180605160928p:plain

実装

では違いがわかったところで、いよいよ実装していきます。

「グループの作成」、「チャンネルの作成」、「通知」をする機能を持ったNotificationHelperというクラスを作ってみました。 最後に全体像をお見せしますが、初めは説明も兼ねて細かくコードを載せていきます。 言い忘れていましたがKotlinで実装しています。

stringを定義

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="notification_group_push">Push通知</string>
    <string name="notification_group_app">アプリ内通知</string>

    <string name="notification_channel_news">お知らせ</string>
    <string name="notification_channel_important">重要</string>
</resources>

グループとチャンネルの列挙型を定義

    /**
     * グループ.
     */
    enum class Group(val resId: Int) {
        Push(R.string.notification_group_push),
        App(R.string.notification_group_app)
    }

    /**
     * 通知チャンネル.
     */
    enum class Channel(
            val resId: Int,
            val importance: Int,
            val color: Int,
            val visibility: Int,
            val group: Group
    ) {
        News(
                R.string.notification_channel_news,
                NotificationManager.IMPORTANCE_MIN,
                Color.GREEN,
                Notification.VISIBILITY_PRIVATE,
                Group.Push
        ),
        Important(
                R.string.notification_channel_important,
                NotificationManager.IMPORTANCE_HIGH,
                Color.BLUE,
                Notification.VISIBILITY_PRIVATE,
                Group.App
        )
    }

グループとチャンネルを作成

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        // グループ生成
        val createGroup: (Group) -> NotificationChannelGroup =
                fun(group: Group) = NotificationChannelGroup(
                        getString(group.resId),  // グループID
                        getString(group.resId))  // グループ名
        manager.createNotificationChannelGroups(arrayListOf(createGroup(Group.Push), createGroup(Group.App)))

        // チャンネル生成
        val createChannel: (Channel) -> NotificationChannel =
                fun(channel: Channel) = NotificationChannel(
                        getString(channel.resId), // チャンネルID
                        getString(channel.resId), // チャンネル名
                        channel.importance) // 優先度
                        .apply { group = getString(channel.group.resId) } // グループと紐付け
            manager.createNotificationChannels(arrayListOf(createChannel(Channel.News), createChannel(Channel.Important)))
        }
    }

初めにバージョンをチェックしているのは古いOSバージョンで通知チャンネルの実装を行うと落ちるからです。 ※ ちょっと面倒くさがってIDと名前を同じにしてしまっています。

通知の発行処理

    fun post(channel: Channel) {
        val pending = TaskStackBuilder.create(this)
                .addNextIntent(Intent(this, MainActivity::class.java))
                .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)

        val notificationBuilder = NotificationCompat.Builder(this, getString(channel.resId)) // チャンネル指定
                .setGroup(getString(channel.group.resId)) // グループ指定
                .setColor(channel.color)
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .setStyle(NotificationCompat.BigTextStyle()
                        .setBigContentTitle("Title")
                        .bigText("Message"))
                .setAutoCancel(true)
                .setVisibility(channel.visibility)
                .setContentIntent(pending)

        manager.notify(notifyId.incrementAndGet(), notificationBuilder.build())
    }

ここの細かいところは割とテキトーに書いてしまいましたが、注目していただきたいところはコメントしてあるところです。 - NotificationCompat.Builderのインスタンス生成時にチャンネルIDを指定します。 - その後setGroupでグループIDを指定します。

あらかじめ作っておいたグループ・チャンネルを指定しないと通知が表示されませんのでご注意を!

ソースコード全体

/**
 * Notification周りのヘルパークラス.
 */
internal class NotificationHelper(context: Context?): ContextWrapper(context) {

    /**
     * グループ.
     */
    enum class Group(val resId: Int) {
        Push(R.string.notification_group_push),
        App(R.string.notification_group_app)
    }

    /**
     * 通知チャンネル.
     */
    enum class Channel(
            val resId: Int,
            val importance: Int,
            val color: Int,
            val visibility: Int,
            val group: Group
    ) {
        News(
                R.string.notification_channel_news,
                NotificationManager.IMPORTANCE_MIN,
                Color.GREEN,
                Notification.VISIBILITY_PRIVATE,
                Group.Push
        ),
        Important(
                R.string.notification_channel_important,
                NotificationManager.IMPORTANCE_HIGH,
                Color.BLUE,
                Notification.VISIBILITY_PRIVATE,
                Group.App
        )
    }

    companion object {
        private val notifyId = AtomicInteger(0)
    }

    private val manager: NotificationManager by lazy {
        getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    }

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            // グループ生成
            val createGroup: (Group) -> NotificationChannelGroup =
                    fun(group: Group) = NotificationChannelGroup(getString(group.resId), getString(group.resId))
            manager.createNotificationChannelGroups(arrayListOf(createGroup(Group.Push), createGroup(Group.App)))

            // チャンネル生成
            val createChannel: (Channel) -> NotificationChannel =
                    fun(channel: Channel) = NotificationChannel(
                            getString(channel.resId),
                            getString(channel.resId),
                            channel.importance).apply {
                        group = getString(channel.group.resId)
                    }
            manager.createNotificationChannels(arrayListOf(createChannel(Channel.News), createChannel(Channel.Important)))
        }
    }

    /**
     * Notificationをpostします.
     *
     * @param channel: Channel
     */
    fun post(channel: Channel) {
        val pending = TaskStackBuilder.create(this)
                .addNextIntent(Intent(this, MainActivity::class.java))
                .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)

        val notificationBuilder = NotificationCompat.Builder(this, getString(channel.resId)) // チャンネル指定
                .setGroup(getString(channel.group.resId)) // グループ指定
                .setColor(channel.color)
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .setStyle(NotificationCompat.BigTextStyle()
                        .setBigContentTitle("Title")
                        .bigText("Message"))
                .setAutoCancel(true)
                .setVisibility(channel.visibility)
                .setContentIntent(pending)

        manager.notify(notifyId.incrementAndGet(), notificationBuilder.build())
    }
}

注意事項

ここまで頑張って実装してもうまく動作しない場合というのがあります。 それはFCMの通知メッセージをアプリがバックグラウンドで動作中の時に受信した場合です。

Firebaseの公式ドキュメントにも記載されていますが、 通知メッセージの場合は通知トレイに直接通知されてしまうので、アプリ側でハンドリングすることができません。

その場合、一部の設定項目をAndroidManifestファイルに定義することができます。 定義できる項目は以下の通りです。

  • アイコン
  • アイコンの背景色とアプリ名の文字色
  • デフォルトチャンネル(一つのみ)

具体的な定義の仕方は以下の通りです。

<!--アイコン-->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_stat_ic_notification" />

<!--アイコンの背景色とアプリ名の文字色-->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/colorAccent" />

<!--デフォルトチャンネル-->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/default_notification_channel_id"/>

上記の通り、設定できる項目がかなり限られてしまいグループを指定することができません。 またデフォルトチャンネルの指定を忘れても通知は表示されますが、勝手に変な名前のチャンネル名が登録されます。

まとめ

Android 8以上におけるNotification Channelの紹介 & 実装方法の説明をしました。 これから慌ててtargetSdkVersionを26以上に上げるよって方々の参考になると嬉しいです。 それでは良きAndroidライフを!