Android通知学习和总结

本篇笔记主要是学习Android通知,主要包括通知渠道,通知分组,通知样式,建立通知,通知关联等部分。本篇笔记的学习资料主要是Android开发官网文档的通知部分,能够点击链接直接进入到网站学习,下面是本篇笔记的内容。java

概述

在以前的笔记中,已经学习了通知栏相关的大部分的内容,这篇笔记主要是对以前的笔记作一个总结和梳理,以及对以前笔记中没有提到过的内容学习一下。android

建立通知的流程

经过以前的学习,已经明白了建立通知栏的流程主要包括如下步骤:微信

  1. 首先建立通知渠道(NotificationChannel):markdown

    虽然通知渠道是8.0版本及以上才须要加上的,可是如今Android版本已经更新到11了,所以对于通知渠道这里默认是须要建立的,建立通知渠道须要设置如下属性:session

    • 渠道ID,渠道名称以及重要性是必需要设置的
    • 渠道说明最好加上,方便用户对当前渠道有一个细致的了解,从而避免用户由于不了解渠道信息错误地设置了相应的属性

    建立完通知渠道以后经过createNotificationChannel等方法注册通知渠道。并发

  2. 接着建立通知app

    通知也就是要显示在通知栏的信息,建立通知须要设置如下信息:ide

    • 必需要设置smallIcon的信息
    • 设置title,content信息
    • 对于音视频通知,会话消息通知等特殊状况下还须要设置style属性来知足不一样的需求
    • 为了兼容8.0如下的系统,还须要设置priority属性等,来保持和8.0拥有相同的重要性等。
  3. 显示通知工具

    首先肯定一个通知Id,而后调用NotificationManagerCompatnotify方法将通知显示出来。oop

下面的代码演示了如何建立一个最基本的通知:

//发送一个基本的通知栏消息
    private fun sendBasicNotification() {
        //首先建立通知渠道
        val channelId = "basicChannelId"
        if (SdkVersionUtils.checkAndroidO()) { //这个工具类只是判断当前的SDK版本是否大于等于8.0
            val channelName = "基本的通知栏消息渠道"
            val importance = NotificationManager.IMPORTANCE_HIGH
            val channel = NotificationChannel(channelId, channelName, importance)
            channel.description = "这个渠道下的消息都是为了演示基本通知栏消息用的"
            val channelManager = getSystemService(Context.NOTIFICATION_SERVICE)
                    as NotificationManager
            channelManager.createNotificationChannel(channel)
        }
        //接下来建立通知
        val builder = NotificationCompat.Builder(this, channelId)
        builder.setSmallIcon(R.drawable.frank) //smallIcon是必须的
        builder.setContentTitle("基本通知栏消息")
        builder.setContentText("这是一条基本的通知栏消息")
        val notificationInfo = builder.build()
        //最后发送通知
        val notificationId = 1000
        NotificationManagerCompat.from(this).notify(notificationId, notificationInfo)
    }

复制代码
  1. 补充操做

    在执行完上面的操做以后,咱们就已经能够在通知栏显示一条信息了,剩下的基本就对上面的步骤设置一些补充的操做,好比设置style以适应特殊状况下的样式,设置PendingIntent属性来设置跳转页面等。

补充渠道操做

上面已经对若是发送一条通知栏消息有了明确的认识,总共三步便可显示一条通知栏消息,而为了可以适应开发中不一样的需求,大多数状况下都须要经过补充上面三步中的某一步。下面是经过补充/扩展渠道相关的信息。

设置属性

经过查看NotificationChannel部分的代码,能够发现渠道属性这里还有不少能够设置的地方,以下演示渠道部分设置。

  1. channel.enableLights(true)

    设置这个属性用于开启当这个渠道的通道发布时显示通知灯。(实际在个人一加手机上尝试并无效果)

  2. lightColor

    设置这个属性用于当通知灯可用时的颜色(实际测试中发现也没有效果)

  3. enableVibration

    设置这个属性用于当这个渠道的通知发布时是否开启震动。(实际测试中没有效果,在通知渠道设置页面有一个选项可供用户选择是否开启震动)

注意: 渠道的相关设置事实上主要受到用户的影响,开发者并不能进行过多的控制,同时,就算开发者设置了部分属性,最终也会以用户设置的为准。

读取属性

读取属性的意义在于咱们能够引导用户进行某项设置,好比上面的设置震动没有效果的时候,此时咱们就能够引导用户跳转到设置页面,让用户勾选容许震动,这样当这个渠道的通知发布的时候就会震动了。

经过如下三步读取渠道属性:

  1. 获取NotificationManager,经过getSystemService(Context.NOTIFICATION_SERVICE)来获取
  2. 获取NotificationChannel,经过NotificationManager.getNotificationChannel(channelId)来获取
  3. 获取相应的属性值,经过shouldXXX或者getXXX相关方法来获取。

例以下面的代码演示了获取当前渠道发布通知时是否容许震动:

val channelManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val readChannel = channelManager.getNotificationChannel(channelId)
val vibrate = readChannel.shouldVibrate()
Logs.e("是否开启震动:$vibrate")
复制代码

跳转到渠道信息设置页面

上面说了,有时候咱们设置的渠道属性没有效果,或者有时候咱们设置的属性被用户修改了,此时咱们能够引导用户跳转到渠道信息设置页面,让用户进行某项操做的设置。

跳转渠道信息设置页面主要经过Intent完成,主要操做步骤以下:

val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, "应用包名")
intent.putExtra(Settings.EXTRA_CHANNEL_ID, "渠道ID")
startActivity(intent)
复制代码

渠道分组

渠道分组的意义在于能够对某些渠道进行分组,便于用户管理。

可是经过以前的学习,我我的认为渠道分组的意义并非很大,并且我我的认为对于渠道的分组应该让用户本身去分组,而不是让开发者分组好了让用户去操做。

