Android FCM推送及通知栏展示
需求:
实现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 = tokenUserDataUtils.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" // 通知通道 IDval channelName = "General Notifications" // 通知通道名称// 判断目标 Activityval 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_TOPif (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.BLUEenableVibration(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 ActivityManagerval appProcesses = activityManager.runningAppProcessesappProcesses?.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,保持 Intent 的 Extras 最新。 |
FLAG_NO_CREATE | 如果 PendingIntent 存在,则返回它;否则返回 null,不会创建新的 PendingIntent。 |
FLAG_ONE_SHOT | PendingIntent 只能使用一次,执行后自动销毁。 |
FLAG_IMMUTABLE (API 23+) | PendingIntent 不能被修改(Android 12 及以上必须显式指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE)。 |
FLAG_MUTABLE (API 31+) | PendingIntent 可以被 AlarmManager、NotificationManager 等修改,适用于 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
<serviceandroid: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 IDUserDataUtils.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 个通知。
- 点击某个通知后,其他通知还在。
🚀 解决方案
-
使用
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)
- 单聊消息:不同用户的聊天,不同 ID(
🟢 问题 3:通知点击后跳转异常
🛠 问题描述
- 用户点击通知后,应该跳转到 聊天页面,但可能会:
- 进入应用后,未能正确跳转到聊天界面。
- 如果 App 进程被杀死,点击通知后只能进入启动页,而不是聊天页面。
🚀 解决方案
-
使用
PendingIntent.FLAG_UPDATE_CURRENT确保 intent 只创建一次 -
App 被杀死时,恢复正确页面,
- 在
SplashActivity里判断Intent,决定是否直接进入聊天界面:if (intent?.hasExtra("chatId") == true) {startActivity(Intent(this, ChatActivity::class.java).apply {putExtras(intent.extras!!)})finish() }
相关文章:
Android FCM推送及通知栏展示
需求: 实现FIrebase Cloud Message推送功能,用户收到通知后,可以悬浮通知,自定义的大/小通知展示在通知栏,判断前台/后台,点击后进行跳转。 步骤: 一、配置及接入依赖库 1.下载 google-serv…...
【Matlab优化算法-第14期】基于智能优化算法的VMD信号去噪项目实践
基于智能优化算法的VMD信号去噪项目实践 一、前言 在信号处理领域,噪声去除是一个关键问题,尤其是在处理含有高斯白噪声的复杂信号时。变分模态分解(VMD)作为一种新兴的信号分解方法,因其能够自适应地分解信号而受到…...
4. Go结构体使用
1、结构体的简介 结构体(Struct)是编程语言中常见的一种复合数据类型,它将不同类型的数据元素(成员)组合成一个单一的实体。通过结构体,程序员可以将具有不同类型和性质的信息绑定到一个对象中,…...
ubuntu20使用tigervnc远程桌面配置记录
一、安装tigervnc sudo apt install tigervnc-common sudo apt install tigervnc-standalone-server二、增加配置文件 安装完后新增配置文件:vim ~/.vnc/xstartup #!/bin/sh #Uncomment the following two lines for normal desktop: #unset SESSION_MANAGER #ex…...
【WB 深度学习实验管理】使用 PyTorch Lightning 实现高效的图像分类实验跟踪
本文使用到的 Jupyter Notebook 可在GitHub仓库002文件夹找到,别忘了给仓库点个小心心~~~ https://github.com/LFF8888/FF-Studio-Resources 在机器学习项目中,实验跟踪和结果可视化是至关重要的环节。无论是调整超参数、优化模型架构,还是监…...
编译spring 6.2.2
如何编译Spring 6.2.2 下载spring 6.2.2 首先,下载spring 6.2.2,地址:下载 解压到你的目录下。 下载gradle 下载gradle,这是spring项目的依赖管理工具,本文下载的是8.12.1。 gradle idea配置如下:在你的…...
【centOS】搭建公司内网git环境-GitLab 社区版(GitLab CE)
1. 安装必要的依赖 以 CentOS 7 系统为例,安装必要的依赖包: sudo yum install -y curl policycoreutils openssh-server openssh-clients postfix sudo systemctl start postfix sudo systemctl enable postfix2. 添加 GitLab 仓库 curl -sS https:/…...
MHTML文件如何在前端页面展示
MHTML文件如何在前端页面展示 需求背景: 目前在给证券公司做项目,但是在使用新系统的过程中,甲方还希望之前之前系统的历史记录可以看到。 最初制定的计划是项目组里面做数据的把原系统页面爬取下来,转成图片,直接给…...
Spring Boot的常用注解
Spring Boot 常用注解 主要分为以下几类: Spring 核心注解Spring Boot 相关注解Spring MVC 相关注解Spring Data JPA 相关注解Spring 事务管理Spring Security 相关注解Spring AOP 相关注解Spring 其他常用注解 下面是详细分类和表格展示👇:…...
【R语言】plyr包和dplyr包
一、plyr包 plyr扩展包主要是实现数据处理中的“分割-应用-组合”(split-apply-combine)策略。此策略是指将一个问题分割成更容易操作的部分,再对每一部分进行独立的操作,最后将各部分的操作结果组合起来。 plyr扩展包中的主要函…...
《XSS跨站脚本攻击》
一、XSS简介 XSS全称(Cross Site Scripting)跨站脚本攻击,为了避免和CSS层叠样式表名称冲突,所以改为了XSS,是最常见的Web应用程序安全漏洞之一,位于OWASP top 10 2013/2017年度分别为第三名和第七名&…...
Golang:精通sync/atomic 包的Atomic 操作
在本指南中,我们将探索sync/atomic包的细节,展示如何编写更安全、更高效的并发代码。无论你是经验丰富的Gopher还是刚刚起步,你都会发现有价值的见解来提升Go编程技能。让我们一起开启原子运算的力量吧! 理解Go中的原子操作 在快…...
代码随想录_二叉树
二叉树 二叉树的递归遍历 144.二叉树的前序遍历145.二叉树的后序遍历94.二叉树的中序遍历 // 前序遍历递归LC144_二叉树的前序遍历 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result new ArrayList<Integer&g…...
详解Swift中 Sendable AnyActor Actor GlobalActor MainActor Task、await、async
详解Swift中 Sendable AnyActor Actor GlobalActor MainActor 的关联或者关系 及其 各自的作用 和 用法 以及与 Task、await、async: Sendable 协议 作用: Sendable 是一个协议,它用于标记可以安全地跨线程或异步任务传递的数据类型。符合 S…...
【C语言标准库函数】浮点数分解与构造: frexp() 和 ldexp()
目录 一、头文件 二、函数简介 2.1. frexp(double x, int *exp) 2.2. ldexp(double x, int exp) 三、函数实现(概念性) 3.1. frexp 的概念性实现 3.2. ldexp 的概念性实现 四、注意事项 五、示例代码 在C语言标准库中,frexp() 和 ld…...
【Git】tortoisegit使用配置
1. 安装 首先下载小乌龟,下载地址:https://tortoisegit.org/download/, 可以顺便下载语言包! 安装时,默认安装就可以,一路next。也可以安装到指定目录中 目前已完成本地安装,接下来就需要与远程仓库建立连接&…...
Spring基于文心一言API使用的大模型
有时做项目我们可能会遇到要在项目中对接AI大模型 本篇文章是对使用文心一言大模型的使用总结 前置任务 在百度智能云开放平台中注册成为开发者 百度智能云开放平台 进入百度智能云官网进行登录,点击立即体验 点击千帆大模型平台 向下滑动,进入到模型…...
运维_Mac环境单体服务Docker部署实战手册
Docker部署 本小节,讲解如何将前端 后端项目,使用 Docker 容器,部署到 dev 开发环境下的一台 Mac 电脑上。 1 环境准备 需要安装如下环境: Docker:容器MySQL:数据库Redis:缓存Nginx&#x…...
[论文笔记] Deepseek-R1R1-zero技术报告阅读
启发: 1、SFT&RL的训练数据使用CoT输出的格式,先思考再回答,大大提升模型的数学与推理能力。 2、RL训练使用群体相对策略优化(GRPO),奖励模型是规则驱动,准确性奖励和格式化奖励。 1. 总体概述 背景与目标 报告聚焦于利用强化学习(RL)提升大型语言模型(LLMs)…...
Centos Ollama + Deepseek-r1+Chatbox运行环境搭建
Centos Ollama Deepseek-r1Chatbox运行环境搭建 内容介绍下载ollama在Ollama运行DeepSeek-r1模型使用chatbox连接ollama api 内容介绍 你好! 这篇文章简单讲述一下如何在linux环境搭建 Ollama Deepseek-r1。并在本地安装的Chatbox中进行远程调用 下载ollama 登…...
一文读懂:TCP网络拥塞的应对策略与方案
TCP(传输控制协议)是互联网中广泛使用的可靠传输协议,它通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制确保数据的可靠传输。然而,在网络环境中,由于多个主机共享网络资源,网络拥塞成为了一个…...
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的时候,哔哩视频铺天盖地,看了很多人主讲的,要找适合自己口味的,各种培训机构喜欢在各种平台引流打广告,看了很多家,要么就是一个视频几个小时,长篇大论不讲原理只讲应用&#…...
doris:MySQL 兼容性
Doris 高度兼容 MySQL 语法,支持标准 SQL。但是 Doris 与 MySQL 还是有很多不同的地方,下面给出了它们的差异点介绍。 数据类型 数字类型 类型MySQLDorisBoolean- 支持 - 范围:0 代表 false,1 代表 true- 支持 - 关键字&am…...
SQL中 的exists用法
EXISTS 是 SQL 中的一个子查询条件,用于检查子查询是否返回任何行。如果子查询返回至少一行,则 EXISTS 返回 TRUE。 例如,查询有订单的客户列表: SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.cust…...
案例1.spark和flink分别实现作业配置动态更新案例
目录 目录 一、背景 二、解决 1.方法1:spark broadcast广播变量 a. 思路 b. 案例 ① 需求 ② 数据 ③ 代码 2.方法2:flink RichSourceFunction a. 思路 b. 案例 ① 需求 ② 数据 ③ 代码 ④ 测试验证 测试1 测试2 测试3 一、背景 在实时作业(如 Spark Str…...
大数据学习之SparkSql
95.SPARKSQL_简介 网址: https://spark.apache.org/sql/ Spark SQL 是 Spark 的一个模块,用于处理 结构化的数据 。 SparkSQL 特点 1 易整合 无缝的整合了 SQL 查询和 Spark 编程,随时用 SQL 或 DataFrame API 处理结构化数据。并且支…...
鸿蒙UI(ArkUI-方舟UI框架)- 使用文本
返回主章节 → 鸿蒙UI(ArkUI-方舟UI框架) 文本使用 文本显示 (Text/Span) Text是文本组件,通常用于展示用户视图,如显示文章的文字内容。Span则用于呈现显示行内文本。 创建文本 string字符串 Text("我是一段文本"…...
Spider 数据集上实现nlp2sql训练任务
NLP2SQL(自然语言处理到 SQL 查询的转换)是一个重要的自然语言处理(NLP)任务,其目标是将用户的自然语言问题转换为相应的 SQL 查询。这一任务在许多场景下具有广泛的应用,尤其是在与数据库交互的场景中&…...
数据结构——【树模板】
#思路 1、 结点类: 属性:数据,孩子结点列表 功能1:认孩子: 前提:在父子都是结点的情况下 2. 树类: 属性:根节点,生成初始化的总结点 功能1:获取初始化…...
