[Android] Notification のチャンネル設計と実装パターン — Android 15 対応版

Androidの通知、なんとなくコピペで動かしていませんか?Android 8.0で導入された通知チャンネルは、今やすべての通知に必須の仕組みです。そしてAndroid 13からは通知の権限リクエストも必要になり、Android 15ではさらに新しい制限も加わっています。

この記事では、通知チャンネルの設計の考え方から、権限リクエスト、グループ化、カスタム通知まで、実装パターンをまとめて紹介していきます。

スポンサーリンク

前提条件

  • Android Studio(最新安定版推奨)
  • compileSdk 35 / targetSdk 35
  • androidx.core:core-ktx:1.15.0 以上
  • androidx.activity:activity-ktx:1.9.0 以上(権限リクエスト用)

通知チャンネルとは

通知チャンネルは、Android 8.0(API 26)で導入された通知のカテゴリ分けの仕組みです。アプリの通知を「種類」ごとにグループ化して、ユーザーが種類ごとに通知のON/OFFや表示方法をカスタマイズできるようにするためのものです。

たとえばメッセージアプリなら「メッセージ通知」「友達リクエスト通知」「お知らせ通知」のように分けられます。ユーザーは「お知らせ通知はいらないけどメッセージは受け取りたい」みたいな設定ができるわけですね。

API 26以降、チャンネルを指定しない通知は表示されません。なので対応は必須です。

チャンネルの設計方針

実装に入る前に、チャンネルをどう設計するかを考えましょう。ここが一番大事なポイントです。

チャンネルは「ユーザーが制御したい単位」で分ける

チャンネルを分けるかどうかの判断基準は、「ユーザーがこの通知だけOFFにしたいと思うか?」です。

  • ✅ 分けるべき例:「チャットメッセージ」と「キャンペーンのお知らせ」
  • ❌ 分けすぎの例:「田中さんからのメッセージ」と「鈴木さんからのメッセージ」

チャンネルは多すぎるとユーザーが管理しきれなくなります。3〜5個くらいが目安です。

重要度(Importance)の選び方

チャンネルの重要度は、通知がどのように表示されるかを決めます。

  • IMPORTANCE_HIGH — ヘッドアップ通知(画面上部にポップアップ)+ 音。緊急性の高いもの向け
  • IMPORTANCE_DEFAULT — 音は鳴るがポップアップなし。一般的な通知はこれ
  • IMPORTANCE_LOW — 音なし、ステータスバーにアイコンだけ。補足情報向け
  • IMPORTANCE_MIN — ステータスバーにも表示されない。ほぼ見えない

注意点として、一度作成したチャンネルの重要度はアプリ側から変更できません。ユーザーだけが変更できます。なので最初の設計が重要です。もし重要度を変えたい場合は、新しいチャンネルIDで作り直す必要があります。

基本実装

チャンネルの作成

チャンネルの作成は、ApplicationクラスのonCreateか、Activityの初期化時に行うのが一般的です。何度呼んでも同じチャンネルIDなら上書きされないので、毎回呼んで大丈夫です。

import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
import android.content.Context
 
object NotificationChannels {
 
    // チャンネルIDは定数で管理する
    const val CHANNEL_MESSAGE = "channel_message"
    const val CHANNEL_PROMOTION = "channel_promotion"
    const val CHANNEL_SYSTEM = "channel_system"
 
    fun createAll(context: Context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
 
        val manager = context.getSystemService(
            Context.NOTIFICATION_SERVICE
        ) as NotificationManager
 
        val channels = listOf(
            NotificationChannel(
                CHANNEL_MESSAGE,
                "メッセージ",
                NotificationManager.IMPORTANCE_HIGH
            ).apply {
                description = "新着メッセージの通知"
            },
            NotificationChannel(
                CHANNEL_PROMOTION,
                "お知らせ・キャンペーン",
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                description = "キャンペーンやお知らせの通知"
            },
            NotificationChannel(
                CHANNEL_SYSTEM,
                "システム通知",
                NotificationManager.IMPORTANCE_LOW
            ).apply {
                description = "アップデートやメンテナンス情報"
            }
        )
 
        manager.createNotificationChannels(channels)
    }
}

Applicationクラスで呼び出します。

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        NotificationChannels.createAll(this)
    }
}

通知の発行

チャンネルを作ったら、通知を発行するときにチャンネルIDを指定します。

