通知を作成する

通知は、アプリが使用されていないときに、アプリ内のイベントに関する短い情報をタイムリーに提供します。このドキュメントでは、さまざまな機能を備えた通知を作成する方法について説明します。Android での通知の表示方法の概要については、通知の概要をご覧ください。通知を使用するサンプルコードについては、GitHub の SociaLite サンプルをご覧ください。

このページのコードでは、AndroidX ライブラリの NotificationCompat API を使用しています。これらの API を使用すると、Android 9(API レベル 28)との互換性を維持しながら、新しいバージョンの Android でのみ利用可能な機能を追加できます。ただし、インライン返信アクションなどの一部の機能は、以前のバージョンでは何も行われません。

基本的な通知を作成する

最も基本的でコンパクトな形式の通知(折りたたみ形式とも呼ばれます)には、アイコン、タイトル、少量のテキスト コンテンツが表示されます。このセクションでは、ユーザーがタップしてアプリのアクティビティを起動できる通知を作成する方法について説明します。

図 1. アイコン、タイトル、テキストを含む通知。

通知の各部分の詳細については、通知の構造をご覧ください。

実行時の権限を宣言する

Android 13(API レベル 33)以降では、アプリから除外対象外(フォアグラウンド サービス(FGS)を含む)通知を送信するための実行時の権限がサポートされています。

アプリのマニフェスト ファイルで宣言する必要がある権限を、次のコード スニペットに示します。

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

実行時の権限について詳しくは、通知の実行時の権限をご覧ください。

通知のコンテンツを設定する

まず、NotificationCompat.Builder オブジェクトを使用して、通知のコンテンツとチャンネルを設定します。次の例は、以下を含む通知を作成する方法を示しています。

  • setSmallIcon() で設定される小さなアイコン。これは、ユーザーに表示される唯一の必須コンテンツです。

  • タイトル。setContentTitle() で設定します。

  • 本文。setContentText() で設定します。

  • 通知の優先度。setPriority() で設定します。優先度によって、Android 7.1 以前での通知の割り込み度が決まります。Android 8.0 以降では、次のセクションで説明するように、代わりにチャンネルの重要度を設定します。

val textTitle = "Title"
val textContent = "Content"
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle(textTitle)
    .setContentText(textContent)
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)

NotificationCompat.Builder コンストラクタには、チャンネル ID を指定する必要があります。これは、Android 8.0(API レベル 26)以降との互換性を確保するために必要ですが、それより前のバージョンでは無視されます。

デフォルトでは、通知のテキスト コンテンツは 1 行に収まるように切り捨てられます。展開可能な通知を作成することで、追加情報を表示できます。

図 2. 折りたたみ形式と展開形式の展開可能な通知。

通知を長くしたい場合は、setStyle() を含むスタイル テンプレートを追加することで、展開可能な通知を有効にできます。たとえば、次のコードは、より大きなテキスト領域を作成します。

val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Much longer text that cannot fit one line...")
    .setStyle(NotificationCompat.BigTextStyle()
        .bigText("Much longer text that cannot fit one line..."))
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)

画像やメディア再生コントロールを追加する方法など、他の大きな通知スタイルの詳細については、展開可能な通知を作成するをご覧ください。

チャネルを作成して重要度を設定する

Android 8.0 以降で通知を配信するには、NotificationChannel のインスタンスを createNotificationChannel() に渡して、アプリの通知チャンネルをシステムに登録します。次のコードは、SDK_INT バージョンの条件によってブロックされます。

fun createNotificationChannel(context: Context) {
    // Create the NotificationChannel, but only on API 26+ because
    // the NotificationChannel class is not in the Support Library.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = context.getString(R.string.channel_name)
        val descriptionText = context.getString(R.string.channel_description)
        val importance = NotificationManager.IMPORTANCE_DEFAULT
        val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
            description = descriptionText
        }
        // Register the channel with the system.
        val notificationManager: NotificationManager =
            context.getSystemService(NotificationManager::class.java) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

