Android FCM推送及通知栏展示

news2025/2/11 4:28:57

需求:

实现FIrebase Cloud Message推送功能,用户收到通知后,可以悬浮通知,自定义的大/小通知展示在通知栏,判断前台/后台,点击后进行跳转。

步骤:

一、配置及接入依赖库

1.下载 google-services.json 并放入 app/ 目录

2.项目里:

dependencies {
    classpath 'com.google.gms:google-services:4.4.0' // 确保使用最新版本
}

3.app的build.gradle 

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services'
}

dependencies {
    implementation 'com.google.firebase:firebase-messaging:23.2.1' // 最新版本
}

tips:Android 13及以后,必须动态申请通知权限哦,不然不给展示 

二、FirebaseMessagingService实现接收通知

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        Log.e("FCM", "New Token:$token")
        BaseApp.pushId = token
        UserDataUtils.toUpdate(token)//上传给服务器
    }


    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        LogUtils.e("FCM", "remoteMessage: $remoteMessage")
        remoteMessage.notification?.let {
            Log.e("FCM", "Message Notification Title: ${it.title}")
            Log.e("FCM", "Message Notification Body: ${it.body}")
        }
        remoteMessage.data.let {
            Log.e("FCM", "Message Data Payload: $it")
        }
        sendNotification2(remoteMessage.data)//我这里使用的是data里的数据
    }

    private fun sendNotification2(data: MutableMap<String, String>) {
        val channelId = "default_channel" // 通知通道 ID
        val channelName = "General Notifications" // 通知通道名称

        // 判断目标 Activity
        val targetActivity: Class<*> = if (isAppAlive(this, packageName)) {
            LogUtils.e("FCM", "isAppAlive ${isAppAlive(this, packageName)} isBackGround ${BaseApp.isBackGround}")
            if (BaseApp.isBackGround) SplashAc::class.java else PlayDetailAc::class.java
        } else {
            SplashAc::class.java
        }

        val shortData = data["payload"]
        val gson = Gson()
        val shortPlay = gson.fromJson(shortData, ShortPlay::class.java)

        // 跳转逻辑
        val intent = Intent(this, targetActivity).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
            if (targetActivity == PlayDetailAc::class.java) {
                putExtra(EXTRA_SHORT_PLAY, shortPlay)
            } else {
                putExtra(AppConstants.SHORTPLAY_ID, shortPlay) // 冷启动时传递自定义数据
            }
        }
        val pendingIntent = PendingIntent.getActivity(
            this, System.currentTimeMillis().toInt(), intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // 获取通知管理器
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // 创建通知通道(适配 Android 8.0+)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH).apply {
                description = "This channel is used for general notifications"
                enableLights(true)
                lightColor = Color.BLUE
                enableVibration(true)
            }
            notificationManager.createNotificationChannel(channel)
        }

        val radiusPx = (12 * resources.displayMetrics.density).toInt()
        val requestOptions = RequestOptions()
            .transform(CenterCrop(), RoundedCorners(radiusPx))
            .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
            .placeholder(R.mipmap.card_default)
            .error(R.mipmap.card_default)

        val customView = RemoteViews(packageName, R.layout.custom_notification_layout).apply {
            setTextViewText(R.id.notification_title, shortPlay.title ?: "Chill Shorts")
            setTextViewText(R.id.notification_message, shortPlay.desc ?: "Chill Shorts")
        }

        val smallCustomView = RemoteViews(packageName, R.layout.custom_notification_small_layout).apply {
            setTextViewText(R.id.notification_title, shortPlay.title ?: "Chill Shorts")
            setTextViewText(R.id.notification_message, shortPlay.desc ?: "Chill Shorts")
        }

        // 使用 Glide 加载图片并构建通知
        Glide.with(this).asBitmap().apply(requestOptions).load(shortPlay.coverImage).into(object : CustomTarget<Bitmap>() {
            override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
                // 设置图片到自定义视图
                customView.setImageViewBitmap(R.id.notification_icon, resource)
                smallCustomView.setImageViewBitmap(R.id.notification_icon, resource)
                // 构建通知
                val notificationBuilder = NotificationCompat.Builder(this@MyFirebaseMessagingService, channelId)
                    .setStyle(NotificationCompat.DecoratedCustomViewStyle())
                    .setCustomHeadsUpContentView(smallCustomView)
                    .setSmallIcon(R.mipmap.app_logo_round)
                    .setCustomContentView(smallCustomView)
                    .setCustomBigContentView(customView)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setContentIntent(pendingIntent)
                    .setAutoCancel(true)
                val notification = notificationBuilder.build()
                notification.flags = Notification.FLAG_AUTO_CANCEL
                // 发送通知
                notificationManager.notify(System.currentTimeMillis().toInt(), notification)
            }

            override fun onLoadFailed(errorDrawable: Drawable?) {
                // 图片加载失败,使用默认占位图
                customView.setImageViewResource(R.id.notification_icon, R.mipmap.card_default)
                smallCustomView.setImageViewResource(R.id.notification_icon, R.mipmap.card_default)

                // 构建通知
                val notificationBuilder = NotificationCompat.Builder(this@MyFirebaseMessagingService, channelId)
                    .setStyle(NotificationCompat.DecoratedCustomViewStyle())
                    .setCustomHeadsUpContentView(smallCustomView)
                    .setSmallIcon(R.mipmap.app_logo_round)
                    .setCustomContentView(smallCustomView)
                    .setCustomBigContentView(customView)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setContentIntent(pendingIntent)
                    .setAutoCancel(true)
                // 发送通知
                notificationManager.notify(System.currentTimeMillis().toInt(), notificationBuilder.build())
            }

            override fun onLoadCleared(placeholder: Drawable?) {
                // 清理资源时无操作
            }
        })
    }


    private fun isAppAlive(context: Context, packageName: String): Boolean {
        val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val appProcesses = activityManager.runningAppProcesses
        appProcesses?.forEach {
            if (it.processName == packageName && it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                return true
            }
        }
        return false
    }


}