import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
 
fun sendMessageNotification(context: Context, title: String, body: String) {
    val notification = NotificationCompat.Builder(
        context,
        NotificationChannels.CHANNEL_MESSAGE // チャンネルIDを指定
    )
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle(title)
        .setContentText(body)
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setAutoCancel(true)
        .build()
 
    NotificationManagerCompat.from(context).notify(
        System.currentTimeMillis().toInt(),
        notification
    )
}

NotificationCompat.BuilderのコンストラクタにチャンネルIDを渡すのがポイントです。API 26未満ではチャンネルIDは無視されるので、互換性は気にしなくてOKです。

通知権限のリクエスト(Android 13以降)

Android 13(API 33)からは、通知を表示するためにPOST_NOTIFICATIONS権限のリクエストが必要です。この権限がないと、通知がまったく表示されません。

マニフェストの宣言

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    ...
</manifest>

権限リクエストの実装

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
 
class MainActivity : AppCompatActivity() {
 
    private val notificationPermissionLauncher =
        registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { isGranted ->
            if (isGranted) {
                // 権限が許可された
                Log.d("Permission", "通知権限が許可されました")
            } else {
                // 権限が拒否された
                Log.d("Permission", "通知権限が拒否されました")
            }
        }
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        requestNotificationPermission()
    }
 
    private fun requestNotificationPermission() {
        // Android 13未満では不要
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
 
        when {
            ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.POST_NOTIFICATIONS
            ) == PackageManager.PERMISSION_GRANTED -> {
                // すでに許可済み
            }
            shouldShowRequestPermissionRationale(
                Manifest.permission.POST_NOTIFICATIONS
            ) -> {
                // 一度拒否されている場合は理由を説明してから再リクエスト
                showPermissionRationale()
            }
            else -> {
                // 初回リクエスト
                notificationPermissionLauncher.launch(
                    Manifest.permission.POST_NOTIFICATIONS
                )
            }
        }
    }
 
    private fun showPermissionRationale() {
        // ダイアログなどで理由を説明してから
        // notificationPermissionLauncher.launch(...) を呼ぶ
    }
}

権限リクエストのタイミングは「初回起動時に即座に出す」よりも、「通知が必要になった文脈で出す」ほうが許可率が上がると言われています。たとえば「メッセージの通知を受け取りますか?」のような画面を挟むのがベターです。

チャンネルグループで整理する

チャンネルが増えてきたら、チャンネルグループで整理すると、ユーザーの設定画面がすっきりします。

import android.app.NotificationChannelGroup
 
fun createChannelGroups(context: Context) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
 
    val manager = context.getSystemService(
        Context.NOTIFICATION_SERVICE
    ) as NotificationManager
 
    // グループを作成
    val groups = listOf(
        NotificationChannelGroup("group_communication", "コミュニケーション"),
        NotificationChannelGroup("group_info", "お知らせ")
    )
    manager.createNotificationChannelGroups(groups)
 
    // チャンネルにグループを設定
    val messageChannel = NotificationChannel(
        "channel_message",
        "メッセージ",
        NotificationManager.IMPORTANCE_HIGH
    ).apply {
        group = "group_communication"  // グループに紐づける
        description = "新着メッセージの通知"
    }
 
    val promotionChannel = NotificationChannel(
        "channel_promotion",
        "お知らせ・キャンペーン",
        NotificationManager.IMPORTANCE_DEFAULT
    ).apply {
        group = "group_info"
        description = "キャンペーンやお知らせの通知"
    }
 
    manager.createNotificationChannels(
        listOf(messageChannel, promotionChannel)
    )
}

ユーザーの設定画面では、「コミュニケーション」グループの下に「メッセージ」チャンネル、「お知らせ」グループの下に「お知らせ・キャンペーン」チャンネル、というように階層化されて表示されます。

チャンネルの状態を確認する

ユーザーがチャンネルをOFFにしている場合、アプリ側でそれを検知して案内を出すこともできます。

/**
 * 指定したチャンネルが有効かどうかを確認する
 */
fun isChannelEnabled(context: Context, channelId: String): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return true
 
    val manager = context.getSystemService(
        Context.NOTIFICATION_SERVICE
    ) as NotificationManager
 
    val channel = manager.getNotificationChannel(channelId)
    return channel?.importance != NotificationManager.IMPORTANCE_NONE
}
 
