Android中Notification的使用详解

文章目录


前言

本文主要介绍Notification的使用方式,以及如何通过自定义布局满足不同的需求。


一、基础使用方法

通过下面的工具类可以直接使用,包含了通知本体的点击事件

kotlin 复制代码
object NotificationUtils {
    private const val NOTIFICATION_SHOW = 1 //通知展示与消失的识别码

    // ===========PendingIntent请求码===============
    const val NOTIFICATION_ACTIVITY = 100 //跳转activity请求码
    const val NOTIFICATION_SERVICE = 101 //跳转service请求码
    const val NOTIFICATION_BOARDCAST = 102 //跳转Boardcast请求码
    private const val NOTIFICATION_CHANNEL_ID = "notification_channel_default" //通知渠道的id

    private var notificationManager: NotificationManager? = null
    private var intentMode = NOTIFICATION_ACTIVITY //选择跳转的目标类型
    private var notificationTitle = "" //通知的标题
    private var notificationText = "" //通知的内容
    private var smallIcon = 0 //小图标的资源id
    private var largeIcon = 0 //下拉后图标的资源id


    /**
     *  @describe: 通知的展示
     *  @params:
     *     context 上下文
     *     intent 跳转目标的上下文
     *     intentMode 跳转目标的类型
     *     notificationTitle 通知的标题
     *     notificationText 通知的内容
     *     smallIcon 通知的小图标资源id
     *     largeIcon 下拉后展示的图标资源id
     *  @return:
     */
    fun show(
        context: Context,
        intent: Intent,
        intentMode: Int,
        notificationTitle: String,
        notificationText: String,
        smallIcon: Int,
        largeIcon: Int,
        chanelName: String? = "消息通知渠道"
    ) {
        this.intentMode = intentMode
        this.notificationTitle = notificationTitle
        this.notificationText = notificationText
        this.smallIcon = smallIcon
        this.largeIcon = largeIcon

        //获取到管理类
        notificationManager =
            context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        //8.0开始必须设置通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //参数:渠道唯一标识,用户可见的频道名称(长按通知会显示名称),决定了响铃/震动/横幅等行为的重要级别
            val channel = NotificationChannel(
                NOTIFICATION_CHANNEL_ID, chanelName,
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                description = "通用的通知渠道" //显示在系统设置里的渠道副标题
                enableVibration(false) //是否允许震动
                enableLights(false) //是否允许闪光灯开启
                setSound(null, null) //可设置自定义提示音
            }
            Log.i("TAG", "show: ")
            notificationManager?.createNotificationChannel(channel)
        }

        //发送
        notificationManager?.notify(NOTIFICATION_SHOW, getNotification(context, intent))
    }


    /**
     *  @describe: 获取通知必须的Notification
     *  @params:
     *  @return:
     */
    private fun getNotification(context: Context, intent: Intent): Notification {
        val bitmap = if (largeIcon != 0) BitmapFactory.decodeResource(context.resources, largeIcon)
        else BitmapFactory.decodeResource(context.resources, R.drawable.touming9)


        val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
            .setSmallIcon(if (smallIcon != 0) smallIcon else R.mipmap.ic_launcher) //设置状态栏小图标显示,只会展示符合当前设备主题的单色显示
            .setLargeIcon(bitmap) //设置状态栏下拉时的图标
            .setContentTitle(notificationTitle) //状态栏下拉时展示的标题
            .setContentText(notificationText) //状态栏下拉时展示的内容
            .setWhen(System.currentTimeMillis()) //设置下拉后展示当前的时间
            .setOngoing(true) //设置不能通过滑动删除通知
            .setContentIntent(getPendingIntent(context, intent)) //设置通知整体点击事件
            .build()

        return notification
    }

    /**
     *  @describe: 设置通知的点击事件跳转的目标,可以设置的意图有activity、service、Boardcast三种
     *  @params:
     *  @return:
     */
    private fun getPendingIntent(context: Context, intent: Intent): PendingIntent {

        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE //FLAG_IMMUTABLE表示内容不会改动,防止外部篡改
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT  //FLAG_UPDATE_CURRENT为更新Intent中内容
        }

        val pendingIntent = when (intentMode) { //根据不同目标进行对应类型的跳转
            NOTIFICATION_ACTIVITY -> {
                PendingIntent.getActivity(
                    context,
                    NOTIFICATION_ACTIVITY,
                    intent,
                    flags
                )
            }

            NOTIFICATION_SERVICE -> {
                PendingIntent.getService(
                    context,
                    NOTIFICATION_SERVICE,
                    intent,
                    flags
                )
            }

            NOTIFICATION_BOARDCAST -> {
                PendingIntent.getBroadcast(
                    context,
                    NOTIFICATION_BOARDCAST,
                    intent,
                    flags
                )
            }

            else -> {}
        }

        return pendingIntent as PendingIntent
    }

    /**
     *  @describe: 通知的隐藏
     *  @params:
     *  @return:
     */
    fun hide() {
        notificationManager?.cancel(NOTIFICATION_SHOW)
    }
}