要注意的是这里只是我认为意义不是很大。可是对于有多个帐户须要管理的人来讲这个渠道分组仍是颇有用的。

好比对于常用邮箱的人来讲,不少人可能拥有多个邮箱帐号(好比我本身有一个163邮箱的,一个微软outlook邮箱的,一个工做用的阿里云邮箱的),此时我经过一个软件,好比Foxmail来管理我这些邮箱帐号。每一个邮箱帐号都会有收到邮件的通知渠道,发送邮件成功的通知渠道,以及其它操做的通知渠道。这个时候,咱们发现,我这3个帐号有9个通知渠道,此时让我去管理这些通知渠道就很麻烦,由于有时候很难分清哪一个通知渠道是属于哪一个帐号下面的,这个时候渠道分组的优点就体现出来了,咱们针对每一个帐号建立一个渠道分组,每一个分组的名字就是这个邮箱帐号,每一个分组下面分别包含发送邮件,接收邮件,其它操做三个渠道。这样,无论是我想操做某个渠道,仍是想操做某个帐号下全部的渠道,最起码我不会不知道该去操做哪一个渠道了!

同时,渠道分组提供了能够直接关闭或者开启某一个渠道分组下全部渠道的通知,这样,好比我这个163邮箱没什么用,常常收到一些垃圾邮件,那我就把这个渠道分组直接关闭掉,这样163邮箱的通知就不会有了,可是不影响其它两个渠道分组。

针对上面的案例,咱们经过如下代码来实现:

//建立渠道分组
    private fun createChannelGroup() {
        if (SdkVersionUtils.checkAndroidO()) {
            //分组id和名称
            //163邮箱的
            val CHANNEL_GROUP_ID_163 = "channelGroupId163"
            val CHANNEL_GROUO_NAME_163 = "163邮箱"
            //outlook邮箱的
            val CHANNEL_GROUP_ID_OUTLOOK = "channelGroupIdOutlook"
            val CHANNEL_GROUO_NAME_OUTLOOK = "outlook邮箱"
            //阿里云邮箱的
            val CHANNEL_GROUP_ID_ALI = "channelGroupIdAli"
            val CHANNEL_GROUP_NAME_ALI = "阿里云邮箱"

            //建立渠道分组
            val channelGroup163 =
                NotificationChannelGroup(CHANNEL_GROUP_ID_163, CHANNEL_GROUO_NAME_163)
            val channelGroupOutlook =
                NotificationChannelGroup(CHANNEL_GROUP_ID_OUTLOOK, CHANNEL_GROUO_NAME_OUTLOOK)
            val channelGroupAli =
                NotificationChannelGroup(CHANNEL_GROUP_ID_ALI, CHANNEL_GROUP_NAME_ALI)
            //通知管理器
            val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            //建立渠道分组
            manager.createNotificationChannelGroup(channelGroup163)
            manager.createNotificationChannelGroup(channelGroupOutlook)
            manager.createNotificationChannelGroup(channelGroupAli)

            //分别建立通知渠道,这里为了方便我直接循环建立
            val channels = mutableListOf<NotificationChannel>()
            for (i in 0 until 9) {
                val channelId = when (i) {
                    0, 1, 2 -> "channelId_163_$i"
                    3, 4, 5 -> "channelId_outlook_$i"
                    else -> "channelId_ali_$i"
                }
                val channelName = when (i) {
                    0, 1, 2 -> "channelName_163_$i"
                    3, 4, 5 -> "channelName_outlook_$i"
                    else -> "channelName_ali_$i"
                }
                val channel = NotificationChannel(
                    channelId,
                    channelName,
                    NotificationManager.IMPORTANCE_DEFAULT
                )
                channel.group = when (i) {
                    0, 1, 2 -> CHANNEL_GROUP_ID_163
                    3, 4, 5 -> CHANNEL_GROUP_ID_OUTLOOK
                    else -> CHANNEL_GROUP_ID_ALI
                }
                channels.add(channel)
            }
            manager.createNotificationChannels(channels)
        }
    }
复制代码

这样,关于通知渠道的相关总结大概就是这些。整体来讲,通知渠道的做用在于将具备相同行为的通知集合在一块儿,咱们经过通知渠道来统一管理某一个通知渠道下全部通知的行为,好比响铃,震动等。可是须要明确的是,只有用户对通知的最终表现形式拥有决定权,开发者能够设置某些属性,可是并不必定会起做用。在这种状况下读取通知渠道信息就变得很重要,能够经过读取渠道信息引导用户从新设置某些通知的表现形式。

补充建立通知操做

在上面的发送一条普通的通知栏消息的代码中,咱们首先是建立通知渠道,建立完通知渠道以后就开始建立通知,咱们使用NotificationCompat.Builder来建立一条通知消息。

在上面咱们只是简单地建立了一个通知栏消息,甚至连点击以后的操做都没有,其实对于通知消息最重要的就是这一块,下面的内容就是对这一块的内容进行填充,从而实现风格多样的通知栏消息。

建立一组通知

在以前了解了通知的渠道分组,和通知渠道,简单来讲,渠道的意义在于将具备相同功能,相同行为的通知进行归类,让开发者和用户可以对某一个渠道的具体行为进行操做。渠道分组的意义则在于按照某一种方法对渠道进行分类总结,这样作的目的在于当渠道相对较多以后用户很难区分某一个渠道的功能,对渠道进行分组,有利于开发者和用户对渠道进行管理。

通知分组在Android 7.0就开始有了,比通知渠道出现的还要早。通知分组主要是为了实现对通知UI的管理。上面说了通知渠道是对通知功能和行为进行分类,这是两者的差异。仍然使用上面提到的邮箱举例:在一个邮箱帐号下可能存在多个联系人,同一个时间段,同一个联系人可能会给咱们发送多条邮件,在没有通知分组的状况下,每一条通知都以独立的形式显示在通知栏中,通知多了以后很难进行管理。而有了通知分组以后,咱们就能够将同一个联系人的邮件归类到同一个分组之下,这样用户就能够在通知栏中对某一个联系人的通知进行管理(能够打开或者合并)。

