前言

小编在前面讲Service的时候用到了通知,还记得我们用来干嘛吗?没错,为了在程序退到后台后前台依然知道该程序的存在,在音乐、地图等APP非常常见。

通知是啥?拿起你的智能机,下拉一下通知栏。看到了吧!

但是,通知的设计初衷是好的,后来却被开发者玩坏了。

开发者为了增加自己的应用程序的打开率,发送各种各样的通知以博取更多的展示机会。作为用户的我们对这些垃圾信息非常厌恶。

虽然Android系统允许我们将某个应用程序的通知完全屏蔽,以防止它一直给我们发送垃圾信息,但是在这些信息中,也可能会有我们关心的内容。比如说我希望收到某个我关注的人的微博更新的消息,但是却不想微博一天到晚给我推一些垃圾信息。再过去,用户没有办法对这些信息做区分,要么同意接受所有信息,要么屏蔽所有信息,这也是Android通知功能的痛点。后来……

通知渠道

后来,Android8.0之后引入了通知渠道。

通知渠道就是每条通知都要属于一个对应的渠道。每个应用都可以自由地创建当前应用的通知渠道,但是这些通知渠道的控制权是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动或者是否要关闭这个渠道的通知。

拥有了控制权之后,用户就再也不用怕那些垃圾通知了,因为用户可以自主地选择关心那些通知。比如,微博可以创建两种通知渠道,一个关注,一个推荐。作为用户的我,就可以关闭推荐的通知渠道。

通知的创建

通知的创建步骤千篇一律,只是因为不同的需求而导致细节不一样。

先贴代码,咱们再逐个讲解

val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
val channel = NotificationChannel("channelId","NAME",NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
binding.sendNotification.setOnClickListener {
val intent = Intent(this,MainActivity::class.java)
val pi = PendingIntent.getActivity(this,0,intent,0)
val notification = NotificationCompat.Builder(this,"channelId")
.setContentTitle("我是一条通知")
.setContentText("10月29日,在文昌航天发射场的测试发射大厅,工作人员组织了发射前最后一次系统间全区合练。各系统完成了相关功能检查,准备就绪,等待正式发射任务。“梦天”实验舱与长征五号B遥四运载火箭组合体在10月25日转运至发射区。")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.large_icon))
.setContentIntent(pi)
.setAutoCancel(true)
.build()
manager.notify(NOTIFY_ID,notification)
}
  1. 首先需要一个NotificationManager对通知进行管理,可以通过调用Context的getSystemService()方法获取。getSystemService()方法接收一个字符串参数用于确定获取系统的哪个服务,这里我们传入Context.NOTIFICATION_SERVICE即可;

  2. 创建通知渠道NotificationChannel,需要至少三个参数(渠道Id,渠道名称,重要等级)

    1. 渠道Id,自定义,只要全局唯一就可以;
    2. 渠道名称,展示给用户看的
    3. 重要等级:有IMPORTANCE_HIGH、IMPORTANCE_DEFAULT、IMPORTANCE_LOW,IMPORTANCE_MIN这几种,对应的重要程度一次从高到低。不同的通知等级会决定通知的不同行为。比如QQ的消息通知就是高重要等级,会弹出横幅、发出声音。

    因为NotificationChannel类和createNotificationChannel()方法都是Android8.0系统中新增的API,所以在使用的时候还需要加上一个版本判断Build.VERSION.SDK_INT>=Build.VERSION_CODES.O

  3. 构建通知

    需要一个Builder构造器来构建Notification对象,但问题在于,Android系统每一个版本都会对通知进行或多或少的修改,API不稳定问题凸显的尤为严重,所以AndroidX库中提供了NotificationCompat类,使用这个类的构造器创建Notification对象,就可以保证我们的程序在所有Android系统版本上能够正常工作了(AndroidX中很多xxxCompat类都是为了版本兼容)

    然后就是一堆set方法,大家看名字应该就能看出来是干嘛的。

  4. 以上工作都完成后,只需要调用NotificationManagernotify()方法就可以让通知显示出来了。

    notify()接收两个参数:第一个参数是id,要保证为每个通知指定的id都是不同的,第二个则是Notification对象,这里直接将我们刚刚创建好的Notification对象传入即可。

  5. 但是如果仅仅按上述步骤操作,我们的通知仅仅起显示的作用,那么像我们平时收到的通知都是可以点击再进入应用的,细心的你应该发现了上述代码中还有一个关键的东西没提到——PendingIntent

    PendingIntent,从名字上就知道是一个特殊的Intent。它们都能指明某一个“意图”,都可以用于启动Activity、启动Service以及发送广播等。不同的是,Intent倾向于立即执行某个动作,而PendingIntent倾向于在某个合适的时机执行某个动作。所以,也可以把PendingIntent简单理解为延迟执行的Intent。

    PendingIntent的用法同样很简单,主要提供了几个静态方法用于获取PendingIntent的实例,可以根据要求来选择是使用getActivity()、getBroadcast(),还是getService()方法。接收的参数都是相同的:

    • 第一个参数是Context
    • 第二个参数一般用不到,传0即可
    • 第三个参数是Intent,把我们的Intent创建传入即可
    • 第四个参数用于确定PendingIntent的行为,有FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这4个值可选,具体请查看官网使用文档,通常情况下传0即可。
  6. 还有一点,我们还需要让系统状态栏的图标消失(也就是我们上方设置的smallIcon),不然的话这个小图标就一直悬挂着。

    有两种解决方案:

    • 第一种:像上方代码一样setAutoCancel(true)

    • 第二种:调用manager.cancel(ID),还记得我们在发起通知时manager.notify(NOTIFY_ID,notification)吗?没错,传入的ID就是这里传入的NOTIFY_ID,当然这个ID是自创的唯一的,看下方代码就明白了。

      class MainActivity : AppCompatActivity() {
      companion object{
      const val NOTIFY_ID = 1
      }
      private lateinit var binding:ActivityMainBinding
      override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      binding = ActivityMainBinding.inflate(layoutInflater)
      setContentView(binding.root)
      val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
      manager.cancel(NOTIFY_ID)
      ...
      binding.sendNotification.setOnClickListener {
      ...
      manager.notify(NOTIFY_ID,notification)
      }

      }
      }

    来看效果展示:

    通知demo

