FCM的集成与Niotfications的使用
前言
说起App推送功能,国内的我们大家都用集成产品如极光个推或者使用厂商推送,而如果是是海外版的应用,所以只能用 FCM 了。
说起 FCM 大家都知道只要你用了 Firebase 那么你的各自依赖必然就会高,导致的就是 Flutter 的 android 项目的target依赖升级,kotlin版本升级,gradle版本升级,androidx升级等一系列的问题。
并且在 Android 项目中使用 FCM 依赖,在前台的时候还需要我们自己手动的弹出通知 Notification,所以我们还需要集成到 notification 插件。
而使用 noticifation 插件之后又会导致一些列的依赖冲突。
这里单独出一篇文章把踩过的坑记录一下。
一、依赖集成
首先我们集成 FCM 的插件
yaml
# FCM推送
firebase_messaging: ^15.2.6
毫无意外的报错,接下来我们需要升级 gradle,kotlin,target 等依赖
打开app中的android文件夹,在根目录的 settings.gradle 升级对应的依赖。
java
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' }
maven { url 'https://jitpack.io' }
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.7.3" apply false
id "org.jetbrains.kotlin.android" version "1.9.0" apply false
// 添加 Google Services 插件
id "com.google.gms.google-services" version "4.4.0" apply false
}
include ":app"
给大家参考,一定别忘记了 Google Services 。
在 gradle.wrapper 中的配置,推荐设置 gradle 版本为8.0+
ini
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
一旦设置 gradle 为 8.0+ 那么你的 android/app 下面的 build.gradle 中还需要修改一些配置
- 强制指定 namespace 属性
- 自动给子依赖添加 namespace 属性
- target 35 (因为andorid-lifecycle版本高了,可以运行但是无法打包,推荐升级到35)
- appcompat 依赖不能太低
- 别忘记添加 Google Services 应用
给出示例:
java
plugins {
id "com.android.application"
id "kotlin-android"
id "kotlin-kapt"
id "dev.flutter.flutter-gradle-plugin"
id 'com.google.gms.google-services'
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new Exception("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
android {
namespace 'com.xxx.xxx'
compileSdk 35
compileOptions {
// 启用核心库脱糖(Core Library Desugaring)
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
// 如打包出现Failed to transform libs.jar to match attributes
checkReleaseBuilds false
}
defaultConfig {
applicationId "com.xxx.xxx"
minSdkVersion 21
targetSdkVersion 35
versionCode 100
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
//选择要添加的对应 cpu 类型的 .so 库。
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId,
JPUSH_APPKEY : "1234567890",
JPUSH_CHANNEL : "developer-default",
queryAllPackages: ""
]
}
//配置keystore签名
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
debug {
}
}
buildTypes {
release {
signingConfig signingConfigs.release
//默认系统混淆
minifyEnabled true
//是否可调试
debuggable false
//Zipalign优化
zipAlignEnabled true
//移除无用的resource文件
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.release
//是否可调试
debuggable true
}
}
}
flutter {
source '../..'
}
// 覆盖所有子模块的命名空间规则
subprojects {
afterEvaluate { project ->
if (project.hasProperty("android") && project.android.namespace == null) {
project.android.namespace = "com.hongyegroup.app24ifm.${project.name}"
}
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
implementation 'com.github.bumptech.glide:glide:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'androidx.appcompat:appcompat:1.6.1' //加入AndroidX依赖
}
接下来你就能编译通过并且运行了。
然后我们在加入本地通知的依赖
yaml
flutter_local_notifications: ^19.2.1
pub get 之后不出意外的报错了,首先如果你有 device_info_plus 插件的话他们是有冲突的,需要升级 device_info_plus 到新版本。
yaml
device_info_plus: ^11.3.0
flutter_local_notifications: ^19.2.1
并且在你的 android/app 下面的 build.gradle 中还需要修改一些配置
需要加入脱糖的配置
arduino
android {
namespace 'com.xxx.xxx'
compileSdk 35
compileOptions {
// 启用核心库脱糖(Core Library Desugaring)
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
...
}
dependencies {
...
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
}
在上面的完整版 build.gradle 中我已经给出了。
到此我们的依赖才算集成完成,那么怎么使用呢?我推荐一个工具类即可。
二、工具类封装
这里直接给出源码,获取FCM token 并且初始化LocalNotification与 FCM 的消息监听
fcm`_utils.dart
dart
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:router/componentRouter/component_service_manager.dart';
import 'package:shared/utils/log_utils.dart';
// 定义一个回调函数类型
typedef TokenCallback = void Function(String token);
class FcmUtils {
//FCM SDK对象
final _firebaseMessaging = FirebaseMessaging.instance;
//Notifications插件对象
final _localNotifications = FlutterLocalNotificationsPlugin();
//Android 8+ 的消息通道
final _androidChannel = const AndroidNotificationChannel(
'Normal', //设置 Channel ID
'Channel Normal Android', //设置 Channel 名称
importance: Importance.defaultImportance); //设置该 Channel 的优先级
// 初始化,获取设备token
Future<void> initNotifications({
TokenCallback? onToken, // Token回调函数
}) async {
//申请动态通知权限
await _firebaseMessaging.requestPermission();
//获取 FCM Token
final fCMToken = await _firebaseMessaging.getToken();
if (fCMToken != null) {
Log.d("FCM -> Token:$fCMToken");
onToken?.call(fCMToken); // 将初始 Token 通过回调传递
}
//刷新 FCM Token 的监听
_firebaseMessaging.onTokenRefresh.listen((newToken) {
Log.d("FCM onTokenRefresh -> Token:$newToken");
onToken?.call(newToken); // 将刷新后的 Token 通过回调传递
});
//拿到 FCM Token 之后初始化一些监听
_initPushNotifications();
_initLocalNotifications();
}
/// Notifications 插件初始化,监听前台消息
Future _initLocalNotifications() async {
const iOS = DarwinInitializationSettings();
// @drawable/ic_launcher是应用的图标,路径是:android/app/src/main/res/mipmap/ic_launcher.png
const android = AndroidInitializationSettings('@mipmap/ic_launcher');
// Notifications 插件只需要处理 Android 和 iOS 平台的配置
const settings = InitializationSettings(android: android, iOS: iOS);
// Notifications 插件初始化
await _localNotifications.initialize(settings, onDidReceiveNotificationResponse: (NotificationResponse response) {
// android 前台消息点击回调
final message = RemoteMessage.fromMap(jsonDecode(response.payload!));
Log.d(response);
// 处理收到消息
handleMessage(message);
});
//Android 8 以上会通过 Channel 创建对应的通知渠道
final platform = _localNotifications.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
await platform?.createNotificationChannel(_androidChannel);
}
/// FCM 初始化接收消息的各种回调
Future _initPushNotifications() async {
await _firebaseMessaging.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);
// 打开app时,会执行该回调,获取消息(通常是程序终止时,点击消息打开app的回调)
_firebaseMessaging.getInitialMessage().then(
(RemoteMessage? message) {
if (message == null) return; // 没有消息不执行后操作
handleMessage(message);
},
);
// 后台程序运行时,点击消息触发
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) => handleMessage(message));
// 前台消息,android不会弹出通知框,所以需要 Notifications 插件自定义本地通知(iOS没有前台消息,iOS的前台消息和后台运行时一样的效果)
FirebaseMessaging.onMessage.listen((message) {
Log.d("前台收到的消息 -> message:${message.data}");
final notification = message.notification;
if (notification == null) return;
if (Platform.isIOS) return;
_localNotifications.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
_androidChannel.id,
_androidChannel.name,
icon: '@mipmap/ic_launcher',
)),
payload: jsonEncode(message.toMap()));
});
}
// 处理收到的消息,比如跳转页面或者其他处理
void handleMessage(RemoteMessage message) {
final dataJson = message.data;
Log.d("点击消息的处理 handleMessage -> data:$dataJson");
}
基本上每一行我都给出了注释,我们只需要调用 initNotifications 传入 callback 就可以获取到 fcmToken 和 FCM Refresh 之后的 fcmToken 。
如何使用呢?
首先把 Firebase 的谷歌服务配置导入到项目

如果是iOS则比较简单一些,只需要替换项目中的GoogleService-Info.plist文件,并启动连接Firebase即可。
从 Firebase 的控制台下载并替换项目中的文件:ios/Runner/GoogleService-Info.plist,并且在 ios/Runner/AppDelegate.swift:

配置好对应的证书即可收到推送。
Flutter 的代码如下:
创建我们的 FirebaseOptions 初始化配置类:
MyFirebaseOptions:
php
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
class MyFirebaseOptions {
static FirebaseOptions get currentPlatform {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
default:
throw UnsupportedError(
'MyFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyDooSUGSf63Ghq02_iIhtnmwMDs4HlWS6c',
appId: '1:406099696497:android:d7780551ab203907262ab6',
messagingSenderId: '406099696497',
projectId: 'flutterfire-e2e-tests',
storageBucket: 'flutterfire-e2e-tests.firebasestorage.app'
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyDooSUGSf63Ghq02_iIhtnmwMDs4HlWS6c',
appId: '1:406099696497:ios:acd9c8e17b5e620e3574d0',
messagingSenderId: '406099696497',
projectId: 'flutterfire-e2e-tests',
databaseURL:
'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app',
storageBucket: 'flutterfire-e2e-tests.appspot.com',
androidClientId:
'406099696497-tvtvuiqogct1gs1s6lh114jeps7hpjm5.apps.googleusercontent.com',
iosClientId:
'406099696497-taeapvle10rf355ljcvq5dt134mkghmp.apps.googleusercontent.com',
iosBundleId: 'io.flutter.plugins.firebase.tests',
);
}
然后我们在应用的入口 main.dart 中初始化
csharp
void main() async {
WidgetsFlutterBinding.ensureInitialized();
//初始化FCM
Firebase.initializeApp(name: 'Your App Name', options: MyFirebaseOptions.currentPlatform);
}
初始化完成之后怎么获取到FCM并监听FCM消息呢?
这个得看你的实际业务场景,你可以在初始化之后就开始监听,也可以在任意你想要的地方,比如我们的场景就是用户登录之后才开始监听获取到fcmToken并post给后台。
我就在首页的逻辑中添加的逻辑
scss
void _registerFCM() {
//获取token,并初始化监听
FcmUtils().initNotifications(onToken: (fcmToken) {
Log.d("准备注册FCM Token :$fcmToken");
_doRegisterFCM(fcmToken);
});
}
/// 调用接口注册FCMToken
void _doRegisterFCM(String fcmToken) async {
final result = await _authRepository.registerFCMToken(fcmToken: fcmToken, cancelToken: cancelToken);
//请求成功去首页
if (result.isSuccess) {
Log.d("注册 FMC Token 成功!");
} else {
ToastEngine.show(result.errorMsg ?? "UnKnow Error");
}
}
这样后台有推送的事件就可以通过 fcmToken 推送到指定的设备了:




后记
本文我们讲了 fmc 与 notification 的插件集成中安卓模块遇到的冲突问题与推荐的升级(iOS没有这些问题)。
其次我们给出了推荐的 FCM初始化工具类,方便获取到 fcmToken 与 消息的监听,并且对前台消息的 Notification 给出示例。
推荐大家多用几个机型和网络尝试,只要有一个设备能收到消息就说明没有问题,其他机型收不到推送可能是网络的问题,可以关闭梯子或者开启梯子尝试,个人亲历有的设备关闭梯子才能收到,有的设备只有开启梯子才能收到。怪异的很。把包发给国外的同事就没这些烦恼,就很顺。
那本文的代码比较简单,全部的代码也已经在文中展出了。
如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,理解不正确的地方,同学们都可以指出修正。
今天的分享就到这里啦,当然如果你有其他更好的方式,也希望大家能评论区交流一起学习进步。
如果感觉本文对你有一点的启发和帮助,还望你能点赞
支持一下,你的支持对我真的很重要。
Ok,这一期就此完结。