下面的代码演示了建立通知分组的过程:

  1. 建立一个简单的通知并加入到分组中
val builder = NotificationCompat.Builder(this,CHANNEL_ID_VIDEO)
        builder.setSmallIcon(R.drawable.frank)
        builder.setContentTitle("通知分组测试")
        builder.setContentText("这是通知分组测试")
        builder.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.frank))
        builder.setGroup(NOTIFICATION_GROUP_KEY_VIDEO)
复制代码

能够看到,上面就是添加了一条普通的通知消息,惟一有区别的就是在最后加入了setGroup()属性,显示这条通知消息也和普通的通知消息没有什么不一样。

  1. 设置通知组摘要

通知组摘要说明了当前通知组下面的通知的一些简略信息,在Android7.0以及更高的版本上,系统会使用每条通知中的文本摘要,自动建立通知组的文本摘要,用户能够展开此通知来查看每条单独的通知。而对于低于Android7.0版本的系统,为了可以表现一致,则须要另外建立一条通知来充当通知组摘要。在这种状况下,系统会隐藏这个通知组下的全部通知,而仅仅显示这个通知组的信息。在这样的状况下,这个通知组的摘要就须要包含其它通知的片断,供用户点击来打开应用。

val NOTIFICATION_GROUP_KEY_VIDEO = "notificationGroupIdVideo"//通知分组的惟一标识
    val SUMMARY_GROUP_ID = 0 //通知组摘要ID 也就是显示这个通知组的时候的ID
    private fun createNotificationGroup() {
        //第一条消息
        val newMessage1 = NotificationCompat.Builder(this, "channelId_163_0").apply {
            setSmallIcon(R.drawable.frank)
            setContentTitle("第一条消息")
            setContentText("这里是第一条消息")
            setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.frank))
            setGroup(NOTIFICATION_GROUP_KEY_VIDEO)
        }.build()
        //第二条消息
        val newMessage2 = NotificationCompat.Builder(this, "channelId_163_0").apply {
            setSmallIcon(R.drawable.frank)
            setContentTitle("第二条新消息")
            setContentText("这里第二条新消息")
            setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.frank))
            setGroup(NOTIFICATION_GROUP_KEY_VIDEO)

        }.build()
        //分组摘要
        val summaryNotification = NotificationCompat.Builder(this, "channelId_163_0").apply {
            setContentTitle("通知分组测试")
            setContentText("两条新消息")
            setSmallIcon(R.drawable.frank)
            setStyle(
                NotificationCompat.InboxStyle()
                    .setBigContentTitle("两条新消息")
                    .setSummaryText("摘要内容")
            )
                .setGroup(NOTIFICATION_GROUP_KEY_VIDEO)
                .setGroupSummary(true)
        }.build()
        NotificationManagerCompat.from(this).apply {
            notify(100, newMessage1)
            notify(101, newMessage2)
            notify(SUMMARY_GROUP_ID, summaryNotification)
        }

    }
复制代码

运行上面的程序,最终会在通知栏显示这两条消息,可是这两条消息都是处于折叠状态,点击摘要区域或者下滑操做能够打开通知的详细信息。另外须要注意的是,通知分组的显示效果和当前通知所在渠道的重要性是有关系的,若是将当前渠道的重要性设置为高,则最新的消息始终是打开状态,其他的消息会折叠在摘要通知下面(在个人一加手机上是如此)。

从通知启动Activity

普通页面跳转

以前咱们已经了解了如何显示一个普通的通知栏消息,可是咱们并无设置点击通知栏消息的时候执行的操做。通常状况下咱们都会在点击通知栏后执行一些操做,好比打开Activity,或者发送一个广播之类的。下面的代码演示了如何在点击通知栏以后跳转到一个Activity页面:

//测试跳转一个普通的Activity
    private fun toActivity() {
        //建立渠道
        createToActivityChannel()
        //构建须要跳转到的Activity
        val intent = Intent(this, NotificationChannelTestActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
        val pendingIntent =
            PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        //建立通知消息
        val notification = NotificationCompat.Builder(this, mToActivityChannel).apply {
            setSmallIcon(R.drawable.frank)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.frank))
            setContentTitle("跳转Activity测试")
            setContentText("用于测试是否可以跳转到Activity")
            setAutoCancel(true)
            setContentIntent(pendingIntent)
            priority = NotificationCompat.PRIORITY_DEFAULT
        }.build()
        //发送通知
        NotificationManagerCompat.from(this).notify(100, notification)
    }
复制代码

使用上面的代码就能够在通知栏显示一条通知消息,同时,点击这条消息则能够跳转到咱们设置的NotificationChannelTestActivity这个页面。

对于通常的页面跳转使用上面的方式指定是没有问题的,可是除了上面的这种方式,Android还为咱们提供了另外的两种方式来应对不一样的状况。

常规Activity

这类的Activity是指咱们在正常使用APP的时候就会使用到的页面,所以,当用户从通知跳转到这类Activity的时候,新的任务应该包含完整的返回堆栈,以便用户能够按返回键返回到以前的页面。

好比咱们常用的通讯APP,有时候当咱们收到消息推送的时候极可能APP进程已经被杀死了,那么此时在点击通知栏的时候,虽然咱们仍然可以跳转到正常的页面,可是点击返回键的时候此时就没有正常的任务栈了,没法返回到上个页面,而是直接返回到桌面了。这个时候,咱们就能够经过TaskStackBuilder设置PendingIntent,这样就能够建立新的返回堆栈,以便用户能够执行正常的返回流程。

