通知可在应用未使用时及时提供有关应用事件的简短信息。本文档介绍了如何创建具有各种功能的通知。如需了解通知在 Android 上的显示方式,请参阅通知概览。 如需查看使用通知的示例代码,请参阅 GitHub 上的 SociaLite 示例。
本页中的代码使用了 AndroidX 库中的 NotificationCompat API。这些 API 允许您添加仅在较新版本 Android 上可用的功能,同时仍向后兼容 Android 9(API 级别 28)。不过,某些功能(例如内嵌回复操作)在较旧版本上相当于空操作。
创建基本通知
通知的最基本和紧凑形式(也称为收起形式)会显示一个图标、一个标题和少量文本内容。本部分介绍了如何创建用户可以点按以启动应用中 activity 的通知。
图 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)及更高版本兼容,但会被更低版本忽略。
默认情况下,通知的文字内容会被截断以适应单行显示。您可以通过创建展开式通知来显示更多信息。
图 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)
如需详细了解其他大型通知样式(包括如何添加图片和媒体播放控件),请参阅创建展开式通知。
创建渠道并设置重要性
必须先通过向 createNotificationChannel() 传递 NotificationChannel 的实例在系统中注册应用的通知渠道,然后才能在 Android 8.0 及更高版本上提供通知。以下代码会受到基于 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() 设置优先级,如上例所示。
虽然您必须设置重要程度或优先级,但系统无法保证提醒行为。系统可能会根据其他因素调整重要程度,并且用户可以随时自定义渠道的重要程度级别。
如需详细了解不同级别的含义,请参阅通知重要程度级别。
设置通知的点按操作
每条通知都必须响应点按操作,通常是打开应用中与通知对应的 activity。为此,请指定使用 PendingIntent 对象定义的内容 intent,并将其传递给 setContentIntent()。
以下代码段展示了如何创建一个基本 intent,以便在用户点按通知时打开 activity:
// 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(),它会在用户点按通知时自动移除通知。
上例中的 intent 标志可确保用户在通过通知打开应用后获得符合预期的导航体验。您可能需要使用它,具体取决于您要启动的 activity 类型,类型可能包括:
仅用于响应通知的 activity。 用户在正常使用应用期间没有理由导航到此 activity,因此该 activity 会启动一个新任务,而不是添加到应用的现有任务和返回堆栈中。这是在前面的示例中创建的 intent 类型。
应用的常规应用流程中存在的 Activity。在这种情况下,启动 activity 会创建一个返回堆栈,以便保留用户对“返回”和“向上”按钮的预期。
显示通知
如需使通知显示出来,请调用 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. 包含一个操作按钮的通知。
如需添加操作按钮,请将 PendingIntent 传递给 addAction() 方法。这类似于设置通知的默认点按操作,但您无需启动 activity,而是可以执行其他操作,例如启动在后台执行作业的 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)中引入的,可让用户直接在通知中输入文本。然后,文本会传送到您的应用,而无需打开 activity。例如,您可以使用直接回复操作,让用户在通知中回复短信或更新任务列表。
图 4. 点按“回复”按钮会打开文本输入。
直接回复操作在通知中显示为一个额外的按钮,用于打开文本输入。当用户完成输入后,系统会将文本回复附加到您为通知操作指定的 intent,并将该 intent 发送到您的应用。
添加回复按钮
如需创建支持直接回复的通知操作,请按以下步骤操作:
创建一个 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 所示。
从回复中检索用户输入
如需从通知的回复界面接收用户输入,请调用 RemoteInput.getResultsFromIntent(),并向其传递 BroadcastReceiver 接收的 Intent:
private fun getMessageText(intent: Intent): CharSequence? { return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(KEY_TEXT_REPLY) }
处理完文本后,通过调用 NotificationManagerCompat.notify()(使用相同的 ID 和标记,如果使用的话)来更新通知。这是隐藏直接回复界面并向用户确认其回复已正确接收和处理所必需的。
// 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() 将回复附加到通知底部。不过,如果您要构建即时通讯应用,请创建消息样式通知,并将新消息附加到对话中。
如需有关即时通讯应用通知的更多建议,请参阅即时通讯应用的最佳实践部分。
显示紧急消息
您的应用可能需要显示紧急、时效性强的消息,例如来电或响铃闹钟。在这些情况下,您可以将全屏 intent 与通知相关联。
调用通知时,用户会看到以下内容之一,具体取决于设备的锁定状态:
- 如果用户的设备处于锁定状态,系统会显示一个全屏 activity,覆盖锁屏界面。
- 如果用户设备处于解锁状态,通知会以展开形式显示,其中包含用于处理或关闭通知的选项。
以下代码段演示了如何将通知与全屏 intent 相关联:
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 时,您还可以提供隐藏某些详细信息的通知内容的备用版本。例如,短信应用可能会显示一条通知,内容为“您有 3 条新短信”,但会隐藏短信内容和发件人。如需提供此备用通知,请先照常使用 NotificationCompat.Builder 创建备用通知。然后,使用 setPublicVersion() 将备用通知附加到常规通知。
请注意,用户始终可以最终控制其通知是否在锁定的屏幕上显示,并且可以根据应用的通知渠道控制这些通知。
更新通知
如需在发出通知后对其进行更新,请再次调用 NotificationManagerCompat.notify(),并向其传递您之前使用的同一 ID。如果之前的通知已被关闭,则系统会改为创建新通知。
您可以选择调用 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()可为包含两个以上用户的群聊设置标题。合适的对话标题可以是群聊的名称,如果群聊没有名称,则可以是对话参与者的列表。否则,该消息可能会被误认为属于与对话中最新消息发送者的一对一对话。 - 使用
MessagingStyle.setData()方法添加图片等媒体消息。支持 image/* 格式的 MIME 类型。
使用直接回复
“直接回复”功能可让用户以内嵌方式回复消息。
- 用户使用内嵌回复操作进行回复后,请使用
MessagingStyle.addMessage()更新MessagingStyle通知,不要撤消或取消通知。不取消通知可让用户通过通知发送多条回复。 - 如需使内嵌回复操作与 Wear OS 兼容,请调用
Action.WearableExtender.setHintDisplayInlineAction(true)。 - 使用
addHistoricMessage()方法,通过向通知添加历史消息来为直接回复对话提供上下文。
启用智能回复
- 如需启用智能回复,请对回复操作调用
setAllowGeneratedResponses(true)。这样,当通知桥接到 Wear OS 设备时,用户就可以使用智能回复功能。智能回复响应由完全在手表上运行的机器学习模型使用NotificationCompat.MessagingStyle通知提供的上下文生成,并且不会将任何数据上传到互联网来生成响应。
添加通知元数据
- 分配通知元数据以告知系统在设备处于免打扰模式时如何处理应用通知。例如,使用
addPerson()或setCategory(Notification.CATEGORY_MESSAGE)方法替换“勿扰”设置。