Android 8.0 以降では、通知を投稿する前に通知チャンネルを作成する必要があるため、アプリの起動時にこのコードを実行します。既存のチャネルを作成しても何も行われないため、繰り返し呼び出しても安全です。

NotificationChannel コンストラクタには、NotificationManager 定数を使用した重要度レベルが必要です。これにより、ユーザーを中断する方法が決まります。Android 7.1 以前をサポートするには、前述の例のように setPriority() で優先度も設定します。

重要度または優先度を設定する必要がありますが、システムはアラートの動作を保証しません。システムは他の要因に基づいて調整することがあります。また、ユーザーはいつでもチャンネルの重要度をカスタマイズできます。

各レベルの意味について詳しくは、通知の重要度をご覧ください。

通知のタップ アクションを設定する

すべての通知はタップに応答する必要があります。通常は、通知に対応するアプリのアクティビティを開きます。そのためには、PendingIntent オブジェクトで定義されたコンテンツ インテントを指定し、それを setContentIntent() に渡します。

次のスニペットは、ユーザーが通知をタップしたときにアクティビティを開く基本的なインテントを作成する方法を示しています。

// Create an explicit intent for an Activity in your app.
val intent = Intent(context, AlertDetails::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent =
    PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)

val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Hello World!")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    // Set the intent that fires when the user taps the notification.
    .setContentIntent(pendingIntent)
    .setAutoCancel(true)

このコードは setAutoCancel() を呼び出します。これにより、ユーザーが通知をタップすると、自動的に通知が削除されます。

上記の例のインテント フラグは、ユーザーが通知を使用してアプリを開いた後、ユーザーが期待するナビゲーション エクスペリエンスを維持します。開始するアクティビティの種類に応じて、次のいずれかを使用します。

  • 通知への応答専用のアクティビティ。通常のアプリの使用中にユーザーがこのアクティビティに移動する理由はないため、このアクティビティは、アプリの既存のタスクとバックスタックに追加されるのではなく、新しいタスクを開始します。これは、前のサンプルで作成したインテントのタイプです。

  • アプリの通常のアプリフロー内に存在するアクティビティ。この場合、アクティビティを開始するとバックスタックが作成されるため、戻るボタンと上へボタンに対するユーザーの期待が維持されます。

通知を表示する

通知を表示するには、NotificationManagerCompat.notify() を呼び出し、通知の一意の ID と NotificationCompat.Builder.build() の結果を渡します。これを次の例に示します。