/**
 * 通知全体が有効かどうかを確認する
 */
fun areNotificationsEnabled(context: Context): Boolean {
    return NotificationManagerCompat.from(context)
        .areNotificationsEnabled()
}
// 使い方
if (!areNotificationsEnabled(this)) {
    // 「通知がOFFになっています」の案内を表示
} else if (!isChannelEnabled(this, NotificationChannels.CHANNEL_MESSAGE)) {
    // 「メッセージ通知がOFFになっています」の案内を表示
}

チャンネルの削除

不要になったチャンネルは削除できます。ただし、削除したチャンネルIDで再度作成すると、ユーザーが以前設定した内容は復元されません。

fun deleteChannel(context: Context, channelId: String) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
 
    val manager = context.getSystemService(
        Context.NOTIFICATION_SERVICE
    ) as NotificationManager
    manager.deleteNotificationChannel(channelId)
}

アプリのアップデートでチャンネルの構成を変更する場合は、古いチャンネルを削除してから新しいチャンネルを作成する流れになります。

Android 15 での変更点

Android 15(API 35)では通知に関していくつか変更が入っています。

通知のクールダウン制限

短時間に大量の通知を送ると、同じアプリからの通知が自動的にまとめられるようになりました。ユーザーに対する通知疲れを防ぐための機能です。

対策としては、頻繁に通知を更新する場合は同じ通知IDでnotifyを呼んで上書きするか、setOnlyAlertOnce(true)を設定して音やバイブレーションの重複を防ぐのがよいです。

val notification = NotificationCompat.Builder(context, channelId)
    .setSmallIcon(R.drawable.ic_notification)
    .setContentTitle("ダウンロード中")
    .setContentText("50% 完了")
    .setOnlyAlertOnce(true)  // 更新時に音を鳴らさない
    .setProgress(100, 50, false)
    .build()
 
// 同じIDで上書きすることで通知を更新
NotificationManagerCompat.from(context).notify(
    DOWNLOAD_NOTIFICATION_ID,  // 固定のID
    notification
)

フォアグラウンドサービスの通知

Android 15では、フォアグラウンドサービスの種類によって追加の制限が入っています。foregroundServiceTypeをマニフェストで正しく宣言していないとクラッシュする可能性があるので、フォアグラウンドサービスを使っている場合は確認しておきましょう。

よくあるトラブルと対処法

通知が表示されない

チェックポイントは以下の順番で確認してみてください。

  1. Android 13以降ならPOST_NOTIFICATIONS権限が許可されているか
  2. 通知チャンネルが作成されているか
  3. チャンネルの重要度がIMPORTANCE_NONEになっていないか
  4. ユーザーがアプリの通知を設定でOFFにしていないか
  5. setSmallIconが設定されているか(設定しないとクラッシュ)

チャンネルの重要度を変更したい

前述の通り、一度作成したチャンネルの重要度はアプリ側からは変更できません。どうしても変えたい場合は、新しいチャンネルIDで作り直してください。

// 古いチャンネルを削除
manager.deleteNotificationChannel("channel_message_v1")
 
// 新しいチャンネルを作成
val newChannel = NotificationChannel(
    "channel_message_v2",  // 新しいID
    "メッセージ",
    NotificationManager.IMPORTANCE_DEFAULT  // 変更後の重要度
)
manager.createNotificationChannel(newChannel)

通知音が鳴らない / 鳴りすぎる

チャンネルの重要度がIMPORTANCE_LOW以下だと音が鳴りません。逆に頻繁な通知で音が鳴りすぎる場合はsetOnlyAlertOnce(true)を使いましょう。

まとめ

通知チャンネルの設計と実装のポイントをおさらいします。

  1. チャンネルは「ユーザーが制御したい単位」で3〜5個に分ける
  2. 重要度は最初にしっかり決める(後からアプリ側で変更できない)
  3. Android 13以降はPOST_NOTIFICATIONS権限のリクエストが必須
  4. チャンネルが増えたらグループで整理する
  5. Android 15のクールダウン制限に注意して、頻繁な通知は同一IDで更新する

通知はユーザー体験に直結する機能なので、雑に実装すると「通知うるさい→アプリ削除」になりかねません。チャンネル設計をちゃんとやっておくだけで、ユーザーの満足度がけっこう変わりますよ。

コメント

タイトルとURLをコピーしました