创建通知

原文地址:https://developer.android.com/training/notify-user/build-notification.html

创建一个通知

通知提供了有关应用程序未使用时的事件的简短而及时的信息。这篇文章教你怎么通过Android4.0及以上版本的各种功能去创建一个通知。有关通知怎么在Android上显示,Android 通知

此页面上的代码使用Android支持库中的NotificationCompat APIs。这些 APIs 允许你添加新版本有的功能,可以兼容到 Android 4.0(API level 14),然而,一个新的特征,例如回复操作会在旧版本中无法运行。

添加支持库

尽管大部分使用 Android Studio 创建的项目都包含了使用 NotificationCompat 的必要依赖,但还是要检验一下项目的 build.gradle 文件中是否包含下面的依赖

1
2
3
dependencies {
implementation "com.android.support:support-compat:27.1.0"
}

[^ com.android.support 中的其他库也包含了 support-compat 传递性依赖,如果引用了其他库也是可以使用 NotificationCompat 的,就不必显示添加上面的依赖 ]

创建一个基础通知

最基本的通知会显示一个 icon ,一个标题,一段简要的内容。这节你将会学习怎么创建一个点击启动APP的通知。

含有通知和文本的通知

关于通知的每个部分的更多细节 请阅读 通知概览

设置通知内容

首先,通过 NotificationCompat.Builder 设置通知的内容和渠道。下面的示例演示了如果通过以下命令创建通知。

  • 小图标 通过 setSmallIcon() ;这是唯一一个必须设置的用户可见内容
  • 标题 通过 setContentTitle() 设置
  • 内容 通过 setContentText() 设置
  • 通知优先权 通过 setPriority() 设置。这个优先权决定了通知在 Android 7.1及以下系统上的行为(Android 8.0 及以上系统通过渠道的 importance 属性统一设置)
1
2
3
4
5
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(textTitle)
.setContentText(textContent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);

注意这个 NotificationCompat.Builder 构造方式需要一个渠道 ID 。 这是为了兼容 Android 8.0 及以上版本所必须的,旧版本忽略就好

默认通知内容会被自适应为一行,如果你想显示更多,可以通过 setStyle() 设置一个样式模板来启用扩展通知。例如,以下代码会创建一个更大的文本区域:

1
2
3
4
5
6
7
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Much longer text that cannot fit one line...")
.setStyle(new NotificationCompat.BigTextStyle()
.bigText("Much longer text that cannot fit one line..."))
.setPriority(NotificationCompat.PRIORITY_DEFAULT);

关于其他大的通知样式,包括怎么去增加图片,控制媒体播放等,创建扩展通知

创建渠道和设置重要性

Android 8.0 及以上版本必须创建Notificationchannel 实例,并通过 createNotificationchannel() 注册通知渠道。下面代码必须保证 SDK_VERSION 在8.0及以上

1
2
3
4
5
6
7
8
9
10
11
12
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
CharSequence name = getString(R.string.channel_name);
String description = getString(R.string.channel_description);
int importance = NotificationManagerCompat.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
// Register the channel with the system
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.createNotificationChannel(channel);
}

注意这个 NotificationChannel 构造方法需要一个 importance 参数,这个参数是类 NotificationManagerCompat 中的常量;这个参数决定了渠道内通知的行为–但是也必须通过 setPriority() 设置优先权来兼容 Android 7.1 及更低的版本。

尽管必须设置通知的重要性和优先级,但是系统并不保证通知的行为,系统可能会因为别的因素改变重要性等级。用户可以随时更改重要性等级。

设置通知点按操作

每个通知都应该响应点按操作,通常是去打开一个通知对应的 Activity 。这样就必须指定一个 PedingIntent 定义的 Intent 并使用 setContentIntent() 设置给通知。

下面的代码演示了怎样去创建一个当用户点按打开 Activity 的基本通知