经过TaskStackBuilder建立返回堆栈的流程以下:

  1. 首先我这里建立了两个Activity,分别是TestTaskStackBuilderActivity1TestTaskStackBuilderActivity2,正常的流程是SummaryActivity -> TestTaskStackBuilderActivity1 -> TestTaskStackBuilderActivity2,而点击通知栏消息则会直接进入到TestTaskStackBuilderActivity2这个页面,为了可以在点击通知栏消息的时候也可以有正常的返回流程,因此首先须要在Manifest文件中注册每个ActivityparentActivityName属性:
<activity
                android:name=".notification.summary.TestTaskStackBuilderActivity2"
                android:parentActivityName=".notification.summary.TestTaskStackBuilderActivity1"></activity>
        <activity
                android:name=".notification.summary.TestTaskStackBuilderActivity1"
                android:parentActivityName=".notification.summary.SummaryActivity" />
复制代码
  1. 接下来能够经过TaskStackBuilder来建立PendingIntent:
//使用TaskStackBuilder构建PendingIntent
    private fun toActivityWithTaskStackBuilder() {
        //渠道信息
        val channelId = "testTaskStackBuilder"
        val channelName = "测试返回任务栈"
        val channelDescription = "用于测试返回任务栈的通知栏消息渠道"
        //建立渠道
        if (SdkVersionUtils.checkAndroidO()) {
            val channel =
                NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT)
            channel.description = channelDescription
            val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(channel)
        }

        //通知信息
        val notificationId = 100
        //构建要跳转的页面
        val intent = Intent(this, TestTaskStackBuilderActivity2::class.java)
        val taskBuilder = TaskStackBuilder.create(this).apply {
            this.addParentStack(TestTaskStackBuilderActivity2::class.java)
            this.addNextIntent(intent)
        }
        val pendingIntent = taskBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
        //构建通知
        val builder = NotificationCompat.Builder(this, channelId).apply {
            setSmallIcon(R.drawable.frank)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.frank))
            setContentTitle("测试返回栈")
            setContentText("测试任务返回栈的消息")
            setContentIntent(pendingIntent)
            priority = NotificationCompat.PRIORITY_DEFAULT
            setAutoCancel(true)
        }
        Handler().postDelayed({
            //发送通知
            NotificationManagerCompat.from(this).notify(notificationId, builder.build())
        }, 5000)
    }
复制代码

经过上面的代码构建的PendingIntent,在APP处于后台的时候也可以准确的跳转到对应的页面,返回时也可以按照正常的返回逻辑进行跳转。可是有一个问题在于当APP处于前台的时候,这个时候点击通知栏消息,此时跳转到指定的页面,退出的时候这个页面以前的页面也会被重建。好比此时咱们已经打开并处于SummaryActivity这个页面了,可是经过点击通知栏消息跳转到TestTaskStackBuilderActivity2页面以后,点击返回键返回到SummaryActivity的时候,这个页面又被重建了。

普通状况下这样作不会有什么问题,可是若是相似于SummaryActivity这样的页面操做逻辑不少,那么就会有问题了。好比在MainActivity咱们可能会请求后台是否有新版本更新,有的话就弹出一个Dialog,原本应该是每次打开APP获取一次,可是使用这样的通知栏消息的时候,返回到MainActivity的时候就会致使MainActivity页面被重建,又一次弹出这个Dialog,就会形成很差的体验。所以仍是要根据不一样的状况选择不一样的方式。

特殊状况下的PendingIntent

除了上面的须要返回栈的状况外,有时候咱们也会遇到某个页面只有在点击通知栏以后才会跳转过去,正常使用APP是不会跳转到这样的页面的这样的状况。对于这样的Activity,咱们能够直接使用PeindingIntent.getActivity来构建,同时配合在Manifest文件中设置的属性,就能够达到相应的效果,以下代码演示了如何建立这样的PendingIntent:

  1. 首先建立一个Activity,这个Activity只有在点击通知栏的状况下才会显示出来。为了达到这样的效果,咱们须要在Manifest文件中设置这个Activity的launchModesingleTask,同时设置taskAffinity为空,用来保证普通状况下不会进入到这个页面。同时设置excludeFromRecents属性为true,用来保证这个页面不会进入到最近任务列表中。(这里并不能保证这个页面在任何状况下都不会进入到最近任务列表,若是当前正处于这个页面,那么按下最近任务按钮,仍然会看这个页面)
<activity android:name=".notification.summary.SpecialActivity"
                android:launchMode="singleTask"
                android:taskAffinity=""
                android:excludeFromRecents="true"
                ></activity>
复制代码
  1. 构建并发出通知:
//跳转到特殊的Activity
    private fun toSpecialActivity() {
        //渠道信息
        val channelId = "testSpecialActivity"
        val channelName = "测试跳转到特殊的Activity"
        val channelDescription = "用于测试跳转到特殊Activity的通知栏消息渠道"
        //建立渠道
        if (SdkVersionUtils.checkAndroidO()) {
            val channel =
                NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT)
            channel.description = channelDescription
            val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(channel)
        }

        //通知信息
        val notificationId = 100
        //构建要跳转的页面
        val intent = Intent(this, SpecialActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent =
            PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        //构建通知
        val builder = NotificationCompat.Builder(this, channelId).apply {
            setSmallIcon(R.drawable.frank)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.frank))
            setContentTitle("测试跳转特殊Activity")
            setContentText("只有经过点击通知栏消息才能跳转到的Activity")
            setContentIntent(pendingIntent)
            priority = NotificationCompat.PRIORITY_DEFAULT
            setAutoCancel(true)
        }
        //发送通知
        NotificationManagerCompat.from(this).notify(notificationId, builder.build())

    }
复制代码

建立展开式通知

在上面咱们已经学习了关于通知的一些知识,经过上面这些知识其实咱们已经基本可以知足平常的需求,可是在上面的基础上,Android也为咱们提供了更多的通知栏样式,从而让咱们的通知可以更加个性化。展开式通知就是为了达到这样的效果,咱们能够在设置通知的时候设置不一样的Style,来达到本身的需求。

