flutter接入推送功能,包含IOS端APNs推送,Android端的推送(支持离线推送)

原文地址
现在TPNS官方插件已全面支持各大安卓厂商的离线推送,请大家移步官方文档!官方完全体插件

最近公司的APP需要接入推送功能,市面上挑选、对比了很多推送服务商,最终Android端选定了腾讯的移动推送(最主要是有现成的插件可用)。IOS端直接有插件可用的,这里记录下实现推送一路的坎坷。
IOS端的实现 ios端实现起来就很简单,我们只需要使用flutter_apns这个插件即可轻松实现。真的是非常简单,这边我就简单的贴出一点代码,仅供大家参考一下
main.dart
@override void initState() { super.initState(); // 这里初始化IOS的apns服务 initIOSApnsState(); }// 注册IOS apns推送 initIOSApnsState () async { connector.configure( onLaunch: onLaunch, onResume: onResume, onMessage: onPush, onBackgroundMessage: onBackgroundPush, ); connector.token.addListener(() { // 这里需要将获取到的device_token发送到后台保存起来 print('Token ${connector.token.value}'); }); // 请求通知权限 connector.requestNotificationPermissions(); }// 收到消息的回调 Future onPush(Map data) { return Future.value(); }// 点击消息的回调 Future onResume(Map data) { // 这里完成用户的逻辑 return Future.value(); }// 静默push的回调 Future onBackgroundPush(Map data) async { return Future.value(); }// 冷启动点击通知栏的回调 Future onLaunch(Map data) { return Future.value(); }

到此为止,APNs推送就集成完毕了,是不是非常简单!对的,IOS端的推送就是这么简单!!!
Android端的实现 安卓端的实现稍微比IOS端要复杂一点点,但是!看完我这篇文章,你就会觉得安卓端的实现也是很简单的一件事情。
准备工作
  1. 腾讯云需要注册一个账号,并且开通腾讯移动推送服务(这项是收费的服务,需要氪金!!!)
  2. 需要在各大手机厂商开通推送服务(因为离线推送是要走厂商通道)。目前腾讯移动推送支持小米、华为、OPPO、VIVO、魅族、FCM的推送服务
当上面这些东西准备好之后就可以开撸了!!!
这边我们需要引入xg_flutter_plugin这个官方的插件。git地址在这里。这边不建议直接git引用,因为这个插件里面还是有几个小问题需要修复一下,后面我会讲下有问题的地方。推荐直接clone下来,path引入这个插件
集成方法
android/app/build.gradle文件中配置以下内容
android { ...... defaultConfig {// 控制台上注册的包名.注意application ID 和当前的应用包名以及控制台上注册应用的包名必须一致。 applicationId "您的包名" ......ndk { // 根据需要 自行选择添加的对应cpu类型的.so库。 abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a' // 还可以添加 'x86', 'x86_64', 'mips', 'mips64' }manifestPlaceholders = [XG_ACCESS_ID:"注册应用的accessid", XG_ACCESS_KEY : "注册应用的accesskey", ] ...... } ...... }

实现TPNS腾讯自建通道
main.dart
@override void initState() { super.initState(); // 这里初始化腾讯信鸽服务 initXgPushState(); }// 初始化腾讯信鸽推送 Future initXgPushState() async { String xgSdkVersion; try { // 【BUG1】这里在安卓端是有一个bug,获取不到version。后面会说到 xgSdkVersion = await XgFlutterPlugin.xgSdkVersion; } catch (e) { print("push error:" + e.toString()); }// 调试模式,默认为false XgFlutterPlugin().setEnableDebug(false); // 注册推送服务 XgFlutterPlugin.xgApi.regPush(); // 【BUG2】获取信鸽推送的token。这里在安卓端也是有一个bug。后面会说到 String xgToken = await XgFlutterPlugin.xgToken; XgFlutterPlugin().addEventHandler( onRegisteredDeviceToken: (String msg) async { // 获取设备token回调(在注册成功里面获取的) }, onRegisteredDone: (String msg) async { // 注册成功回调 }, unRegistered: (String msg) async { // 反注册回调 }, onReceiveNotificationResponse: (Map msg) async { // 收到通知回调 }, onReceiveMessage: (Map msg) async { // 收到透传通知回调 }, xgPushDidSetBadge: (String msg) async { // 设置角标回调,仅仅IOS可用(这边我们只在安卓端使用),这个可以不要 }, xgPushDidBindWithIdentifier: (String msg) async { // 绑定账号跟标签回调 }, xgPushDidUnbindWithIdentifier: (String msg) async { // 解绑账号跟标签回调 }, xgPushDidUpdatedBindedIdentifier: (String msg) async { // 更新账号跟标签回调 }, xgPushDidClearAllIdentifiers: (String msg) async { // 清除账号跟标签回调 }, xgPushClickAction: (Map msg) async { // 通知点击事件回调 } ); }

代码混淆。在/android/app/proguard-rules.pro文件中,加入以下代码
-keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep class com.tencent.android.tpush.** {*; } -keep class com.tencent.bigdata.baseapi.** {*; } -keep class com.tencent.bigdata.mqttchannel.** {*; } -keep class com.tencent.tpns.dataacquisition.** {*; }

至此,TPNS通道就集成完毕了,现在就可以开心的发推送消息了。
实现小米通道
1、导入依赖
implementation 'com.tencent.tpns:xiaomi:[VERSION]-release'

2、开启小米推送
if (await XgFlutterPlugin.xgApi.isMiuiRom()) { XgFlutterPlugin.xgApi.setMiPushAppId(appId: "小米的AppId"); XgFlutterPlugin.xgApi.setMiPushAppKey(appKey: "小米的AppKey"); } XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush();

3、代码混淆,在/android/app/proguard-rules.pro文件中,加入以下代码
-keep class com.xiaomi.**{*; } -keep public class * extends com.xiaomi.mipush.sdk.PushMessageReceiver

实现OPPO通道
1、导入依赖
implementation 'com.tencent.tpns:oppo:[VERSION]-release'

2、开启OPPO推送
if (await XgFlutterPlugin.xgApi.isOppoRom()) { XgFlutterPlugin.xgApi.setOppoPushAppId(appId: "oppo的AppId"); // 这边是oppo的appSecret!!! XgFlutterPlugin.xgApi.setOppoPushAppKey(appKey: "oppo的AppSecret"); } XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush();

3、代码混淆,在/android/app/proguard-rules.pro文件中,加入以下代码
-keep public class * extends android.app.Service -keep class com.heytap.mcssdk.** {*; }

实现VIVO通道
1、在android/app/build.gradle文件中配置配置 vivo 的 AppID 和 AppKey
manifestPlaceholders = [ VIVO_APPID: "vivo推送的appid" VIVO_APPKEY: "vivo推送的appkey", ]

2、导入依赖
implementation 'com.tencent.tpns:vivo:[VERSION]-release'

3、开启vivo推送
if (await XgFlutterPlugin.xgApi.isVivoRom()) {} XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush();

4、代码混淆,在/android/app/proguard-rules.pro文件中,加入以下代码
-dontwarn com.vivo.push.** -keep class com.vivo.push.**{*; } -keep class com.vivo.vms.**{*; } -keep class com.tencent.android.vivopush.VivoPushMessageReceiver{*; }

实现魅族通道
1、导入依赖
implementation 'com.tencent.tpns:meizu:[VERSION]-release'

2、开启魅族推送
if (await XgFlutterPlugin.xgApi.isMeizuRom()) { XgFlutterPlugin.xgApi.setMzPushAppId(appId: "魅族的appId"); XgFlutterPlugin.xgApi.setMzPushAppKey(appId: "魅族的appKey"); } XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush();

3、代码混淆,在/android/app/proguard-rules.pro文件中,加入以下代码
-dontwarn com.meizu.cloud.pushsdk.** -keep class com.meizu.cloud.pushsdk.**{*; }

实现华为通道
1、在android/app/build.gradle文件中配置配置华为的 AppID
manifestPlaceholders = [ HW_APPID: "华为的APPID" ]

2、导入依赖
implementation 'com.tencent.tpns:huawei:[VERSION]-release'

3、开启华为推送
if (await XgFlutterPlugin.xgApi.isEmuiRom()) {} XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush();

4、代码混淆,在/android/app/proguard-rules.pro文件中,加入以下代码
-ignorewarnings -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.hianalytics.android.**{*; } -keep class com.huawei.updatesdk.**{*; } -keep class com.huawei.hms.**{*; } -keep class com.huawei.android.hms.agent.**{*; }

解决插件中的BUG
上面说到插件中有两个明显的bug,在这里我说一下
【BUG1】
XgFlutterPlugin.xgSdkVersion安卓端这个接口没有返回值。原因是,插件中没有实现这个方法!!!
我们打开插件目录android/src/main/kotlin/com/tencent/tpns/xg_flutter_plugin/XgFlutterPlugin.kt文件
override fun onMethodCall(@NonNull p0:MethodCall, @NonNull p1:MethodChannel.Result) { ...... when (p0.method) { ...... // 新增一个 "xgSdkVersion" -> getXgSdkVersion(p0, p1) } ...... }// 这边需要增加一个相应的方法 fun getXgSdkVersion(call: MethodCall?, result: MethodChannel.Result) { Log.i(TAG, "调用信鸽SDK-->getXgSdkVersion()") result.success("这里随便写点什么都行") }

简单的解决这个bug了
【BUG2】
XgFlutterPlugin.xgToken这个接口不返回token。原因是,插件中的通道名称写错了(很低级的错误)
这个地方有两处需要修改
1、插件目录lib/xg_flutter_plugin.dart
/// 获取信鸽token static Future get xgToken async { final String version = await _channel.invokeMethod('xgToken'); // 修改为 final String version = await _channel.invokeMethod('getXgToken'); return version; }

2、插件目录lib/android/xg_android_api.dart中(这处不改也是行的)
/// 获取token /// 第一次注册会产生 Token,之后一直存在手机上,不管以后注销注册操作,该 Token 一直存在, /// 当 App 完全卸载重装了 Token 会发生变化。不同 App 之间的 Token 不一样。 Future getXgToken() async { final String token = await _channel.invokeMethod('xgToken'); // 修改为 final String token = await _channel.invokeMethod('getXgToken'); return token; }

到这里呢,腾讯移动推送的安卓端已经集成完毕了!同学们可以试试看看,把APP完全退出,试试离线推送能不能送达。注意:华为手机需要在签名包下才能推送哦
最后,我们需要处理一下消息的点击事件
处理消息的点击事件
因为有些厂商通道是不支持消息的点击回调的(例如:小米),所以我们这边采用Intent的方式来跳转。
1、在项目的AndroidManifest.xml配置intent
..... .....

2、在我们项目代码中app\src\main\***\MainActivity.kt中实现
注意:我这边的示例代码是用Kotlin实现的
package 你的包名 import android.os.Bundle import android.util.Log import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodCallclass MainActivity: FlutterActivity() { private var TAG = "| zrong.life,tools |"// 通道名称 private var CHANNEL = "zrong.life/tools" // native端的Intent数据 private var intentMap = mutableMapOf()override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); }override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)// flutter版本更新后使用 getFlutterEngine().getDartExecutor().getBinaryMessenger()代替 getFlutterView() val channel = MethodChannel(getFlutterEngine()?.getDartExecutor()?.getBinaryMessenger(), CHANNEL)// 这里要获取启动时的intent val uri = intent.data val paramsMap = mutableMapOf() if (uri != null) { val set = uri.queryParameterNames for (name in set) { val value = https://www.it610.com/article/uri.getQueryParameter(name) paramsMap[name.toString()] = value.toString() } }intentMap = paramsMapchannel.setMethodCallHandler { call, result -> when (call, method) { "getIntent" -> get_intent(call, result) } } }// 获取intent private fun get_intent(call: MethodCall, result: MethodChannel.Result) { Log.i(TAG, "调用${call.method}方法") result.success(intentMap) } }

3、上面这段代码实现了在APP启动时获取intent参数。接下来我们要实现dart的方法。
我们在项目里面lib文件夹下新建一个dart文件,我这边就要直接叫tools.dart。写入以下内容
import 'package:flutter/services.dart'; class Tools { static const MethodChannel _channel = MethodChannel("zrong.life/tools"); Future getIntent() async { Map result = await _channel.invokeMethod("getIntent"); return result; } }

这样,在APP启动时,在任意的页面我们引入这个tools.dart,就能获取到intent参数,这样,我们就能愉快的跳转啦!
综上,我们创建推送的intent就是zrong://launch/?param1=a¶m2=b,而我们在native端获取到的数据就是
{ param1: a, param2: b }

扩展方法
因为我司的项目还集成了腾讯即时通讯IM,需要用到离线消息推送的功能(在这里特别感谢蒋具宏大神提供的腾讯即时通讯IM的flutter插件)。当我仔细阅读两个服务的离线推送文档,发现两者其实是一样的。所以,我决定尝试一下,果真!行的通!不需要任何的配置,就直接可以跑通了!本人亲测是可行的。因为IM需要设置离线推送的TOKEN,所以,正好我们可以借用信鸽插件来实现。代码如下:
1、我们打开插件目录android/src/main/kotlin/com/tencent/tpns/xg_flutter_plugin/XgFlutterPlugin.kt文件
override fun onMethodCall(@NonNull p0:MethodCall, @NonNull p1:MethodChannel.Result) { ...... when (p0.method) { ...... // 新增一个 "getOtherToken" -> getOtherToken(p0, p1) } ...... }// 这边需要增加一个相应的方法 fun getOtherToken(call: MethodCall?, result: MethodChannel.Result) { val token: String = XGPushConfig.getOtherPushToken(if (mPluginBinding == null) registrar.context() else mPluginBinding.applicationContext) Log.i(TAG, "调用信鸽SDK-->getOtherPushToken()") result.success(token) }

2、插件目录lib/xg_flutter_plugin.dart中加入
static Future get getOtherToken async{ final String token = await _channel.invokeMethod("getOtherToken"); return token; }

然后在项目中使用XgFlutterPlugin.getOtherToken就能获取到厂商的token了!就是这么简单。这样,推送搞好了,连IM的离线消息也顺带搞好了,也是美滋滋!
** 这些都是我在项目开发中遇到的一些问题以及解决的办法,现在把它记录下来,也是一种学习的方式吧。如果您发现了bug或者有代码优化或者更好的方法请您留言,我会及时回复的。一起学习flutter,关注本站。 **
其他
1、导入的依赖中的[VERSION]指的是腾讯移动推送安卓SDK的版本号,版本号可以在腾讯移动推送Android SDK发布动态页面查看
2、配置OPPO的推送ChannelID
由于OPPO的IM消息离线通知需要配置ChannelID,所以我们还需要修改一下我们的代码
在我们的项目代码中app\src\main\***\MainActivity.kt,在原来代码的基础上再加上以下内容(这边是Kotlin实现的)
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ..... notifyChannel() ..... }// 这边实现OPPO ChannelID public fun notifyChannel() { val isOppo = Utils.isOppoRom(); Log.i(TAG, "当前是否为OPPO手机:${isOppo}") if (isOppo) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 需要在OPPO推送上新建一个私信通道// 通道名称 val channelName = "test" // 通道ID val channelId = "testPush" val importance = NotificationManager.IMPORTANCE_HIGH createNotificationChannel(channelId, channelName, importance) } } }@TargetApi(Build.VERSION_CODES.O) private fun createNotificationChannel(channelId: String, channelName: String, importance: Int) { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager var channel = notificationManager.getNotificationChannel(channelId) if (channel == null) { channel = NotificationChannel(channelId, channelName, importance) channel.setShowBadge(true) channel.description = "desc" // channel的描述 channel.enableLights(true) channel.enableVibration(true) channel.setShowBadge(true) channel.lightColor = Color.GREENnotificationManager.createNotificationChannel(channel) } }

然后在app\src\main\***\MainActivity.kt同级下新建一个Utils.java文件,写入以下内容
package 您的包名; import android.text.TextUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Utils {public static String getSystemProperty(String propName) { String line; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec("getprop " + propName); input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); line = input.readLine(); input.close(); } catch (IOException ex) { return null; } finally { if (input != null) { try { input.close(); } catch (IOException e) { } } } return line; }// 检测是否为oppo手机 public static boolean isOppoRom() { String property = getSystemProperty("ro.build.version.opporom"); return !TextUtils.isEmpty(property); } }

这样,就完成了OPPO的channelID通道的建立了
至此,IM的离线消息推送也全部完成了
【flutter接入推送功能,包含IOS端APNs推送,Android端的推送(支持离线推送)】原文地址

    推荐阅读