使用:
//当前案例为跳转目标activity
  NotificationUtils.show(
                this@MainActivity,
                Intent(this@MainActivity, MainActivity::class.java),
                NotificationUtils.NOTIFICATION_ACTIVITY,
                "标题",
                "内容区",
                R.drawable.touming9,
                R.drawable.touming9
            )

api33以上需要动态申请权限

kotlin 复制代码
清单文件中添加权限:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />


进行权限申请后发送:
 private val requestNotificationPermissionLauncher =
        registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { granted ->
            if (granted) {
                Log.i("Permission", "通知权限已授予")
                // 可以安全显示通知
                NotificationUtils.show(
                    this@MainActivity,
                    Intent(this@MainActivity, MainActivity::class.java),
                    NotificationUtils.NOTIFICATION_ACTIVITY,
                    "标题",
                    "内容区",
                    R.drawable.touming9,
                    R.drawable.touming9
                )
            } else {
                Log.w("Permission", "通知权限被拒绝")
                // 可提示用户:去设置页开启
            }
        }

    override fun initListener() {
        textBtn.setOnClickListener {
            if (Build.VERSION.SDK_INT >= 33 &&
                ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                requestNotificationPermissionLauncher.launch(
                    Manifest.permission.POST_NOTIFICATIONS
                )
            } else
                //进行通知发送
                NotificationUtils.show(
                    this@MainActivity,
                    Intent(this@MainActivity, MainActivity::class.java),
                    NotificationUtils.NOTIFICATION_ACTIVITY,
                    "标题",
                    "内容区",
                    R.drawable.touming9,
                    R.drawable.touming9
                )
        }
    }

二、自定义布局

由于自定义布局随机因素太多,因此没有工具类制作的意义,所以这里放上案例以供参考。

kotlin 复制代码
帮助类:
object NotificationHelper {

    //通知渠道的id
    private const val CHANNEL_ID = "notification_channel"

    //用户可见的通知渠道名称(长按状态栏通知内容可显示)
    private const val CHANNEL_NAME = "音乐播放中"

    //==================id==========================

    //用于标识当前通知的唯一id,如果同一个id多次发送则会更新此通知
    private const val NOTIFICATION_ID = 100

    //通知主体的点击事件返回码
    private const val PENDINGINTENT_ID = 200
    //通知自定义布局中按钮1识别码
    private const val PENDINGINTENT_CUSTOM_BTN1 = 210
    //通知自定义布局中按钮2识别码
    private const val PENDINGINTENT_CUSTOM_BTN2 = 220


    var notificationManager: NotificationManager? = null