添加大图片

若是须要在通知中显示图片,则可使用BigPictureStyle

//显示大图片的通知
    private fun createBigPictureNotification() {
        val channelUtils = ChannelUtils.getInstance(this);
        //建立通道
        channelUtils.createDefaultChannel()
        //建立通知
        val builder = NotificationCompat.Builder(this, channelUtils.defaultChannelId)
        builder.setSmallIcon(R.drawable.frank)
        builder.setContentTitle("大图通知")
        builder.setContentText("大图通知")
        builder.setStyle(
            NotificationCompat.BigPictureStyle()
                .bigLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.frank))
                .bigPicture(BitmapFactory.decodeResource(resources, R.drawable.frank))
                .setSummaryText("二级标题")
        )
        builder.priority = NotificationCompat.PRIORITY_DEFAULT
        builder.setAutoCancel(true)
        //显示通知
        NotificationManagerCompat.from(this).notify(100, builder.build())
    }
复制代码

上面的代码中,经过指定建立通知时的Style属性就能够建立一个大图通知,在通知栏打开的时候,通知信息就是咱们指定的这个图片。

添加大段文本

若是想要在通知中显示大段的文本信息,则能够经过指定Style属性为BigTextStyle属性来设置:

private fun createBigTextNotification() {
        //建立一个渠道
        createTestStyleChannel()
        //通知相关的信息
        val title = "大段文本通知"
        val content = "测试大段文本的通知"
        //建立大段文本通知的style
        val style = NotificationCompat.BigTextStyle().run {
            this.bigText("这里是大段通知文本这里是大段通知文本这里是大段通知文本这里是大段通知文本这里是大段通知文本")
            this.setSummaryText("用于测试大段文本通知的style")
        }

        val notificationUtils = NotificationUtils.getInstance(this)
        //建立通知实体
        val builder = notificationUtils
            .createNotification(
                channelId,
                R.drawable.frank,
                title,
                content,
                style,
                null,
                testStyleNotificationGroupKey
            )
        //发送通知
        notificationUtils.sendNotification(builder.build())
        //建立并发送通知分组
        createTestStyleNotificationGroup()
    }
复制代码

经过上面的代码就能够成功地建立出一个包含大段文本的通知,默认状况下会显示content设置的内容,当通知展开后就会显示设置的bigText的内容。上面主要的代码是建立出style,使用到的createTestStyleChannel()createTestStyleNotificationGroup()则是两个工具类,会在本篇笔记学习完Style以后贴出来。

建立收件箱样式的通知

若是在一条通知中想要显示多个通知内容,好比收到多条邮件,每条信息展现一条通知简介,咱们则可使用InboxStyle这样的样式来设置,这里须要注意的是InboxStyle和通知分组的区别:通知分组下的每一条通知都有本身单独的行为属性,好比每一条单独的通知均可以设置样式,设置跳转页面,可是InboxStyle只是某一条通知的样式不一样,本质上只是一条通知。通知分组理论上也能够实现InboxStyle的效果,可是对于刚才的例子,咱们只想展现邮件简介而没有其它特殊内容,此时使用通知分组则有些杀鸡用牛刀的感受。

使用下面的代码能够建立InboxStyle类型的通知:

//建立收件箱样式的通知
    private fun createInboxStyleNotification() {
        //建立渠道
        createTestStyleChannel()
        //通知内容
        val title = "邮件"
        val content = "收到新邮件"
        val style = NotificationCompat.InboxStyle().run {
            this.addLine("第一条简介")
            this.addLine("第二条简介")
            this.addLine("第三条简介")
        }
        //建立消息
        val utils = NotificationUtils.getInstance(this)
        val builder = utils.createNotification(
            testStyleChannelId,
            R.drawable.frank,
            title,
            content,
            style,
            null,
            testStyleNotificationGroupKey
        )
        //发送消息
        utils.sendNotification(builder.build())
        //建立并发送通知分组
        createTestStyleNotificationGroup()
    }
复制代码

使用上面的代码则能够在通知栏中显示收件箱样式的通知消息,默认状况下显示的是contentText配置的内容,点击展开以后显示的就是咱们添加的这三条简介内容。

在通知中显示对话

使用MessagingStyle能够显示任意人数之间依序发送的消息,对于即时通信应用来讲这种方式是很是友好的,以下代码能够在通知栏建立对话通知:

//在通知中显示对话
    private fun createMessagingStyleNotification() {
        //建立渠道
        createTestStyleChannel()
        //通知消息实体
        val title = "联系人"
        val content = "联系人发来新消息"
        //建立发送消息用户的信息
        val user = Person.Builder().run {
            this.setIcon(IconCompat.createWithResource(this@SummaryActivity, R.drawable.ic_icon1))
            this.setName("Bob")
        }
        //建立style
        val style = NotificationCompat.MessagingStyle(user.build()).run {
            //添加消息
            this.addMessage("Hello World",System.currentTimeMillis() - 1000 * 60 * 60 * 24,user.build())
            this.addMessage("Hello World",System.currentTimeMillis() - 1000 * 60 * 60 * 24,user.build())
            this.addMessage(NotificationCompat.MessagingStyle.Message("Thank you",System.currentTimeMillis(),user.build()))
        }
        //建立消息
        val utils = NotificationUtils.getInstance(this)
        val builder = utils.createNotification(testStyleChannelId,R.drawable.frank,title,content,style,null,testStyleNotificationGroupKey)
        //发送消息
        utils.sendNotification(builder.build())
        //建立并发送通知分组消息
        createTestStyleNotificationGroup()
    }
复制代码