with(NotificationManagerCompat.from(context)) {
    if (ActivityCompat.checkSelfPermission(
            context,
            Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        // TODO: Consider calling ActivityCompat#requestPermissions here
        // to request the missing permissions, and then overriding
        // public fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
        //                                        grantResults: IntArray)
        // to handle the case where the user grants the permission. See the documentation
        // for ActivityCompat#requestPermissions for more details.

        return@with
    }
    // notificationId is a unique int for each notification that you must define.
    notify(notificationId, builder.build())

NotificationManagerCompat.notify() に渡す通知 ID は、通知を更新または削除するときに必要になるため、保存しておきます。

また、Android 13 以降を搭載したデバイスで基本的な通知をテストするには、通知を手動でオンにするか、通知をリクエストするダイアログを作成します。

アクション ボタンを追加する

通知には、ユーザーがすばやく応答できるように、最大 3 つの操作ボタン(リマインダーのスヌーズやテキスト メッセージへの返信など)を表示できます。ただし、これらの操作ボタンは、ユーザーが通知をタップしたときに実行されるアクションと重複してはなりません。

図 3. 1 つの操作ボタンを含む通知。

操作ボタンを追加するには、addAction() メソッドに PendingIntent を渡します。これは、通知のデフォルトのタップ操作を設定するのと似ていますが、アクティビティを起動する代わりに、バックグラウンドでジョブを実行する BroadcastReceiver を開始するなど、他の操作を行うことができます。これにより、すでに開いているアプリが操作によって中断されることはありません。

たとえば、次のコードは、特定のレシーバにブロードキャストを送信する方法を示しています。

val ACTION_SNOOZE = "snooze"
val snoozeIntent = Intent(context, MyBroadcastReceiver::class.java).apply {
    action = ACTION_SNOOZE
    putExtra(EXTRA_NOTIFICATION_ID, 0)
}
val snoozePendingIntent: PendingIntent =
    PendingIntent.getBroadcast(context, 0, snoozeIntent, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Hello World!")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    .setContentIntent(pendingIntent)
    .addAction(R.drawable.snooze, context.getString(R.string.snooze),
        snoozePendingIntent)

バックグラウンド処理を実行する BroadcastReceiver の作成について詳しくは、ブロードキャストの概要をご覧ください。

トラックの一時停止やスキップなど、メディア再生ボタンを含む通知を作成する場合は、メディア コントロールを含む通知を作成する方法をご覧ください。

ダイレクト返信アクションを追加する

Android 7.0(API レベル 24)で導入されたダイレクト返信アクションを使用すると、ユーザーは通知に直接テキストを入力できます。テキストは、アクティビティを開かずにアプリに配信されます。たとえば、ダイレクト返信アクションを使用すると、ユーザーは通知内からテキスト メッセージに返信したり、タスクリストを更新したりできます。

図 4. [返信] ボタンをタップすると、テキスト入力が開きます。

ダイレクト返信アクションは、テキスト入力を開く通知の追加ボタンとして表示されます。ユーザーが入力し終えると、システムは、通知アクションに指定されたインテントにテキスト レスポンスを添付し、そのインテントをアプリに送信します。

返信ボタンを追加する

ダイレクト返信をサポートする通知アクションを作成する手順は次のとおりです。

通知アクションに追加できる RemoteInput.Builder のインスタンスを作成します。このクラスのコンストラクタは、システムがテキスト入力のキーとして使用する文字列を受け入れます。アプリは後でそのキーを使用して入力のテキストを取得します。

// Key for the string that's delivered in the action's intent.
val replyLabel: String = context.resources.getString(R.string.reply_label)
val remoteInput: RemoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).run {
    setLabel(replyLabel)
    build()
}

返信アクションの PendingIntent を作成します。

// Build a PendingIntent for the reply action to trigger.
val replyPendingIntent: PendingIntent =
    PendingIntent.getBroadcast(context,
        conversationId,
        getMessageReplyIntent(conversationId),
        PendingIntent.FLAG_MUTABLE)

addRemoteInput() を使用して、RemoteInput オブジェクトをアクションにアタッチします。

// Create the reply action and add the remote input.
val action: NotificationCompat.Action =
    NotificationCompat.Action.Builder(R.drawable.reply,
        context.getString(R.string.reply_label), replyPendingIntent)
        .addRemoteInput(remoteInput)
        .build()

アクションを通知に適用し、通知を発行します。

// Build the notification and add the action.
val newMessageNotification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_message)
    .setContentTitle(context.getString(R.string.title))
    .setContentText(context.getString(R.string.content))
    .addAction(action)
    .build()

// Issue the notification.
NotificationManagerCompat.from(context).notify(notificationId, newMessageNotification)

図 4 に示すように、ユーザーが通知アクションをトリガーすると、システムはユーザーにレスポンスの入力を求めます。

返信からユーザー入力を取得する

通知の返信 UI からユーザー入力を受け取るには、RemoteInput.getResultsFromIntent() を呼び出し、BroadcastReceiver が受け取った Intent を渡します。

private fun getMessageText(intent: Intent): CharSequence? {
    return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(KEY_TEXT_REPLY)
}

テキストを処理したら、同じ ID とタグ(使用されている場合)で NotificationManagerCompat.notify() を呼び出して通知を更新します。これは、ダイレクト返信 UI を非表示にし、返信が正しく受信され、処理されたことをユーザーに確認するために必要です。

// Build a new notification, which informs the user that the system
// handled their interaction with the previous notification.
val repliedNotification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.message)
    .setContentText(context.getString(R.string.replied))
    .build()

