最近一段时间写的都是android源码的文章,前几天在公司做了一个需求是关于前台服务的,在写前台服务的时候深入使用到了通知,今天就先写一篇文章总结一下通知的相关内容,后面有时间了在介绍一下前台服务的相关内容。
通知概述本篇文章主要介绍通知的以下知识点:
不同android版本上通知功能 通知的结构 创建通知 通知的操作 从通知启动 Activity 展开式通知 通知渠道 通知的级别 自定义通知上面的这么多内容基本覆盖了通知百分之八十的知识点,了解这些足够日常通知的使用了。
不同android版本上通知功能Android 4.1(API 级别 16)
引入了展开式通知模板(称为通知样式),可以提供较大的通知内容区域来显示信息。用户可以使用单指向上/向下滑动的手势来展开通知。Android 5.0(API 级别 21)
引入了锁定屏幕和浮动通知。 向 API 集添加了通知是否在锁定屏幕上显示的方法 (setVisibility()),以及指定通知文本的“公开”版本的方法。 添加了 setPriority() 方法,告知系统该通知应具有的“干扰性”(例如,将其设置为“高”,可使该通知以浮动通知的形式显示)。Android 7.0(API 级别 24)
用户可以使用内联回复直接在通知内回复(用户可以输入文本,然后将其发送给通知的父级应用)。Android 8.0(API 级别 26)
现在必须将单个通知放入特定渠道中。 用户现在可以按渠道关闭通知,而不是关闭应用的所有通知。 包含活动通知的应用会在应用图标上方显示通知“标志”。(小圆点或数字) 用户可以暂停抽屉式通知栏中的通知。您可以为通知设置自动超时。 可以设置通知的背景颜色。 通知的结构...
构建包含返回堆栈的 PendingIntent
要启动包含 Activity 的返回堆栈的 Activity,您需要创建 TaskStackBuilder 的实例并调用 addNextIntentWithParentStack(),向其传递您要启动的 Activity 的 Intent。
只要您为每个 Activity 定义了父 Activity(如上文所述),就可以调用 getPendingIntent() 来接收包含整个返回堆栈的 PendingIntent。
Intent resultIntent = new Intent(this, ResultActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntentWithParentStack(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
如有必要,您可以通过调用 TaskStackBuilder.editIntentAt() 向堆栈中的 Intent 对象添加参数。有时候需要这样做,以确保返回堆栈中的 Activity 在用户向上导航到它时显示有意义的数据。
展开式通知基本通知通常包括标题、一行文本,以及用户可以执行的一项或多项响应操作。要提供更多信息,您还可以应用本页介绍的多个通知模板之一来创建大型展开式通知。
1.添加大图片NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("通知")
.setContentText("收到一条消息")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.image)))
.setAutoCancel(true);
这里通过setStyle来使用系统提供的模版创建大图片通知,要使该图片仅在通知收起时显示为缩略图,请调用 setLargeIcon() 并传入该图片,同时调用 BigPictureStyle.bigLargeIcon() 并传入 null,这样大图标就会在通知展开时消失:
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("通知")
.setContentText("收到一条消息")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.image))
.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.image))
.bigLargeIcon(null))
.setAutoCancel(true);
2.添加大文本
应用 NotificationCompat.BigTextStyle,以在通知的展开内容区域显示文本:
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("通知")
.setContentText("收到一条消息")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText("无论是否意识到 Gradle 的存在,每位 Android 程序员都会直接或间接的与 Gradle 打交道。每当通过 Android Studio 新建一个工程时,AS 都会自动创建一个通用的目录结构,然后就可以进行开发,在 app 的 build.gradle 中添加一些依赖,点击右上角的 Sync Now"))
.setAutoCancel(true);
3.收件箱样式的通知
如果您想要添加多个简短的摘要行(例如收到的电子邮件的片段),可对通知应用 NotificationCompat.InboxStyle。这样,您就可以添加多条内容文本,并且每条文本均截断为一行,而不是显示为 NotificationCompat.BigTextStyle 提供的一行连续文本。
要添加新行,最多可调用 addLine() 6 次。如果添加的行超过 6 行,则仅显示前 6 行。
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("通知")
.setContentText("收到一条消息")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setStyle(new NotificationCompat.InboxStyle()
.addLine("无论是否意识到 Gradle 的存在,每位 Android 程序员都会直接或间接的与 Gradle 打交道。每当通过 Android Studio 新建一个工程时")
.addLine("AS 都会自动创建一个通用的目录结构,然后就可以进行开发,在 app 的 build.gradle 中添加一些依赖,点击右上角的 Sync Now")
.addLine("编写代码,点击绿色小箭头 Run 运行代码,一切都这么美好"))
.setAutoCancel(true);
通知渠道
从 Android 8.0(API 级别 26)开始,所有的通知都必须分配到相应的渠道。对于每个渠道,您可以设置应用于其中的所有通知的视觉和听觉行为(也就是通知的级别)。然后,用户可以更改这些设置,并确定您应用中的哪些通知渠道应具有干扰性或应该可见。我认为添加这个功能主要是为了让用户能够更透明的管理通知。(百分之九十九的人都不知道,知道也不会去看,唉!!!)
我们来看一下酷狗音乐的通知渠道情况(不同的手机厂商系统不同显示会有出入):
可以看到酷狗总共有5个通知渠道,我们看一下酷狗推送消息这个渠道:
渠道详情里有重要程度(也就是通知的级别)、提示音、震动等设置,这些都是我们可以在代码中设置的,当然用户也可以主动的修改他们。
前面已经讲了通知渠道的创建,下面来看一下创建的时候可以做哪些额外操作:
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(channelID, channelNAME, level);
//设置提示音
channel.setSound();
//开启指示灯
channel.enableLights();
//开启震动
channel.enableVibration();
//设置锁屏展示
channel.setLockscreenVisibility();
//设置渠道描述
channel.setDescription();
manager.createNotificationChannel(channel);
这里只列举了一部分,在Android 7.1(API 级别 25)以下这些都是直接在NotificationCompat.Builder中设置的,只是在 Android 8.0(API 级别 26)上把这些功能拆分到了渠道设置上。
在创建了渠道之后就无法更改这些设置了。 这里需要单独讲一下,无论我们调用多少次创建渠道的方法,对于同一个渠道ID只有第一次创建是有效的,因为创建的时候会去判断是否存在这个渠道,如果存在是不会重新创建的。其实这么说也不完全对,有兴趣的可以看一下我在文章最后做的一个分析。
虽然在创建渠道之后我们不能再去修改他,但是我们可以获取渠道设置,因为渠道设置对用户是透明的,用户可以去随意的设置他,所以我们可以在获取渠道设置之后引导用户设置我们想要的行为。比如我是微信的开发者,微信的通知被用户手动关掉了,我在检测到通知被关之后就会去告诉用户这个通知很重要不能关掉,然后引导用户跳到那个页面进行设置。主要的步骤如下:
1.读取通知渠道设置
通过调用 getNotificationChannel() 或 getNotificationChannels() 获取 NotificationChannel 对象。 查询特定的渠道设置,例如 getVibrationPattern()、getSound() 和 getImportance()。2.打开通知渠道设置
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId());
startActivity(intent);
注意,该 intent 需要两个提取项,分别用于指定您应用的软件包名称(也称为应用 ID)和要修改的渠道。
其实还有一个渠道分组没讲,但是用的很少就不说了。
通知的级别通知的级别主要体现在通知的展示方式上,有的通知只在状态栏现在一个图标,有的通知却会弹出来悬浮在屏幕上(例如微信),这就是通过设置通知的级别实现的。在 Android 7.1(API 级别 25)及更低版本上是通过priority这个属性设置,在 Android 8.0(API 级别 26)及更高版本上是通过importance属性设置。
用户可见的重要性级别 | 重要性(Android 8.0 及更高版本) | 优先级(Android 7.1 及更低版本) |
---|---|---|
紧急 发出提示音,并以浮动通知的形式显示 |
IMPORTANCE_HIGH | PRIORITY_HIGH 或 PRIORITY_MAX |
高 发出提示音 |
IMPORTANCE_DEFAULT | PRIORITY_DEFAULT |
中 不发出提示音 |
IMPORTANCE_LOW | PRIORITY_LOW |
低 不发出提示音,且不会在状态栏中显示 |
IMPORTANCE_MIN | PRIORITY_MIN |
1.Android 7.1(API 级别 25)及更低版本
通过setPriority(NotificationCompat.PRIORITY_LOW)方法可以直接设置。
2.Android 8.0(API 级别 26)及更高版本
在Android8.0中这一功能需要在通知渠道中设置
NotificationChannel channel = new NotificationChannel(channelID, channelNAME, NotificationManager.IMPORTANCE_LOW);
最后一个参数就是通知的级别,除了在创建渠道的时候设置,在渠道创建完成之后也可以设置
NotificationChannel channel = new NotificationChannel(channelID, channelNAME, NotificationManager.IMPORTANCE_LOW);
channel.setImportance(NotificationManager.IMPORTANCE_LOW);
兼容
对比上面两个设置方法,我们发现把这两个设置都加上,并且设置相同的等级就可以兼容所有的版本。
使用自定义通知布局时,请特别注意确保您的自定义布局适用于不同的设备屏幕方向和分辨率。虽然对于所有界面布局,此建议都适用,但它对通知布局而言尤为重要,因为抽屉式通知栏中的空间非常有限。自定义通知布局的可用高度取决于通知视图。通常情况下,收起后的视图布局的高度上限为 64 dp,展开后的视图布局的高度上限为 256 dp。
自定义通知有两种,一种是为内容区域创建自定义布局,另一种是创建完全自定义的通知布局。
如果您需要自定义内容区域的布局,可以将 NotificationCompat.DecoratedCustomViewStyle 应用到您的通知。借助此 API,您可以为通常由标题和文本内容占据的内容区域提供自定义布局,同时仍对通知图标、时间戳、子文本和操作按钮使用系统装饰。
自定义布局的使用方式如下:
String channelId = createNotificationChannel("my_channel_ID", "my_channel_NAME", NotificationManager.IMPORTANCE_MAX);
RemoteViews notificationLayout = new RemoteViews(getPackageName(), R.layout.custom_notification_item);
RemoteViews notificationLayoutExpanded = new RemoteViews(getPackageName(), R.layout.custom_notification_large);
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.mipmap.ic_launcher)
.setStyle(new NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notificationLayout)
.setCustomBigContentView(notificationLayoutExpanded)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(100, notification.build());
如果您不希望使用标准通知图标和标题装饰通知,请按照上述步骤使用 setCustomBigContentView(),但不要调用 setStyle()。
要支持低于 Android 4.1(API 级别 16)的 Android 版本,您还应调用 setContent(),向其传递同一 RemoteViews 对象。
在测试创建了渠道之后还能不能修改的时候我发现有些信息是能修改的,但是大部分是不能的,于是我去看了一下创建渠道的源码,结果发现前面说的渠道在创建之后就不能进行修改了其实不完全对,正确的是大部分主要的都不能修改了,有一些次要的信息还是能修改的,看一下创建渠道时发现渠道已经存在处理的代码:
NotificationChannel existing = r.channels.get(channel.getId());
// Keep most of the existing settings
if (existing != null && fromTargetApp) {
if (existing.isDeleted()) {
existing.setDeleted(false);
needsPolicyFileChange = true;
// log a resurrected channel as if it's new again
MetricsLogger.action(getChannelLog(channel, pkg).setType(
com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
}
//发现渠道名字不同会更新
if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) {
existing.setName(channel.getName().toString());
needsPolicyFileChange = true;
}
//发现渠道描述不同会更新
if (!Objects.equals(channel.getDescription(), existing.getDescription())) {
existing.setDescription(channel.getDescription());
needsPolicyFileChange = true;
}
//我也不知道是什么
if (channel.isBlockableSystem() != existing.isBlockableSystem()) {
existing.setBlockableSystem(channel.isBlockableSystem());
needsPolicyFileChange = true;
}
//发现组不同会更新
if (channel.getGroup() != null && existing.getGroup() == null) {
existing.setGroup(channel.getGroup());
needsPolicyFileChange = true;
}
// Apps are allowed to downgrade channel importance if the user has not changed any
// fields on this channel yet.
//当用户没有手动修改过渠道的信息,并且要更新的通知等级小于现有的等级可以更新
final int previousExistingImportance = existing.getImportance();
if (existing.getUserLockedFields() == 0 &&
channel.getImportance() < existing.getImportance()) {
existing.setImportance(channel.getImportance());
needsPolicyFileChange = true;
}
// system apps and dnd access apps can bypass dnd if the user hasn't changed any
// fields on the channel yet
if (existing.getUserLockedFields() == 0 && hasDndAccess) {
boolean bypassDnd = channel.canBypassDnd();
if (bypassDnd != existing.canBypassDnd()) {
existing.setBypassDnd(bypassDnd);
needsPolicyFileChange = true;
if (bypassDnd != mAreChannelsBypassingDnd
|| previousExistingImportance != existing.getImportance()) {
updateChannelsBypassingDnd(mContext.getUserId());
}
}
}
updateConfig();
return needsPolicyFileChange;
}
通过代码发现setName、setDescription、setGroup是可以被修改的,setImportance也能修改但是必须当用户没有手动修改过渠道的信息,并且要修改的通知等级小于现有的等级可以修改。
作者:mashanshui