    /**
     *  @describe: 设置通知的显示
     *  @params:
     *  @return:
     */
    fun show(context: Context) {

        //获取通知管理类
        notificationManager =
            context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        //设置8.0以上所需的通知渠道
        setNotificationChannel()
        val notification = getNotification(context)
        Log.i("NotificationHelper", "show: 通知已发送")
        //发送
        notificationManager?.notify(NOTIFICATION_ID,notification)


    }


    private fun getNotification(context: Context): Notification {
        val remoteViews = getCreateCustomView(context) //设置折叠态的布局
        val bigRemoteViews = getCreateBigCustomView(context) //设置展开时自定义布局相关
        //api31及以上强制对折叠态高度有所要求
        val targetRemoteViews = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
              remoteViews
        }else{
             bigRemoteViews
        }

        val pendingIntent = getPendingIntent(
            context,
            Intent(context, MainActivity::class.java),
            PENDINGINTENT_ID
        ) //获取通知主体点击意图

        val notification = NotificationCompat.Builder(context,CHANNEL_ID)
            .setSmallIcon(R.drawable.touming9) //设置通知小图标
            .setCustomContentView(targetRemoteViews) //高版本的折叠布局 限高64dp
            .setCustomBigContentView(bigRemoteViews) //高版本的展开布局 限高256dp
            .setCustomHeadsUpContentView(bigRemoteViews) //横幅通知的布局(紧急程度至少为IMPORTANCE_HIGH,且有打断性行为则会展示)
            .setStyle(
                androidx.media.app.NotificationCompat.MediaStyle()
                    .setShowActionsInCompactView(0, 1)
            )
            .setOngoing(true) //设置滑动不删除
            .setColor(ContextCompat.getColor(context, R.color.black)) //添加通知颜色,来适配小图标颜色
            .setContentIntent(pendingIntent) //设置主体点击事件
            .build() //构建
        Log.i("NotificationHelper", "getNotification: 通知创建成功notification=$notification")
        return notification
    }


    /**
     *  @describe: 折叠态的自定义布局,31及以上高度最高只显示64dp,通常只放置一个TextView
     *  来显示内容,不放置imageView,因为最前面必带一个用setSmallIcon设置的小图标,布局的高度
     *  最好也设置不超过48dp以保证良好的显示
     *  @params: 
     *  @return: 
     */
    private fun getCreateCustomView(context: Context): RemoteViews {
        //设置折叠态布局
        val remoteViews = RemoteViews(context.packageName,R.layout.notification_custom_small)
        //设置折叠态布局中的控件
        remoteViews.setTextViewText(R.id.nitification_small_text,"播放中:歌曲名")
        return remoteViews
    }


    /**
     *  @describe: 创建展开时通知的自定义布局,以及设置内容显示等(不支持ConstraintLayout布局)
     *  @params:
     *  @return:
     */
    private fun getCreateBigCustomView(context: Context): RemoteViews {
        //设置自定义布局
        val remoteViews = RemoteViews(context.packageName, R.layout.notification_custom)
        //设置自定义布局中控件的显示
        remoteViews.setTextViewText(R.id.notification_custom_title, "自定义标题")
        remoteViews.setTextViewText(R.id.notification_custom_text, "自定义内容")

        //设置自定义布局中的点击事件
        //按钮1
        val pendingIntent1 = getPendingIntent(context,Intent(context, MainActivity::class.java).apply {
            putExtra("custom","btn1")
        },PENDINGINTENT_CUSTOM_BTN1)

        remoteViews.setOnClickPendingIntent(R.id.notification_custom_btn1,pendingIntent1)
        //按钮2
        val pendingIntent2 = getPendingIntent(context,Intent(context, MainActivity::class.java).apply {
            putExtra("custom","btn2")
        },PENDINGINTENT_CUSTOM_BTN2)
        remoteViews.setOnClickPendingIntent(R.id.notification_custom_btn2,pendingIntent2)

        return remoteViews
    }


    /**
     *  @describe: 通知主体的点击事件
     *  @params:
     *  @return:
     */
    private fun getPendingIntent(
        context: Context,
        intent: Intent,
        requestCode: Int
    ): PendingIntent {
        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //可以更新数据    当前PendingIntent不可变,外部无法进行修改(不适用可回复通知与气泡通知)
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }
        val pendingIntent = PendingIntent.getActivity(context, requestCode, intent, flags)
        Log.i("NotificationHelper", "getPendingIntent: 自定义内容创建pendingIntent=$pendingIntent")
        return pendingIntent
    }


    /**
     *  @describe: 8.0以上必须设置通知渠道
     *  @params:
     *  @return:
     */
    private fun setNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //渠道唯一标识符,长按通知时会显示的渠道名称,决定了响铃/震动/横幅等提示的重要等级
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
               
                description = "音乐播放通知,显示播放状态并支持快捷操作" //添加渠道说明
                enableVibration(false) //是否允许震动
                enableLights(false) //是否允许闪光灯开启
                setSound(null, null) //可设置自定义提示音
            }
            Log.i("NotificationHelper", "setNotificationChannel:渠道创建成功channel=$channel ")
            //创建通知渠道
            notificationManager?.createNotificationChannel(channel)
        }
    }


    fun hide() {
        notificationManager?.cancel(NOTIFICATION_ID)
    }

}