// Issue the new notification.
NotificationManagerCompat.from(context).notify(notificationId, repliedNotification)

その他のデータを取得する

他のデータ型の処理も RemoteInput と同様に機能します。次の例では、入力として画像を使用しています。

val replyLabel: String = context.resources.getString(R.string.reply_label)
val remoteInput: RemoteInput = RemoteInput.Builder(KEY_REPLY).run {
    setLabel(replyLabel)
    // Allow for image data types in the input.
    // This method can be used again to allow for other data types.
    setAllowDataType("image/*", true)
    build()
}

RemoteInput#getDataResultsFromIntent を呼び出して、対応するデータを抽出します。

class ReplyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val dataResults = RemoteInput.getDataResultsFromIntent(intent, KEY_REPLY)
        val imageUri: Uri? = dataResults?.get("image/*") as? Uri

        if (imageUri != null) {
            // Extract the image
            context.contentResolver.openInputStream(imageUri)?.use { inputStream ->
                val bitmap = BitmapFactory.decodeStream(inputStream)
                // Display the image
                // ...
            }
        }
    }

    companion object {
        const val KEY_REPLY = "key_reply"
        const val KEY_TEXT_REPLY = "key_text_reply"
    }
}

この新しい通知を処理する場合は、レシーバの onReceive() メソッドに渡されるコンテキストを使用します。

setRemoteInputHistory() を呼び出して、通知の下部に返信を追加します。ただし、メッセージ アプリを作成する場合は、メッセージ スタイルの通知を作成し、新しいメッセージを会話に追加します。

メッセージ アプリからの通知に関するその他のアドバイスについては、メッセージ アプリのベスト プラクティスのセクションをご覧ください。

緊急メッセージを表示する

アプリで、着信やアラームの鳴動など、緊急性の高いメッセージを表示する必要がある場合があります。このような場合は、全画面インテントを通知に関連付けることができます。

通知が呼び出されると、デバイスのロック ステータスに応じて、次のいずれかが表示されます。

  • ユーザーのデバイスがロックされている場合は、ロック画面を覆う全画面アクティビティが表示されます。
  • ユーザーのデバイスがロック解除されている場合、通知は展開された形式で表示され、通知を処理または閉じるためのオプションが含まれます。

次のコード スニペットは、通知を全画面インテントに関連付ける方法を示しています。

val fullScreenIntent = Intent(context, ImportantActivity::class.java)
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0,
    fullScreenIntent, PendingIntent.FLAG_IMMUTABLE)

val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Hello World!")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    .setFullScreenIntent(fullScreenPendingIntent, true)

ロック画面の可視性を設定する

ロック画面から通知に表示される詳細レベルを制御するには、setVisibility() を呼び出して、次のいずれかの値を指定します。

  • VISIBILITY_PUBLIC: 通知の全内容がロック画面に表示されます。

  • VISIBILITY_SECRET: 通知の一部がロック画面に表示されません。

  • VISIBILITY_PRIVATE: 通知のアイコンやコンテンツのタイトルなど、基本情報のみがロック画面に表示されます。通知の全コンテンツは表示されません。

VISIBILITY_PRIVATE を設定すると、特定の詳細を非表示にする通知コンテンツの代替バージョンを提供することもできます。たとえば、SMS アプリでは、「3 件の新しいテキスト メッセージがあります」という通知を表示し、メッセージの内容と送信者は非表示にできます。この代替通知を提供するには、まず通常どおり NotificationCompat.Builder を使用して代替通知を作成します。次に、setPublicVersion() を使用して、代替通知を通常の通知に添付します。

ロック画面に通知を表示するかどうかは、常にユーザーが最終的に管理します。ユーザーは、アプリの通知チャンネルに基づいて通知を管理できます。

通知を更新する

発行した通知を更新するには、以前に使用したのと同じ ID を渡して、NotificationManagerCompat.notify() を再度呼び出します。以前の通知が閉じられている場合は、代わりに新しい通知が作成されます。

