记一次Flutter JPush SDK 导致的问题(Android 点击Notification 无反应)

本文详细记录了在Flutter项目中,由FCM切换至极光推送时遇到的问题及解决过程。问题在于Android端集成极光JPush插件后,首次安装无法获取registrationID,点击推送通知无回调。经分析发现,Firebase_messaging插件导致JPushPlugin对象重复初始化,造成回调丢失。通过删除firebase_messaging或禁用其后台消息处理解决该问题。总结强调慎用静态变量,以及在Flutter中可直接修改本地插件代码进行调试。

背景

原来的Flutter 项目直接使用FCM 进行推送,目前有需求将FCM 变为极光推送。

代码版本

Flutter 3.0
Jpush_flutter 2.3.4

问题

在原有项目中直接集成极光JPush 的pub Flutter 插件进行支持。Jpush Flutter
按照官方文档集成以后,Android 端和iOS 端都可以正常接收推送消息,但是在Android 端有几个很奇怪的表现
1、首次安装,调用Flutter 代码
getRegistrationID() 没有收到相应的回调。
2、点击推送消息,onOpenNotification 中也没有收到相应的回调(实际上addEventHandler 所有的回调都没有收到)

解决思路

  1. 理清Jpush 在Flutter 的运行方式。
    实际上,我们看到Jpush_flutter 插件中,主要是以原生代码为主,插件做的就只是做桥接进行通讯。
    在这里插入图片描述
    可以看到除了JpushPlugin 剩下的就是极光自身android 集成要求中的内容了。
    再看jpush_flutter.dart 就能确定,这个插件中只处理通讯问题。

  2. 运行方式大致了解了,在运行过程中,我们查看日志,当点击推送Notification 的时候,Java 层的日志有打印,但是Flutter 层的日志没有打印,所以这里可以断定是插件通讯的问题。

  3. 翻看JPushPlugin.java 文件中,可以看到,java 的日志也只打印了一部分在这里插入图片描述
    其中就可以看到大部分都是判断dartIsReady 这个字段。
    幸运的是,这里代码量不大,我们再几个使用到和修改 dartIsReady字段的地方进行日志打印。
    发现,dartIsReady 只有被设置为true ,并没有设置为false
    并且在调用 onOpenNotification 的时候,dartIsReady 却为false,
    这个表现,直觉告诉我,JpushPlugin 对象被重复初始化了,两个对象不是同一个。
    打印中加入 hashCode,证实以上猜想。

  4. 排查为什么会有重复创建的问题,最简单就算在实例化JPushPlugin 对象的时候打印方法调用栈。
    所幸,我们能直接修改插件中的代码,并且运行也会生效。
    在这里插入图片描述

  5. 发现两次初始化,第一次是由FlutterActivity 中开始的创建
    第二次是在FlutterFirebaseMessagingBackgroundExecutor 中创建了FlutterEngine 过程中初始化插件。

  6. 删除了Firebase_messaging 以后(包括其他相关代码)JPush 一切正常。 到这里,基本可以断定是Firebase_messaging 插件导致的。

翻看代码
在这里插入图片描述
在这里看到,backgroundFlutterEngine 生成的FlutterEngine 是作为 backgroundFlutterEngine 使用。(具体为什么,到现在没找到原因)

问题分析

  1. firebase_messaging 会导致所有插件再次初始化,疑问点是,为什么其他插件正常,反倒只有极光不正常
  2. 根据上一个疑问,我们回看JPushPlugin 发现代码中,有JpushReceiver, (极光SDK 中用于接收回调事件的地方),并且跟踪代码,这里所有的 调用,都是直接调用 JPushPlugin 中的静态方法,并且,静态方法中,通过instance 获取到 JpushPlugin 实例。
  3. 问题点就出在这里,因为重复初始化,导致生产了两个JpushPlugin 实例,极光的初始化,需要在dart 代码中调用setup 进行初始化,然而,setup 的过程在第一个实例化的时候就开始了,第二个实例化对象并没有setup 导致dartIsReady 始终为false
  4. 并且所有JpushReceiver 使用的instance 都是第二个实例化出来的对象。

结束

最终我们通过删除了firebase_messaging 解决了这个问题,但是实际业务上会有使用到firebase_messaging 相关,但是因为目前的问题,只能删除。
实际上到此,这个问题已经花了4 个小时去查找了。

结束2

第一次对接FCM 的同事想了一想这个问题,并且联系起来,
在firebase_messageing 集成过程中有这么一行
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
删除了以后,使用firebase_messageing ,Jpush 也正常了
完美解决