为了防止通知里按钮重复跳转当前页面,造成多个相同页面重复进栈,可以将该页面的启动模式设置为 android:launchMode="singleInstance"

kotlin 复制代码
使用时:
class MainActivity : BaseActivity() {
    private lateinit var sendBtn: Button
    private lateinit var cancelBtn: Button


    override fun setViewId(): Int = R.layout.activity_main

    override fun initView() {
        sendBtn = findViewById(R.id.send_btn)
        cancelBtn = findViewById(R.id.cancel_btn)
    }


    //当页面启动方式为singleInstance时,会调用onNewIntent将新intent传入,所以原来的getIntent获取的是以前初次启动时的旧数据
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        setIntent(intent) //更新当前的intent为新intent
        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
        val key = intent?.getStringExtra("custom")
        Log.i("MainActivity", "onStart: key=$key")
        when (key) {
            "btn1" -> {
                Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show()
            }
            "btn2" -> {
                Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show()
            }
        }

    }


    private val requestNotificationPermissionLauncher =
        registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { granted ->
            if (granted) {
                Log.i("Permission", "通知权限已授予")
                // 可以安全显示通知
                NotificationUtils.show(
                    this@MainActivity,
                    Intent(this@MainActivity, MainActivity::class.java),
                    NotificationUtils.NOTIFICATION_ACTIVITY,
                    "标题",
                    "内容区",
                    R.drawable.touming9,
                    R.drawable.touming9
                )
            } else {
                Log.w("Permission", "通知权限被拒绝")
                // 可提示用户:去设置页开启
            }
        }

    override fun initListener() {
        sendBtn.setOnClickListener {
            if (Build.VERSION.SDK_INT >= 33 &&
                ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                requestNotificationPermissionLauncher.launch(
                    Manifest.permission.POST_NOTIFICATIONS
                )
            } else
            //进行通知发送
                NotificationHelper.show(this)
        }

        cancelBtn.setOnClickListener {
            NotificationHelper.hide()
        }
    }
}
相关推荐
·云扬·1 小时前
MySQL Binlog落盘机制深度解析:性能与安全性的平衡艺术
android·mysql·adb
phltxy2 小时前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
tb_first2 小时前
LangChain4j简单入门
java·spring boot·langchain4j
独自破碎E2 小时前
【BISHI9】田忌赛马
android·java·开发语言
范纹杉想快点毕业3 小时前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc
Byron07073 小时前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js
smileNicky3 小时前
布隆过滤器怎么提高误差率
java
それども3 小时前
分库分表的事务问题 - 怎么实现事务
java·数据库·mysql
Java面试题总结3 小时前
基于 Java 的 PDF 文本水印实现方案(iText7 示例)
java·python·pdf