1
2
3
4
5
6
7
8
9
10
11
12
13
// Create an explicit intent for an Activity in your app
Intent intent = new Intent(this, AlertDetails.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// Set the intent that will fire when the user taps the notification
.setContentIntent(pendingIntent)
.setAutoCancel(true);

注意:这个通知调用了setAutoCancel(true) 当用户点按后会自动移除通知。

上面所示的 setFlags() 方法有助于在通过通知打开应用程序后保留用户的预期导航体验。但是,是否要用这个功能取决于你打开的 Activity 类型,可能是以下一种情况:

  • 专用于响应通知的 Activity。用户在正常情况下不会打开这个 Activity,所以该 Activity 启动一个新的任务栈而没有必要添加到程序的返回栈中。这就是上面所示的内容意图类型
  • 应用程序中的常规 Activity . 这种情况,启动的 Activity 应该创建一个返回栈以便保留用户的导航体验。

更多关于配置通知意图的方法,移步阅读从通知启动一个Activity

显示通知

显示通知需要调用 NotificationManagerCompat.notify() 方法,参数是一个唯一ID和一个 NotificationCompat.Builder.build() 构建的结果。例如

1
2
3
4
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
// notificationId is a unique int for each notification that you must define
notificationManager.notify(notificationId, mBuilder.build());

记得保存 NotificationManagerCompat.notify() 使用的唯一ID之后要更新或者移除通知都要用到。

注意:从 Android 8.0 开始,程序每秒钟不能发出两次通知声音,如果你每秒钟发出多个通知,只有第一个通知有声音提示。

增加操作按钮

一个通知能够添加三个操作按钮去允许用户快速响应。例如停止提醒或者快速回复文本消息。但是这些操作按钮最好不要跟通知点按响应重复。

有一个按钮的通知

通知按钮是用 PendingIntentaddAction() 方法添加的。就像设置通知默认的点按操作一样,可以不启动 Activity 去做其他的任何事情例如启动一个在后台作业的 BroadcastReceiver 这样就可以不打断用户当前操作。

例如,下面的代码演示了怎么个一个指定的广播接收者发送广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Intent snoozeIntent = new Intent(this, MyBroadcastReceiver.class);
snoozeIntent.setAction(ACTION_SNOOZE);
snoozeIntent.putExtra(EXTRA_NOTIFICATION_ID, 0);
PendingIntent snoozePendingIntent =
PendingIntent.getBroadcast(this, 0, snoozeIntent, 0);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_snooze, getString(R.string.snooze),
snoozePendingIntent);

关于构建一个后台运行的 BroadcastReceiver 的更多信息,可以查看广播指南

如果要构造一个媒体控制(暂停,跳过等)通知,可以查看创建媒体控制通知

添加直接回复操作

Android 7.0 引入的直接回复操作,允许用户不打开 Activity 的情况下在通知内直接输入文本给你程序。例如,你可以在通知内回复文本消息或者更新任务列表。

点击回复按钮直接输入文本

这个直接回复操作在通知上是一个附加操作按钮打开的文本输入框。当用户完成输入,系统会将含有响应文本的意图发送给你的程序。

添加回复按钮

创建一个支持直接回复的通知

  1. 创建一个 RemoteInput.Builder 实例添加到你的通知操作。这个类的构造方法接受一个系统为存储输入文本使用的KEY。之后可以在程序中通过这个KEY检索输入文本。

    1
    2
    3
    4
    5
    6
    7
    // Key for the string that's delivered in the action's intent.
    private static final String KEY_TEXT_REPLY = "key_text_reply";
    String replyLabel = getResources().getString(R.string.reply_label);
    RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
    .setLabel(replyLabel)
    .build();
  2. 为回复操作创建 PendingIntent

1
2
3
4
5
6
// Build a PendingIntent for the reply action to trigger.
PendingIntent replyPendingIntent =
PendingIntent.getBroadcast(getApplicationContext(),
conversation.getConversationId(),
getMessageReplyIntent(conversation.getConversationId()),
PendingIntent.FLAG_UPDATE_CURRENT);

警告:如果您重新使用PendingIntent,用户可能会回复与他们认为不同的对话。你必须为每一个会话提供一个不同的请求码或者提供一个在任何其他对话的回复意图中调用equals()时不会返回true的意图,对话ID经常作为intent的额外套件的一部分传递,但在您调用equals()时会被忽略。

  1. 使用 addRemoteInput() 方法将 RemoteInput 对象附给一个操作。
1
2
3
4
5
6
// Create the reply action and add the remote input.
NotificationCompat.Action action =
new NotificationCompat.Action.Builder(R.drawable.ic_reply_icon,
getString(R.string.label), replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
  1. 添加操作到通知并发送通知
1
2
3
4
5
6
7
8
9
10
11
// Build the notification and add the action.
Notification newMessageNotification = new Notification.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentTitle(getString(R.string.title))
.setContentText(getString(R.string.content))
.addAction(action)
.build();
// Issue the notification.
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, newMessageNotification);