必要に応じて setOnlyAlertOnce() を呼び出すと、通知が最初に表示されたときにのみ、音、バイブレーション、視覚的な手がかりでユーザーに通知が割り込み、その後の更新では割り込まないようにできます。

通知を消去する

通知は、次のいずれかが発生するまで表示されます。

  • ユーザーが通知を閉じる。
  • ユーザーが通知をタップしたとき(通知の作成時に setAutoCancel() を呼び出した場合)。
  • 特定の通知 ID に対して cancel() を呼び出します。このメソッドは、進行中の通知も削除します。
  • cancelAll() を呼び出すと、以前に発行したすべての通知が削除されます。
  • setTimeoutAfter() を使用して通知の作成時にタイムアウトを設定した場合、指定した時間が経過したとき。必要に応じて、指定したタイムアウト時間が経過する前に通知をキャンセルできます。

メッセージング アプリのおすすめの方法

メッセージ アプリやチャットアプリの通知を作成する際は、ここに記載されているベスト プラクティスを考慮してください。

MessagingStyle を使用する

Android 7.0(API レベル 24)以降、Android にはメッセージ コンテンツ専用の通知スタイル テンプレートが用意されています。NotificationCompat.MessagingStyle クラスを使用すると、会話のタイトル、追加メッセージ、通知のコンテンツ ビューなど、通知に表示されるラベルをいくつか変更できます。

次のコード スニペットは、MessagingStyle クラスを使用して通知のスタイルをカスタマイズする方法を示しています。

val message1 = NotificationCompat.MessagingStyle.Message(
    messages[0].text,
    messages[0].time,
    messages[0].sender
)
val message2 = NotificationCompat.MessagingStyle.Message(
    messages[1].text,
    messages[1].time,
    messages[1].sender
)
notification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setStyle(
        NotificationCompat.MessagingStyle(Person.Builder().setName("Me").build())
            .addMessage(message1)
            .addMessage(message2)
    )
    .build()

Android 9.0(API レベル 28)以降では、通知とそのアバターを最適にレンダリングするために、Person クラスを使用することも必要です。

NotificationCompat.MessagingStyle を使用する場合は、次の操作を行います。

  • MessagingStyle.setConversationTitle() を呼び出して、3 人以上のグループ チャットのタイトルを設定します。適切な会話タイトルとしては、グループ チャットの名前や、名前がない場合は会話の参加者のリストなどが考えられます。これがないと、メッセージは、会話内の最新のメッセージの送信者との 1 対 1 の会話に属していると誤認される可能性があります。
  • 画像などのメディア メッセージを含めるには、MessagingStyle.setData() メソッドを使用します。image/* パターンの MIME タイプがサポートされています。

ダイレクト返信を使用する

ダイレクト返信を使用すると、ユーザーはメッセージにインラインで返信できます。

  • ユーザーがインライン返信アクションで返信したら、MessagingStyle.addMessage() を使用して MessagingStyle 通知を更新します。通知を取り消したりキャンセルしたりしないでください。通知をキャンセルしないことで、ユーザーは通知から複数の返信を送信できます。
  • インライン返信アクションを Wear OS に対応させるには、Action.WearableExtender.setHintDisplayInlineAction(true) を呼び出します。
  • addHistoricMessage() メソッドを使用して、過去のメッセージを通知に追加することで、ダイレクト返信の会話にコンテキストを提供します。

スマート リプライを有効にする

  • スマート リプライを有効にするには、返信アクションで setAllowGeneratedResponses(true) を呼び出します。これにより、通知が Wear OS デバイスにブリッジされたときに、スマート リプライの返信をユーザーが利用できるようになります。スマート リプライの返信は、NotificationCompat.MessagingStyle 通知によって提供されるコンテキストを使用して、完全にスマートウォッチ上の機械学習モデルによって生成されます。返信を生成するためにインターネットにアップロードされるデータはありません。

通知メタデータを追加する