总结:

  1. 在代码中,慎用static 这类型全局静态变量,因为无论从Android GC的角度,还是开发可读性的角度,甚至是代码维护的角度,都是不良的。
  2. 根据问题反思,这个问题,在Flutter 的module 模式下也会出现,因为 Flutter 原生的Module 是多引擎模型。(当然,如果是module 也不会单独集成极光插件)
  3. 解决问题过程中,忽略了太多Log 细节,实际上 dartIsReady = false 这个问题,自己翻看代码和Log 就能发现(解决BUG 的过程中,发现dartIsReady = false 这里已经过去了3小时)
  4. 在Flutter 开发中,所有插件都是代码形式下载到本地,在运行过程中,才进行编译和打包的,所以可以开心的本地修改插件内的代码,去验证问题,(但是涉及到其他同事插件是pub 下载的,所以也只能是调试用)
<think> 我们正在解决Android 12+极光推送冷启动兼容性问题。根据之前的信息和引用,我们可以从以下几个方面入手: 1. **适配Android 12的PendingIntent可变性要求**: Android 12引入了PendingIntent的mutability(可变性)标志要求。在创建PendingIntent时,必须明确指定是否可变(FLAG_MUTABLE或FLAG_IMMUTABLE)。 在极光推送中,通常需要修改通知的PendingIntent。因此,我们需要确保在创建PendingIntent时使用`FLAG_IMMUTABLE`(如果不需要修改)或`FLAG_MUTABLE`(如果需要修改)。但注意,`FLAG_MUTABLE`需要额外的权限,且仅在确实需要修改时才使用。 在AndroidManifest.xml中,我们可以通过元数据来配置极光推送的PendingIntent标志。例如: ```xml <meta-data android:name="JPUSH_PENDING_INTENT_FLAG" android:value="FLAG_IMMUTABLE" /> ``` 或者,如果极光推送SDK版本已经适配了Android 12,则可能不需要额外配置。 2. **处理Android 12的显式广播限制**: Android 12限制了对其他应用的隐式广播的接收。如果极光推送使用了隐式广播,可能会导致冷启动时无法接收。因此,确保使用显式广播。 在极光推送中,通常我们自定义的广播接收器需要设置为显式。在AndroidManifest.xml中,为接收器添加`android:exported`属性,并设置为`true`或`false`(根据是否需要被其他应用访问)。同时,确保使用全限定类名的IntentFilter。 例如: ```xml <receiver android:name=".MyJPushReceiver" android:exported="false"> <intent-filter> <action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" /> <category android:name="${applicationId}" /> </intent-filter> </receiver> ``` 3. **适配启动画面(Splash Screen)**: Android 12引入了新的启动画面API。冷启动时,系统会显示一个默认的启动画面,直到应用的第一帧绘制完成。如果应用有自定义的启动画面,需要适配新的API,否则可能导致推送跳转延迟。 在Flutter中,我们可以使用`splashscreen`相关的插件或原生代码来适配。确保在启动Activity中设置正确的主题,该主题应继承自`Theme.SplashScreen`。 4. **确保目标SDK版本设置正确**: 在`build.gradle`中,确保`targetSdkVersion`设置为31或更高(针对Android 12+)。同时,检查极光推送SDK版本是否支持该目标SDK版本。 5. **冷启动时通知栏点击处理**: 当用户点击通知栏消息时,应用可能处于冷启动状态(进程未启动)。在Android 12上,需要确保在启动Activity的`onCreate`方法中能够正确解析通知携带的数据。 在Flutter中,我们通常通过`getLaunchAppNotification`方法在应用启动后获取冷启动通知。但要注意,在Android 12上,由于后台启动限制,可能需要额外的处理。 在AndroidManifest.xml中,为启动Activity(通常是FlutterActivity)添加intent-filter,并确保能够处理极光推送的Intent: ```xml <activity android:name=".MainActivity" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!-- 添加极光推送的intent-filter --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="yourapp" /> </intent-filter> </activity> ``` 6. **在Flutter端处理冷启动通知**: 在Flutter的`main`函数中,在应用初始化后,获取冷启动通知并处理: ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); // 初始化极光推送 await JPush.setup(appKey: 'your_app_key', channel: 'developer-default'); // 获取冷启动通知 JPush.getLaunchAppNotification().then((message) { if (message != null) { // 处理通知,例如跳转到指定页面 Navigator.pushNamed(context, '/notificationPage', arguments: message); } }); runApp(MyApp()); } ``` 注意:由于冷启动时Flutter引擎尚未初始化,我们必须在`runApp`之前获取通知,但跳转页面需要在Flutter应用启动后进行。因此,我们可以在MyApp的初始化逻辑中处理跳转。 7. **测试**: 在Android 12设备上测试冷启动场景: - 确保应用进程未运行(通过设置强制停止) - 发送一条推送通知 - 点击通知栏消息启动应用 - 检查是否能正确跳转到指定页面并携带通知数据 8. **极光推送SDK版本**: 确保使用的极光推送Flutter插件版本支持Android 12。查看[jpush_flutter插件](https://pub.dev/packages/jpush_flutter)的更新日志,确认其已适配Android 12。 在`pubspec.yaml`中,使用最新版本: ```yaml dependencies: jpush_flutter: ^2.3.5 # 或更高版本 ``` 9. **后台启动限制**: Android 12对后台启动Activity有更严格的限制。如果应用在后台时点击通知栏消息,可能无法直接启动Activity(除非通知是用户可见的,例如在通知栏中)。因此,需要确保通知的发送符合规范,特别是高优先级通知。 在极光推送中,设置通知的优先级为高: ```dart JPush.sendNotification({ 'title': '标题', 'content': '内容', 'extras': {'key': 'value'}, 'priority': 'high' // 设置高优先级 }); ``` 总结步骤: 1. 更新jpush_flutter插件到最新版本(支持Android 12)。 2. 在AndroidManifest.xml中配置PendingIntent标志(如果需要)。 3. 检查广播接收器的显式设置。 4. 适配Android 12的启动画面。 5. 在Flutter中正确处理冷启动通知。 6. 测试冷启动场景。 如果问题仍然存在,建议查阅极光推送官方文档或联系技术支持。 §§相关问题§§ 1. 如何在Flutter中适配Android 12的启动画面(Splash Screen)? 2. 如何测试Android 12上的冷启动推送场景? 3. 极光推送如何设置高优先级通知以应对Android 12的后台限制? </think> ### 解决 Android 12+ 极光推送冷启动兼容性问题的方法 #### 1. **适配 Android 12 PendingIntent 变更** Android 12 引入了 **PendingIntent 可变性强制声明**要求: ```xml <!-- AndroidManifest.xml --> <meta-data android:name="JPUSH_PENDING_INTENT_FLAG" android:value="FLAG_IMMUTABLE" /> <!-- 或 FLAG_MUTABLE --> ``` - 使用 `FLAG_IMMUTABLE`(默认推荐)确保通知点击意图不可篡改[^1] - 若需修改通知内容,改用 `FLAG_MUTABLE` 并声明 `RECEIVER_EXPORTED` 权限 #### 2. **优化冷启动流程** 通过 **启动阶段拆分** 避免阻塞主线程[^1]: ```dart void main() async { // 第一阶段:极光初始化(非阻塞) JPush.setup(appKey: "your_key", channel: "default"); // 第二阶段:必要预加载 WidgetsFlutterBinding.ensureInitialized(); await Future.wait([ loadUserData(), JPush.getLaunchAppNotification() // 获取冷启动通知 ]); // 第三阶段:渲染UI runApp(MyApp(notification: coldStartNotification)); } ``` #### 3. **处理通知权限变更** Android 13+ 需要动态申请新权限: ```dart // 检查并申请权限 void checkPermissions() async { if (Platform.isAndroid13OrHigher) { final status = await Permission.notification.request(); if (status.isDenied) { JPush.openSettingsForNotification(); // 引导至设置页 } } } ``` #### 4. **优化通知渠道配置** 适配 Android 12 的 **精确通知控制** 特性: ```java // Android原生代码 (App.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { NotificationManager.areNotificationsEnabled(); // 检查全局开关 notificationChannel.setImportantConversation(true); // 关键会话标识 } ``` #### 5. **处理后台启动限制** 针对 Android 12 的 **后台启动限制**: ```xml <!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" /> ``` ```dart // 冷启动时触发前台服务 void handleColdStart() { if (Platform.isAndroid12OrHigher) { JPush.startForegroundService( title: "启动中", content: "正在处理通知" ); } } ``` #### 6. **厂商通道适配** 通过 **个推统一推送平台** 解决国产手机限制[^2]: ```yaml # pubspec.yaml dependencies: jpush_flutter: ^3.6.0 # 必须≥3.5.0 jcore_flutter: ^2.0.0 # 厂商通道依赖 ``` - 在个推平台配置 **VIVO/Oppo/小米** 等厂商密钥[^2] - 使用华为 HMS Core 替代 GMS 服务 #### 7. **冷启动数据保活** 防止系统回收通知数据: ```dart // 使用持久化存储冷启动通知 void saveColdStartData(Map<String, dynamic> msg) { SharedPreferences.getInstance().then((prefs) { prefs.setString('cold_start_payload', json.encode(msg)); }); } // 应用启动时读取 JPush.getLaunchAppNotification().then((msg) { if (msg == null) { final saved = prefs.getString('cold_start_payload'); if (saved != null) msg = json.decode(saved); } handleNotification(msg); }); ``` #### 验证步骤: 1. 使用 **Android 12+ 真机** 测试 2. 通过 `adb` 强制停止应用模拟冷启动: ```bash adb shell am force-stop com.your.package ``` 3. 发送测试通知后点击通知栏启动应用 4. 监控日志: ```bash adb logcat | grep -E 'JPush|ActivityTaskManager' ``` > **关键版本要求**: > - 极光 SDK ≥ v4.7.0 > - jpush_flutter ≥ v3.5.0 > - 编译目标 `targetSdkVersion ≥ 31`[^1]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值