用户出发回复操作按钮时系统会提示用户输入消息。如上图所示。

从回复中检索用户输入

要从通知的答复用户界面接收用户输入,请调用 RemoteInput.getResultsFromIntent() ,并将接受到的 Intent 传递给他:

1
2
3
4
5
6
7
private CharSequence getMessageText(Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
return remoteInput.getCharSequence(KEY_TEXT_REPLY);
}
return null;
}

处理完文本后,你必须调用 NotificationManagerCompat.notify() 传入ID和TAG(如果有)来更新通知。以便隐藏直接回复界面并向用户确认他们的回复已经被正确接受并处理。

1
2
3
4
5
6
7
8
9
10
// Build a new notification, which informs the user that the system
// handled their interaction with the previous notification.
Notification repliedNotification = new Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentText(getString(R.string.replied))
.build();
// Issue the new notification.
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, repliedNotification);

在处理这个新通知时,使用传递给接收者的 onReceive() 上下文。

还应该通过调用 setREmoteInputHistory() 将该答复追加到底部。但是,如果您正在构建消息传递应用程序,则应该创建消息传递式通知并将新消息追加到对话中。

添加一个进度条

通知能包含一个动画进度指示器,向用户展示正在进行的操作状态。

操作过程中和之后的进度条

如果能知道操作在什么时候完成。通过调用 setProgress(max,progress,false) 使用指示器的“确定”模式。第一个参数是“完成”时的值(例如100),第二个参数是当前的完成值,最后一个表示这是一个“确定”进度条。

随着操作的进行,持续的调用 setProgress(max,progress,false) 更新进度值并且重复发送通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
mBuilder.setContentTitle("Picture Download")
.setContentText("Download in progress")
.setSmallIcon(R.drawable.ic_notification)
.setPriority(NotificationCompat.PRIORITY_LOW);
// Issue the initial notification with zero progress
int PROGRESS_MAX = 100;
int PROGRESS_CURRENT = 0;
mBuilder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false);
notificationManager.notify(notificationId, mBuilder.build());
// Do the job here that tracks the progress.
// Usually, this should be in a worker thread
// To show progress, update PROGRESS_CURRENT and update the notification with:
// mBuilder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false);
// notificationManager.notify(notificationId, mBuilder.build());
// When done, update the notification one more time to remove the progress bar
mBuilder.setContentText("Download complete")
.setProgress(0,0,false);
notificationManager.notify(notificationId, mBuilder.build());

操作结束时,progress(完成进度)应该等于 max (最大值)。你可以让通知显示完成时间或者移除它。不管哪种情况,都应该更新通知显示操作已经完成。调用 setProgress(0,0,false) 可以移除通知进度条。

注意:因为进度条需要你不停的更新进度,所以这些代码通常运行在后台服务中。

显示一个指示器进度条(不确定模式,不显示完成百分比),可以调用 setProgress(0,0,true)。 样式是一个跟上边一样的进度条,除了进度条是一个不表示进度的连续动画。这个进度条动画会一直进行着直到你调用 setProgress(0,0,false) 然后更新通知删除进度指示器。

记得在操作完成后更新通知文本去通知用户

注意:如果确实需要下载文件,应该考虑使用 DownloadManager 他提供自己的进度通知来跟踪下载进度。

设置一个系统范围的类别

Android 通过一些预定义的系统范围类型去确定在用户开启不打扰模式的情况下怎么用通知打扰用户。

如果你的通知是 NotificationCompat 中预定义的通知类别中的一个–例如 CATEGORY_ALARM,CATEGORY_REMINDER,CATEGORY_EVENT or CATEGORY_CALL ,你应该通过 setCatergory() 定义一个类别。

1
2
3
4
5
6
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_MESSAGE);

当设备进入忽扰模式系统将根据设置的通知类别决定通知的行为。

此外,通知类别不是必须的,只有你的通知输入系统预定义中一种才需要设置。

设置锁屏可见性

要在锁定屏幕中控制通知可见的详细程度,调用 setVisiblity() 并设置下面中的一个值:

  • VISIBILITY_PUBLIC 显示通知的完整内容
  • VISIBILITY_SECRET 不显示任何内容
  • VISIBILITY_PRIVATE 显示基础内容,例如通知的icon,标题,但是隐藏了内容;