三、注意事项和代码逻辑

1.RemoteMessage.data获取控制台的配置的数据

2.isAppAlive判断当前App是否存活,isBackGround判断App是否处于后台

3.intent和pendingIntent要设置正确的,合适的flag

常见的 PendingIntent Flags

Flag说明
FLAG_CANCEL_CURRENT取消当前已有的 PendingIntent,并创建新的 PendingIntent(适用于要确保新的 Intent 被处理的情况)。
FLAG_UPDATE_CURRENT更新已存在的 PendingIntent,保持 IntentExtras 最新。
FLAG_NO_CREATE如果 PendingIntent 存在,则返回它;否则返回 null,不会创建新的 PendingIntent
FLAG_ONE_SHOTPendingIntent 只能使用一次,执行后自动销毁。
FLAG_IMMUTABLE (API 23+)PendingIntent 不能被修改(Android 12 及以上必须显式指定 FLAG_IMMUTABLEFLAG_MUTABLE)。
FLAG_MUTABLE (API 31+)PendingIntent 可以被 AlarmManagerNotificationManager 等修改,适用于 Foreground Service 及 Remote Input。

4.常见的 Notification Flags

Flag说明
Notification.FLAG_AUTO_CANCEL点击通知后自动取消(移除通知)
Notification.FLAG_ONGOING_EVENT使通知成为前台通知(用户不能手动清除)
Notification.FLAG_NO_CLEAR不能通过滑动或清除按钮删除通知
Notification.FLAG_FOREGROUND_SERVICE适用于前台服务的通知
Notification.FLAG_INSISTENT让通知的声音、震动等一直持续,直到用户处理

5. 记得适配NotificationChannel。

6.RemoteViews是用于设置通知的自定义View的,在上述的代码里,我设置了

val notificationBuilder = NotificationCompat.Builder(this@MyFirebaseMessagingService, channelId)
    .setStyle(NotificationCompat.DecoratedCustomViewStyle())
    .setCustomHeadsUpContentView(smallCustomView)//悬浮通知
    .setSmallIcon(R.mipmap.app_logo_round)
    .setCustomContentView(smallCustomView)//正常的自定义通知view
    .setCustomBigContentView(customView)//展开后的大通知View
    .setPriority(NotificationCompat.PRIORITY_HIGH)
    .setContentIntent(pendingIntent)
    .setAutoCancel(true)
