背景
原来的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 所有的回调都没有收到)
解决思路
- 理清Jpush 在Flutter 的运行方式。
实际上,我们看到Jpush_flutter 插件中,主要是以原生代码为主,插件做的就只是做桥接进行通讯。 - 可以看到除了JpushPlugin 剩下的就是极光自身android 集成要求中的内容了。
再看jpush_flutter.dart 就能确定,这个插件中只处理通讯问题。 - 运行方式大致了解了,在运行过程中,我们查看日志,当点击推送Notification 的时候,Java 层的日志有打印,但是Flutter 层的日志没有打印,所以这里可以断定是插件通讯的问题。
- 翻看JPushPlugin.java 文件中,可以看到,java 的日志也只打印了一部分
- 其中就可以看到大部分都是判断dartIsReady 这个字段。
幸运的是,这里代码量不大,我们再几个使用到和修改 dartIsReady字段的地方进行日志打印。
发现,dartIsReady 只有被设置为true ,并没有设置为false
并且在调用 onOpenNotification 的时候,dartIsReady 却为false,
这个表现,直觉告诉我,JpushPlugin 对象被重复初始化了,两个对象不是同一个。
打印中加入 hashCode,证实以上猜想。 - 排查为什么会有重复创建的问题,最简单就算在实例化JPushPlugin 对象的时候打印方法调用栈。
所幸,我们能直接修改插件中的代码,并且运行也会生效。 - 发现两次初始化,第一次是由FlutterActivity 中开始的创建
第二次是在FlutterFirebaseMessagingBackgroundExecutor 中创建了FlutterEngine 过程中初始化插件。 - 删除了Firebase_messaging 以后(包括其他相关代码)JPush 一切正常。 到这里,基本可以断定是Firebase_messaging 插件导致的。
翻看代码
在这里看到,backgroundFlutterEngine 生成的FlutterEngine 是作为 backgroundFlutterEngine 使用。(具体为什么,到现在没找到原因)
问题分析
- firebase_messaging 会导致所有插件再次初始化,疑问点是,为什么其他插件正常,反倒只有极光不正常
- 根据上一个疑问,我们回看JPushPlugin 发现代码中,有JpushReceiver, (极光SDK 中用于接收回调事件的地方),并且跟踪代码,这里所有的 调用,都是直接调用 JPushPlugin 中的静态方法,并且,静态方法中,通过instance 获取到 JpushPlugin 实例。
- 问题点就出在这里,因为重复初始化,导致生产了两个JpushPlugin 实例,极光的初始化,需要在dart 代码中调用setup 进行初始化,然而,setup 的过程在第一个实例化的时候就开始了,第二个实例化对象并没有setup 导致dartIsReady 始终为false
- 并且所有JpushReceiver 使用的instance 都是第二个实例化出来的对象。
结束
最终我们通过删除了firebase_messaging 解决了这个问题,但是实际业务上会有使用到firebase_messaging 相关,但是因为目前的问题,只能删除。
实际上到此,这个问题已经花了4 个小时去查找了。
结束2
第一次对接FCM 的同事想了一想这个问题,并且联系起来,
在firebase_messageing 集成过程中有这么一行
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
删除了以后,使用firebase_messageing ,Jpush 也正常了
完美解决
总结:
- 在代码中,慎用static 这类型全局静态变量,因为无论从Android GC的角度,还是开发可读性的角度,甚至是代码维护的角度,都是不良的。
- 根据问题反思,这个问题,在Flutter 的module 模式下也会出现,因为 Flutter 原生的Module 是多引擎模型。(当然,如果是module 也不会单独集成极光插件)
- 解决问题过程中,忽略了太多Log 细节,实际上 dartIsReady = false 这个问题,自己翻看代码和Log 就能发现(解决BUG 的过程中,发现dartIsReady = false 这里已经过去了3小时)
- 在Flutter 开发中,所有插件都是代码形式下载到本地,在运行过程中,才进行编译和打包的,所以可以开心的本地修改插件内的代码,去验证问题,(但是涉及到其他同事插件是pub 下载的,所以也只能是调试用)