通知的进阶技巧

当然,在上面我们只讲述了基本的通知创建,NotificationCompat.Builder中还提供了非常丰富的API,以便我们展示更好的通知效果。我们来学习一些常用的API。

先来看看setStyle()方法,这个方法允许我们构建出富文本的通知内容。也就是说,通知中不光可以有文字和图标,还可以包含很多东西。setStyle()方法接收一个NotificationCompat.Style参数,这个参数就是用来构建具体的富文本信息的,比如长文字、图片等。

val notification = NotificationCompat.Builder(this,"channelId")
.setContentTitle("我是一条通知")
.setStyle(NotificationCompat.BigTextStyle().bigText("10月29日,在文昌航天发射场的测试发射大厅,工作人员组织了发射前最后一次系统间全区合练。各系统完成了相关功能检查,准备就绪,等待正式发射任务。“梦天”实验舱与长征五号B遥四运载火箭组合体在10月25日转运至发射区。"))
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.large_icon))
.setContentIntent(pi)
.setAutoCancel(true)
.build()

看下方效果,可以明显看出与setContentText的区别,能够显示完全部文字,而不是以省略号结尾。

image-20221031213742453

还能设置大图片,通过NotificationCompat.BigPictureStyle对象,这个对象就是用于设置大图片的,然后调用它的bigPicture()方法并将图片传入。

val notification = NotificationCompat.Builder(this,"channelId")
.setContentTitle("我是一条通知")
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources,R.drawable.big_image)))
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.large_icon))
.setContentIntent(pi)
.setAutoCancel(true)
.build()

image-20221031214224887

总结

好了,我欠的一篇关于通知的文章就完成了。其实通知的创建步骤是固定的,只是因为我们的不同需求而处理更多的细节。

再来回忆一下创建通知的3步:1、创建NotificationManager;2、创建通知渠道NotificationChannel;3、通过NotificationCompat.Builder创建通知实体。

参考:《第一行代码》