val notification = notificationBuilder.build()
notification.flags = Notification.FLAG_AUTO_CANCEL
// 发送通知
notificationManager.notify(System.currentTimeMillis().toInt(), notification)

 7.在 Android 8.0 (API 26) 及更高版本,官方建议使用 NotificationChannel 控制通知行为,而不是直接使用 flags。使用.setAutoCancel(true) // 等价于 FLAG_AUTO_CANCEL,但是上面为啥我加了notification.flags = Notification.FLAG_AUTO_CANCEL,是因为设置自定义的通知,似的setAutoCancel失效了,所以又对flag进行了配置。(这个坑了我好一会儿)

四、注册 FirebaseMessagingService

manifest.xml

<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

五、获取Token

 FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
            if (task.isSuccessful) {
                LogUtils.e("FCM Token", "token:${task.result}")
                pushId = task.result // 这是你的 Firebase Push ID
                UserDataUtils.toUpdate(pushId.toString())//上传后端
            } else {
                LogUtils.e("FCM Token", "获取 Firebase Push ID 失败")
            }
        }

📌 案例:IM 消息推送通知的设计与优化

常见问题及其优化:

🟢 问题 1:消息推送通知丢失
🛠 问题描述
  • IM 消息是通过 FCM(Firebase Cloud Messaging)厂商推送(小米、华为、OPPO) 发送的,有时候通知收不到,比如:
    • App 进程被杀死,无法接收到推送。
    • Android 8.0+ 以后,应用后台时间过长,FCM 推送可能被系统限制。
    • 部分国产 ROM 对后台进程的管控严格,通知会被系统拦截。
🚀 解决方案
  • FCM 作为主要推送通道(Google Play 设备)。
  • 厂商通道(小米、华为、OPPO、vivo)作为备用推送通道。
  • 长连接保活(用户在线时,直接使用 WebSocket 推送)。
  • 检测推送通道是否可用

    • 如果 FCM 无法收到消息,就尝试 WebSocket轮询 获取未读消息。
  • 使用 WorkManager 确保消息送达

    • onMessageReceived() 里保存消息,防止推送丢失。
    • 结合 WorkManager 定时检查未读消息。

🟢 问题 2:重复通知、通知不合并

🛠 问题描述
  • 多条 IM 消息推送后,每条消息都会弹出 独立通知,导致通知栏很混乱。
  • 例如:
    • 5 条新消息,出现 5 个通知。
    • 点击某个通知后,其他通知还在。
🚀 解决方案
  1. 使用 setGroup() 进行通知分组

    • 单聊消息:不同用户的聊天,不同 ID(notify(userID, notification))。
    • 群聊消息:同一个群的消息,使用 setGroup() 归类。
      val groupKey = "IM_GROUP_CHAT"
      
      // 子通知
      val messageNotification = NotificationCompat.Builder(this, channelId)
          .setContentTitle("新消息")
          .setContentText("你有 3 条未读消息")
          .setSmallIcon(R.drawable.ic_message)
          .setGroup(groupKey)
          .build()
      
      // 汇总通知(id = 0,保证只有一个)
      val summaryNotification = NotificationCompat.Builder(this, channelId)
          .setContentTitle("IM 消息")
          .setContentText("你有新的消息")
          .setSmallIcon(R.drawable.ic_message)
          .setGroup(groupKey)
          .setGroupSummary(true)
          .build()
      
      notificationManager.notify(1, messageNotification)
      notificationManager.notify(0, summaryNotification)
      

🟢 问题 3:通知点击后跳转异常

🛠 问题描述
  • 用户点击通知后,应该跳转到 聊天页面,但可能会:
    • 进入应用后,未能正确跳转到聊天界面。
    • 如果 App 进程被杀死,点击通知后只能进入启动页,而不是聊天页面。
🚀 解决方案
  1. 使用 PendingIntent.FLAG_UPDATE_CURRENT 确保 intent 只创建一次

  2. App 被杀死时,恢复正确页面,

  3. SplashActivity 里判断 Intent,决定是否直接进入聊天界面:
    if (intent?.hasExtra("chatId") == true) {
        startActivity(Intent(this, ChatActivity::class.java).apply {
            putExtras(intent.extras!!)
        })
        finish()
    }
    

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2296119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Matlab优化算法-第14期】基于智能优化算法的VMD信号去噪项目实践