当设置为 VISIBILITY_PRIVATE 时可以提供一个隐藏部分内容的备用版本。例如,一个 SMS app 可能会显示 “你有三条文本消息”,但是隐藏了消息内容和发送人。提供这个备用通知,首先通过 NotificationCompat.Builder 创建一个备用通知。然后调用 setPublicVersion() 附加进普通通知。

然而,用户对这些有最终控制权,甚至可以从通知渠道控制。

更新通知

在发布后更新通知,可以再次调用 NotificationManagerCompat.notify() 使用原来的通知ID。如果通知已经消失会发出一个新的通知。

可以选择性调用 setOnlyAlertOnce() 让通知只在第一次显示时才会中断用户(包括声音,震动,视觉),而不是以后的更新都中断用户。

注意:Android系统在更新通知时应用了比例限制。如果你的更新过于频繁(一秒内多个),系统可能会放弃一些更新(通常一秒内只更新一次)。

移除通知

通知会在以下几种情况被移除:

  • 用户清除
  • 创建通知时调用了 setAutoCancel(),用户点击通知后会自动消失。
  • 调用 cancel() 方法,传入指定ID,这个方法会删除指定通知。
  • 调用 cancelAll() 移出所有你发出的通知
  • 如果创建的时候调用 setTimeoutAfter() 设置了超市时间,系统会在指定时间后清除此通知,如果需要,你可以在系统清除之前清除掉。

消息应用最佳实践

使用此处列出的最佳做法作为创建消息传递和聊天应用通知时要记住的内容的快速参考

使用 MessagingStyle

从 Android 7.0 开始,Android 为消息类型通知子提供了样式模板。 使用 NotificationCompat.MessagingStyle 类,你可以更改多个显示在通知的标签,包括会话标题,其他的消息和通知的内容视图。

下面的代码片段演示了怎么用 Messagingstyle 类自定一个通知样式。

1
2
3
4
5
6
7
8
Notification notification = new Notification.Builder(this, CHANNEL_ID)
.setStyle(new NotificationCompat.MessagingStyle("Me")
.setConversationTitle("Team lunch")
.addMessage("Hi", timestamp1, null) // Pass in null for user.
.addMessage("What's up?", timestamp2, "Coworker")
.addMessage("Not much", timestamp3, null)
.addMessage("How about lunch?", timestamp4, "Coworker"))
.build();

从Android 8.0(API级别26)开始,使用NotificationCompat.MessagingStyle类的通知将以折叠形式显示更多内容。您还可以使用addHistoricMessage()方法通过向与消息传递相关的通知添加历史消息来为对话提供上下文。

使用 NotificationCompat.MessagingStyle 的情况:

  • 调用MessagingStyle.setConversationTitle()为两个以上的人设置群组聊天的标题。一个好的对话标题可能是群组聊天的名称,或者如果它没有特定的名称,则可能是对话中的参与者列表。如果没有这个,该消息可能被误认为属于与对话中最近消息的发送者的一对一对话。
  • 使用MessagingStyle.setData()方法包含媒体消息,如图像。目前支持模式图像/ *的MIME类型

使用直接回复

直接回复允许用户回复内部消息

  • 在用户使用内联回复操作回复后,使用MessagingStyle.addMessage()更新MessagingStyle通知,并且不收回或取消通知。不取消通知允许用户从通知中发送多个回复。
  • 要使内联回复操作与Android Wear兼容,请调用Action.WearableExtender.setHintDisplayInlineAction(true)。
  • 使用addHistoricMessage()方法通过向通知添加历史消息来为直接回复对话提供上下文

启用智能回复

要启用智能回复,请在回复操作上调用setAllowGeneratedResponses(true)。这会使通知桥接到Android Wear设备时,用户可以使用智能回复响应。智能答复响应由完全在机器学习模型生成,使用NotificationCompat.MessagingStyle通知提供的上下文,并且没有数据上传到互联网以生成响应

添加通知元数据

当设备处于免打扰模式时,分配通知元数据以告知系统如何处理您的应用通知。例如,使用addPerson()或setCategory(Notification.CATEGORY_MESSAGE)方法覆盖免打扰模式。