使用上面的代码会在通知栏显示对话消息,须要注意的是,显示对话消息的时候,咱们设置的contentTitlecontentText内容将不会显示,同时在通知栏默认显示两条消息,若是消息多余两条则会折叠起来,点击展开以后则会显示所有对话消息。

使用媒体控件建立通知

当咱们的应用在播放视频或者音乐的时候,咱们可能会须要在通知栏显示当前播放的曲目信息,此时就须要使用媒体控件来建立通知消息。

在使用媒体控件建立通知栏消息的时候,能够经过调用addAction来添加按钮,最多可以添加5个,用于执行不一样的操做。同时能够调用setLargeIcon()来设置专辑封面。同时,咱们还能够经过setShowActionsInCompactView()来设置通知收起以后仍然要显示的按钮。

同时,若是通知表示媒体会话,则还能够调用setMediaSession()在通知上附加MediaSession.Token,这样,系统就会将其识别为表示媒体会话的通知并相应的作出响应(例如在锁屏中显示专辑封面)

经过以下代码建立媒体控件通知:

//建立媒体控件通知
    private fun createMediaStyleNotification() {
        //建立渠道
        createTestStyleChannel()
        //建立消息实体
        //五个按钮对应的操做
        val operateAction = TestMediaStyleBroadcastReceiver::class.java.name
        //上一首
        val preIntent = Intent(operateAction)
        //preIntent.action = operateAction
        preIntent.putExtra(operateKey, "上一首")
        val prePendingIntent =
            PendingIntent.getBroadcast(this, 0, preIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        //下一首
        val nextIntent = Intent()
        nextIntent.action = operateAction
        nextIntent.putExtra(operateKey, "下一首")
        val nextPendingIntent =
            PendingIntent.getBroadcast(this, 1, nextIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        //播放或者暂停
        val stateIntent = Intent()
        stateIntent.action = operateAction
        stateIntent.putExtra(operateKey, "播放/暂停")
        val statePendingIntent =
            PendingIntent.getBroadcast(this, 2, stateIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        //收藏
        val collectionIntent = Intent()
        collectionIntent.action = operateAction
        collectionIntent.putExtra(operateKey, "收藏")
        val collectionPendingIntent =
            PendingIntent.getBroadcast(this, 3, collectionIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        //喜欢
        val likeIntent = Intent()
        likeIntent.action = operateAction
        likeIntent.putExtra(operateKey, "喜欢")
        val likePendingIntent =
            PendingIntent.getBroadcast(this, 4, likeIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        //建立消息实体
        val utils = NotificationUtils.getInstance(this)
        val style = androidx.media.app.NotificationCompat.MediaStyle().run {
            this.setShowActionsInCompactView(0, 1, 3)
        }
        val builder = utils.createNotification(
            testStyleChannelId,
            R.drawable.frank,
            R.drawable.frank,
            "My Music",
            "个人音乐",
            null,
            style,
            true,
            NotificationCompat.PRIORITY_DEFAULT,
            null
        )
        builder.addAction(R.mipmap.ic_back_black, "pre", prePendingIntent)
        builder.addAction(R.mipmap.ic_back_white, "state", statePendingIntent)
        builder.addAction(R.mipmap.ic_launcher, "next", nextPendingIntent)
        builder.addAction(R.mipmap.ic_search_white, "collection", collectionPendingIntent)
        builder.addAction(R.mipmap.ic_bg_layout_title, "like", likePendingIntent)
        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        builder.color = Color.parseColor("#336699")
        //发送消息
        utils.sendNotification(1000, builder.build())
    }
复制代码

使用上面的代码,咱们就能够建立出一个媒体通知样式的通知栏,包含5个操做按钮。

须要注意的是,若是项目使用的是androidx,那么在使用MediaStyle的时候可能出现找不到这个类的问题,此时须要导入androidxmedia库,包括以下部分:

"androidx.media2:media2-session:1.0.3",
 "androidx.media2:media2-widget:1.0.3",
 "androidx.media2:media2-player:1.0.3"
复制代码

版本号能够在官方文档中查询最新版。

其它

上面建立通知渠道和通知的工具类代码以下:

public void createChannel(
            String channelId,
            String channelName,
            int importance,
            String description,
            String groupId
    ) {
        if (moreThan8()) {
            NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
            channel.setDescription(description);
            if (!TextUtils.isEmpty(groupId))
                channel.setGroup(groupId);
            NotificationManager manager =
                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            manager.createNotificationChannel(channel);
        }
    }
复制代码
public NotificationCompat.Builder createNotification(
            String channelId,
            int smallImageRes,
            Integer largeImageRes,
            String title,
            String content,
            PendingIntent pendingIntent,
            NotificationCompat.Style style,
            boolean cancel,
            int priority,
            String groupKey
    ) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
        //小图标
        builder.setSmallIcon(smallImageRes);
        //大图标,能够不设置
        if (largeImageRes != null)
            builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), largeImageRes));
        //标题
        builder.setContentTitle(title);
        //内容
        builder.setContentText(content);
        //点击以后执行的操做,跳转页面或者发送通知等
        if (pendingIntent != null)
            builder.setContentIntent(pendingIntent);

        //设置样式
        if (style != null)
            builder.setStyle(style);

        //是否点击后能够取消
        builder.setAutoCancel(cancel);
        //重要性,这里须要和渠道中的重要性保持一致
        builder.setPriority(priority);
        //不容许添加默认操做按钮
        builder.setAllowSystemGeneratedContextualActions(false);
        if (!TextUtils.isEmpty(groupKey))
            builder.setGroup(groupKey);
        return builder;
    }
复制代码

建立基本通知

在上面咱们已经将通知这里学习的差很少了,对于不一样的需求上面的学习过程基本都涉及到了,可是还有一些比较特殊的没有涉及到,以下是一些比较特殊的设置。

添加操做按钮

其实在上面的MediaStyle的时候咱们已经添加了5个操做按钮,可是对于普通的通知,最多只能添加3个操做按钮,添加的方式和上面的操做是同样的,都是经过给Intent设置action来启动一个BroadcastReceiver或者Activity:

//添加操做按钮
    private fun createHaveActionsNotification() {
        //建立渠道
        createTestStyleChannel()
        //建立通知实体
        val title = "操做按钮"
        val content = "包含操做按钮的通知"
        //两个操做按钮
        val intent1 = Intent(TestMediaStyleBroadcastReceiver::class.java.name)
        val action1 =
            PendingIntent.getBroadcast(this, 10, intent1, PendingIntent.FLAG_UPDATE_CURRENT)
        val intent2 = Intent(this, MessagingStyleActivity::class.java)
        val action2 =
            PendingIntent.getActivity(this, 11, intent2, PendingIntent.FLAG_UPDATE_CURRENT)
        //建立通知工具类
        val utils = NotificationUtils.getInstance(this)
        val builder = utils
            .createNotification(testStyleChannelId, R.drawable.frank, title, content)
        //将操做按钮添加到通知上
        builder.addAction(R.drawable.ic_icon1, "已读", action1)
        builder.addAction(R.drawable.ic_icon4, "查看详情", action2)
        //显示通知
        utils.sendNotification(builder.build())
    }
复制代码

在上面的代码中,建立了两个操做按钮,一个点击以后会发出一个广播,一个点击以后会跳转到指定的Activity页面。

添加直接回复操做

Android7.0及以上的版本中引入了直接回复操做,用户能够在通知栏中直接添加要回复的内容而没必要打开Activity,建立直接回复的流程以下:

//添加直接回复操做的通知
    private val REPLY_TEXT_KEY = "replyTextKey"
    //回复的对象
    private val REPLY_USER_ID = "replyUserId"
    //通知id
    private val REPLAY_NOTIFICATION_ID = 5000
    private fun createHaveReplyNotification() {
        //建立通知渠道
        createTestStyleChannel()
        //通知实体
        val title = "直接回复操做的通知"
        val content = "点击下面的回复按钮能够直接回复信息"
        val utils = NotificationUtils.getInstance(this)
        val builder = utils.createNotification(testStyleChannelId, R.drawable.frank, title, content)
        //用于直接回复的`RemoteInput.Builder`
        val remoteInput = RemoteInput.Builder(REPLY_TEXT_KEY).run {
            setLabel("请输入要回复的内容")
            build()
        }
        //为回复建立PendingIntent
        val replyRequestCode = 200
        val intent = Intent(TestMediaStyleBroadcastReceiver::class.java.name)
        intent.putExtra(REPLY_USER_ID, replyRequestCode)
        val replyPendingIntent =
            PendingIntent.getBroadcast(
                this,
                replyRequestCode,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
        //建立用于回复的action 并绑定remoteInput
        val replyAction =
            NotificationCompat.Action.Builder(R.drawable.ic_icon6, "当即回复", replyPendingIntent)
                .addRemoteInput(remoteInput)
                .build()
        //将action绑定到通知上
        builder.addAction(replyAction)
        //发送通知
        utils.sendNotification(REPLAY_NOTIFICATION_ID,builder.build())
    }
复制代码

经过上面的代码就能够在状态栏建立一个可以直接回复消息的通知,在通知中会有一个当即回复的按钮,点击这个按钮就能够调出输入法,而后能够输入要回复的内容了。

不少时候咱们不只须要让用户可以直接回复,咱们还须要获取到用户回复的内容,经过如下方式能够获取到用户回复的内容:

在上面的代码中,咱们对回复按钮绑定的是一个BroadcastReceiver,那么在这个receiver中就能够获取到用户输入的内容:

//获取用户回复的内容
    val replayIntent = RemoteInput.getResultsFromIntent(intent)
    replayIntent?.let {
        Log.e("TAG","用户回复的内容:${it.getCharSequence(REPLY_TEXT_KEY)}")
    }
复制代码

在成功获取到用户输入的内容并执行完操做以后,咱们可能倾向于继续对这条通知作一些操做,或者隐藏这条通知,经过以下方式修改通知:

//收到用户回复的内容后更新通知
    createTestStyleChannel()
    val utils = NotificationUtils.getInstance(context)
    val builder = utils.createNotification(
        testStyleChannelId,
        R.drawable.frank,
        "直接回复操做的通知",
        "回复成功"
    )
    utils.sendNotification(REPLY_NOTIFICATION_ID, builder.build())
    //等待3秒钟删除掉这条通知
    Handler().postDelayed({
        utils.deleteNotification(REPLY_NOTIFICATION_ID)
    },3000)
复制代码

在上面的代码中咱们在收到回复内容并处理以后更新了这条通知,而后在等待3秒以后关闭了这条通知。

添加进度条

有时候咱们须要提醒用户一些进度信息,好比当咱们在执行下载等任务的时候,此时就能够在通知栏中添加进度条。在通知栏中的进度条分为两种,一种是可以肯定当前进度的,另一种是不能肯定当前进度,也就是不肯定结束时间的进度条,多于这两种进度条差异并不大,以下分别建立一个肯定结束时间的进度条和一个不肯定结束时间的进度条:

肯定结束时间的进度条
//建立一个肯定结束时间的进度条
    private val mNotificationUtils by lazy {
        NotificationUtils.getInstance(this)
    }
    private val mWithFinishProgressBuilder by lazy {
        //建立渠道
        createTestStyleChannel()
        mNotificationUtils.createNotification(
            testStyleChannelId,
            R.drawable.frank,
            "进度条通知",
            "包含进度条的通知"
        ).run {
            setOnlyAlertOnce(true)
        }
    }

    private var mProgress = 0;
    private val FINISH_PROGRESS_NOTIFICATION_ID = 4000
    private val mFinishNotificationHandler by lazy {
        Handler()
    }

    private fun createWithFinishNotification() {
        mWithFinishProgressBuilder.setProgress(100, mProgress, false)
        mNotificationUtils.sendNotification(
            FINISH_PROGRESS_NOTIFICATION_ID,
            mWithFinishProgressBuilder.build()
        )
        if (mProgress >= 100) {
            mFinishNotificationHandler.removeCallbacksAndMessages(null)
            mNotificationUtils.deleteNotification(FINISH_PROGRESS_NOTIFICATION_ID)
        } else {
            mProgress += 10
            mFinishNotificationHandler.postDelayed({
                createWithFinishNotification()
            }, 1000)
        }
    }
复制代码

经过上面的代码咱们就能够建立出一个肯定结束时间的进度条,上面使用Handler模拟进度条每次增长10。另外,因为这条通知渠道的重要新为IMPORTANCE_DEFAULT,因此设置setOnlyAlertOnce(true)能够保证只会响铃一次,也能够设置IMPORTANCE_LOW关闭响铃。

不肯定结束时间的进度条

不肯定结束时间的进度条和上面建立的肯定结束时间的进度条差异不大,只是在NotificationCompat.Builder.setProgress(0,0,true)传递这样的参数就能够了,以下所示:

//建立不肯定结束时间的进度条通知
    private val INDETERMINATE_NOTIFICATION_ID = 40001
    private val mIndeterminateBuilder by lazy {
        createTestStyleChannel()
        mNotificationUtils.createNotification(
            testStyleChannelId,
            R.drawable.frank,
            "进度条通知",
            "不肯定结束时间的进度条"
        ).run {
            setOnlyAlertOnce(true)
        }
    }

    private var mTime = 0;
    private fun createIndeterminateNotification() {
        mIndeterminateBuilder.setProgress(0, 0, true)
        mNotificationUtils.sendNotification(
            INDETERMINATE_NOTIFICATION_ID,
            mIndeterminateBuilder.build()
        )
        if (mTime >= 10 * 1000) {
            mFinishNotificationHandler.removeCallbacksAndMessages(null)
            mNotificationUtils.deleteNotification(INDETERMINATE_NOTIFICATION_ID)
        } else {
            mFinishNotificationHandler.postDelayed(
                {
                    mTime += 1000
                    createIndeterminateNotification()
                }, 1000
            )
        }

    }

复制代码

设置锁定屏幕公开范围

经过设置setVisibility()属性来控制通知在锁定屏幕上的显示级别,这个属性取值范围以下:

  • VISIBILITY_PUBLIC:显示完整的通知内容
  • VISIBILITY_SECRET: 不在锁定屏幕上显示任何内容
  • VISIBILITY_PRIVATE: 只显示基本信息,例如通知图标和内容标题,可是隐藏通知的完整内容。

当设置可见性为VISIBILITY_PRIVATE的时候,咱们能够提供通知的备用版本,以隐藏特定的详情信息,经过设置setPublicVersion(Notification)来将备用通知附加到普统统知之中:

//设置锁定屏幕公开范围为private并设置通知的备用版本
    private fun createPrivateVisibility() {
        //建立渠道
        createTestStyleChannel()
        //建立备用通知
        val backupBuilder = mNotificationUtils.createNotification(
            testStyleChannelId,
            R.drawable.frank,
            "新消息",
            "收到三条新消息"
        )
        //建立通知
        val person = Person.Builder().run {
            setName("Bob")
            build()
        }
        val style = NotificationCompat.MessagingStyle(person).run {
            addMessage("Hello", System.currentTimeMillis(), person)
            addMessage("World", System.currentTimeMillis(), person)
            addMessage("Thanks", System.currentTimeMillis(), person)
        }
        val builder = mNotificationUtils.createNotification(
            testStyleChannelId,
            R.drawable.frank,
            "新消息",
            "收到三条新消息",
            style
        ).run {
            setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
        }
        //设置备用通知
        builder.setPublicVersion(backupBuilder.build())
        //发送通知
        mNotificationUtils.sendNotification(builder.build())
    }
复制代码

经过上面的代码设置通知以后,若是是锁屏状态下就会显示备用通知,也就是只显示"新消息"和"收到三条新消息"的通知,打开锁屏页面后就会显示通知的真正内容,也就是设置的style中的内容。

显示紧急消息

有时候咱们可能须要显示一些紧急消息,好比来电,好比微信视频通话等,这种状况下,咱们能够将全屏Intent与通知关联,调用通知的时候,根据设备的锁定状态,用户将会看到如下状况之一:

  • 若是用户设备被锁定,会显示全屏Activity
  • 若是用户设备处于解锁状态,通知以展开形式展现,其中包含用于处理或关闭通知的选项。

如下代码演示了将通知和全屏Intent关联:

//显示紧急消息
    private fun createUrgentNotification() {
        //建立渠道
        createTestStyleChannel()
        //建立全屏Intent
        val intent = Intent(this, ImportantActivity::class.java)
        val fullScreenPendingIntent =
            PendingIntent.getActivity(this, 10000, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        //建立通知
        val utils = NotificationUtils.getInstance(this)
        val builder =
            utils.createNotification(testStyleChannelId, R.drawable.frank, "紧急通知", "Hello World")
        //关联全屏Intent
        builder.setFullScreenIntent(fullScreenPendingIntent, true)
        //发送通知
        utils.sendNotification(builder.build())

    }
复制代码

在实际测试中,除了须要设置上面的属性,还须要在要打开的Activity中设置下面的属性才能在锁屏页面直接打开要显示的Activity

override fun onCreate(savedInstanceState: Bundle?) {
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        //设置如下属性在锁屏显示通知的时候直接打开本页面
        window.addFlags(
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                    or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
        )

        super.onCreate(savedInstanceState)
    }
复制代码

须要注意的是,以上代码的效果仅在个人一加5手机上测试经过,在其它手机上并无测试过。

相关文章
相关标签/搜索