基于智能优化算法的VMD信号去噪项目实践 一、前言 在信号处理领域&#xff0c;噪声去除是一个关键问题&#xff0c;尤其是在处理含有高斯白噪声的复杂信号时。变分模态分解&#xff08;VMD&#xff09;作为一种新兴的信号分解方法&#xff0c;因其能够自适应地分解信号而受到…

ubuntu20使用tigervnc远程桌面配置记录

一、安装tigervnc sudo apt install tigervnc-common sudo apt install tigervnc-standalone-server二、增加配置文件 安装完后新增配置文件&#xff1a;vim ~/.vnc/xstartup #!/bin/sh #Uncomment the following two lines for normal desktop: #unset SESSION_MANAGER #ex…

【WB 深度学习实验管理】使用 PyTorch Lightning 实现高效的图像分类实验跟踪

本文使用到的 Jupyter Notebook 可在GitHub仓库002文件夹找到&#xff0c;别忘了给仓库点个小心心~~~ https://github.com/LFF8888/FF-Studio-Resources 在机器学习项目中&#xff0c;实验跟踪和结果可视化是至关重要的环节。无论是调整超参数、优化模型架构&#xff0c;还是监…

编译spring 6.2.2

如何编译Spring 6.2.2 下载spring 6.2.2 首先&#xff0c;下载spring 6.2.2&#xff0c;地址&#xff1a;下载 解压到你的目录下。 下载gradle 下载gradle&#xff0c;这是spring项目的依赖管理工具&#xff0c;本文下载的是8.12.1。 gradle idea配置如下&#xff1a;在你的…

【centOS】搭建公司内网git环境-GitLab 社区版(GitLab CE)

1. 安装必要的依赖 以 CentOS 7 系统为例&#xff0c;安装必要的依赖包&#xff1a; sudo yum install -y curl policycoreutils openssh-server openssh-clients postfix sudo systemctl start postfix sudo systemctl enable postfix2. 添加 GitLab 仓库 curl -sS https:/…

【R语言】plyr包和dplyr包

一、plyr包 plyr扩展包主要是实现数据处理中的“分割-应用-组合”&#xff08;split-apply-combine&#xff09;策略。此策略是指将一个问题分割成更容易操作的部分&#xff0c;再对每一部分进行独立的操作&#xff0c;最后将各部分的操作结果组合起来。 plyr扩展包中的主要函…

《XSS跨站脚本攻击》

一、XSS简介 XSS全称&#xff08;Cross Site Scripting&#xff09;跨站脚本攻击&#xff0c;为了避免和CSS层叠样式表名称冲突&#xff0c;所以改为了XSS&#xff0c;是最常见的Web应用程序安全漏洞之一&#xff0c;位于OWASP top 10 2013/2017年度分别为第三名和第七名&…

Golang:精通sync/atomic 包的Atomic 操作

在本指南中&#xff0c;我们将探索sync/atomic包的细节&#xff0c;展示如何编写更安全、更高效的并发代码。无论你是经验丰富的Gopher还是刚刚起步&#xff0c;你都会发现有价值的见解来提升Go编程技能。让我们一起开启原子运算的力量吧&#xff01; 理解Go中的原子操作 在快…

代码随想录_二叉树

二叉树 二叉树的递归遍历 144.二叉树的前序遍历145.二叉树的后序遍历94.二叉树的中序遍历 // 前序遍历递归LC144_二叉树的前序遍历 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result new ArrayList<Integer&g…

【C语言标准库函数】浮点数分解与构造: frexp() 和 ldexp()

目录 一、头文件 二、函数简介 2.1. frexp(double x, int *exp) 2.2. ldexp(double x, int exp) 三、函数实现&#xff08;概念性&#xff09; 3.1. frexp 的概念性实现 3.2. ldexp 的概念性实现 四、注意事项 五、示例代码 在C语言标准库中&#xff0c;frexp() 和 ld…

【Git】tortoisegit使用配置

1. 安装 首先下载小乌龟&#xff0c;下载地址:https://tortoisegit.org/download/, 可以顺便下载语言包&#xff01; 安装时&#xff0c;默认安装就可以&#xff0c;一路next。也可以安装到指定目录中 目前已完成本地安装&#xff0c;接下来就需要与远程仓库建立连接&…

Spring基于文心一言API使用的大模型

有时做项目我们可能会遇到要在项目中对接AI大模型 本篇文章是对使用文心一言大模型的使用总结 前置任务 在百度智能云开放平台中注册成为开发者 百度智能云开放平台 进入百度智能云官网进行登录&#xff0c;点击立即体验 点击千帆大模型平台 向下滑动&#xff0c;进入到模型…

Centos Ollama + Deepseek-r1+Chatbox运行环境搭建

Centos Ollama Deepseek-r1Chatbox运行环境搭建 内容介绍下载ollama在Ollama运行DeepSeek-r1模型使用chatbox连接ollama api 内容介绍 你好&#xff01; 这篇文章简单讲述一下如何在linux环境搭建 Ollama Deepseek-r1。并在本地安装的Chatbox中进行远程调用 下载ollama 登…

SpringSecurity:授权服务器与客户端应用(入门案例)

文章目录 一、需求概述二、开发授权服务器1、pom依赖2、yml配置3、启动服务端 三、开发客户端应用1、pom依赖2、yml配置3、SecurityConfig4、接口5、测试 一、需求概述 maven需要3.6.0以上版本 二、开发授权服务器 1、pom依赖 <dependency><groupId>org.springfr…

Python与java的区别

一开始接触Python的时候&#xff0c;哔哩视频铺天盖地&#xff0c;看了很多人主讲的&#xff0c;要找适合自己口味的&#xff0c;各种培训机构喜欢在各种平台引流打广告&#xff0c;看了很多家&#xff0c;要么就是一个视频几个小时&#xff0c;长篇大论不讲原理只讲应用&#…

大数据学习之SparkSql

95.SPARKSQL_简介 网址&#xff1a; https://spark.apache.org/sql/ Spark SQL 是 Spark 的一个模块&#xff0c;用于处理 结构化的数据 。 SparkSQL 特点 1 易整合 无缝的整合了 SQL 查询和 Spark 编程&#xff0c;随时用 SQL 或 DataFrame API 处理结构化数据。并且支…

鸿蒙UI(ArkUI-方舟UI框架)- 使用文本

返回主章节 → 鸿蒙UI&#xff08;ArkUI-方舟UI框架&#xff09; 文本使用 文本显示 (Text/Span) Text是文本组件&#xff0c;通常用于展示用户视图&#xff0c;如显示文章的文字内容。Span则用于呈现显示行内文本。 创建文本 string字符串 Text("我是一段文本"…

Spider 数据集上实现nlp2sql训练任务

NLP2SQL&#xff08;自然语言处理到 SQL 查询的转换&#xff09;是一个重要的自然语言处理&#xff08;NLP&#xff09;任务&#xff0c;其目标是将用户的自然语言问题转换为相应的 SQL 查询。这一任务在许多场景下具有广泛的应用&#xff0c;尤其是在与数据库交互的场景中&…

【DeepSeek】DeepSeek概述 | 本地部署deepseek

目录 1 -> 概述 1.1 -> 技术特点 1.2 -> 模型发布 1.3 -> 应用领域 1.4 -> 优势与影响 2 -> 本地部署 2.1 -> 安装ollama 2.2 -> 部署deepseek-r1模型 1 -> 概述 DeepSeek是由中国的深度求索公司开发的一系列人工智能模型&#xff0c;以其…

ASP.NET Core 使用 WebClient 从 URL 下载

本文使用 ASP .NET Core 3.1&#xff0c;但它在.NET 5、 .NET 6和.NET 8上也同样适用。如果使用较旧的.NET Framework&#xff0c;请参阅本文&#xff0c;不过&#xff0c;变化不大。 如果想要从 URL 下载任何数据类型&#xff0c;请参阅本文&#xff1a;HttpClient 使用WebC…