原文地址
最近公司的APP需要接入推送功能,市面上挑选、对比了很多推送服务商,最终Android端选定了腾讯的移动推送(最主要是有现成的插件可用)。IOS端直接有插件可用的,这里记录下实现推送一路的坎坷。
IOS端的实现
ios端实现起来就很简单,我们只需要使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | @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<dynamic> onPush(Map<String, dynamic> data) { return Future.value(); } // 点击消息的回调 Future<dynamic> onResume(Map<String, dynamic> data) { // 这里完成用户的逻辑 return Future.value(); } // 静默push的回调 Future<dynamic> onBackgroundPush(Map<String, dynamic> data) async { return Future.value(); } // 冷启动点击通知栏的回调 Future<dynamic> onLaunch(Map<String, dynamic> data) { return Future.value(); } |
到此为止,APNs推送就集成完毕了,是不是非常简单!对的,IOS端的推送就是这么简单!!!
Android端的实现
安卓端的实现稍微比IOS端要复杂一点点,但是!看完我这篇文章,你就会觉得安卓端的实现也是很简单的一件事情。
准备工作
- 腾讯云需要注册一个账号,并且开通腾讯移动推送服务(这项是收费的服务,需要氪金!!!)
- 需要在各大手机厂商开通推送服务(因为离线推送是要走厂商通道)。目前腾讯移动推送支持小米、华为、OPPO、VIVO、魅族、FCM的推送服务
当上面这些东西准备好之后就可以开撸了!!!
这边我们需要引入
集成方法
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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腾讯自建通道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | @override void initState() { super.initState(); // 这里初始化腾讯信鸽服务 initXgPushState(); } // 初始化腾讯信鸽推送 Future<void> 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<String, dynamic> msg) async { // 收到通知回调 }, onReceiveMessage: (Map<String, dynamic> 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<String, dynamic> msg) async { // 通知点击事件回调 } ); } |
代码混淆。在
1 2 3 4 5 6 | -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、导入依赖
1 | implementation 'com.tencent.tpns:xiaomi:[VERSION]-release' |
2、开启小米推送
1 2 3 4 5 6 | if (await XgFlutterPlugin.xgApi.isMiuiRom()) { XgFlutterPlugin.xgApi.setMiPushAppId(appId: "小米的AppId"); XgFlutterPlugin.xgApi.setMiPushAppKey(appKey: "小米的AppKey"); } XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush(); |
3、代码混淆,在
1 2 | -keep class com.xiaomi.**{*;} -keep public class * extends com.xiaomi.mipush.sdk.PushMessageReceiver |
实现OPPO通道
1、导入依赖
1 | implementation 'com.tencent.tpns:oppo:[VERSION]-release' |
2、开启OPPO推送
1 2 3 4 5 6 7 | 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、代码混淆,在
1 2 | -keep public class * extends android.app.Service -keep class com.heytap.mcssdk.** {*;} |
实现VIVO通道
1、在
1 2 3 4 | manifestPlaceholders = [ VIVO_APPID: "vivo推送的appid" VIVO_APPKEY: "vivo推送的appkey", ] |
2、导入依赖
1 | implementation 'com.tencent.tpns:vivo:[VERSION]-release' |
3、开启vivo推送
1 2 3 4 5 | if (await XgFlutterPlugin.xgApi.isVivoRom()) { } XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush(); |
4、代码混淆,在
1 2 3 4 | -dontwarn com.vivo.push.** -keep class com.vivo.push.**{*; } -keep class com.vivo.vms.**{*; } -keep class com.tencent.android.vivopush.VivoPushMessageReceiver{*;} |
实现魅族通道
1、导入依赖
1 | implementation 'com.tencent.tpns:meizu:[VERSION]-release' |
2、开启魅族推送
1 2 3 4 5 6 | if (await XgFlutterPlugin.xgApi.isMeizuRom()) { XgFlutterPlugin.xgApi.setMzPushAppId(appId: "魅族的appId"); XgFlutterPlugin.xgApi.setMzPushAppKey(appId: "魅族的appKey"); } XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush(); |
3、代码混淆,在
1 2 | -dontwarn com.meizu.cloud.pushsdk.** -keep class com.meizu.cloud.pushsdk.**{*;} |
实现华为通道
1、在
1 2 3 | manifestPlaceholders = [ HW_APPID: "华为的APPID" ] |
2、导入依赖
1 | implementation 'com.tencent.tpns:huawei:[VERSION]-release' |
3、开启华为推送
1 2 3 4 5 | if (await XgFlutterPlugin.xgApi.isEmuiRom()) { } XgFlutterPlugin.xgApi.enableOtherPush(); XgFlutterPlugin.xgApi.regPush(); |
4、代码混淆,在
1 2 3 4 5 6 7 8 9 10 | -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】
我们打开插件目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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】
这个地方有两处需要修改
1、插件目录
1 2 3 4 5 6 7 | /// 获取信鸽token static Future<String> get xgToken async { final String version = await _channel.invokeMethod('xgToken'); // 修改为 final String version = await _channel.invokeMethod('getXgToken'); return version; } |
2、插件目录
1 2 3 4 5 6 7 8 9 | /// 获取token /// 第一次注册会产生 Token,之后一直存在手机上,不管以后注销注册操作,该 Token 一直存在, /// 当 App 完全卸载重装了 Token 会发生变化。不同 App 之间的 Token 不一样。 Future<String> getXgToken() async { final String token = await _channel.invokeMethod('xgToken'); // 修改为 final String token = await _channel.invokeMethod('getXgToken'); return token; } |
到这里呢,腾讯移动推送的安卓端已经集成完毕了!同学们可以试试看看,把APP完全退出,试试离线推送能不能送达。注意:华为手机需要在签名包下才能推送哦
最后,我们需要处理一下消息的点击事件
处理消息的点击事件
因为有些厂商通道是不支持消息的点击回调的(例如:小米),所以我们这边采用Intent的方式来跳转。
1、在项目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <activity android:name=".MainActivity" ..... > ..... <!-- 这边配置我们的intent --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="配置你自己的scheme(例如:zrong)" android:host="配置你自己的host(例如:launch)" /> </intent-filter> ..... </activity> |
2、在我们项目代码中
注意:我这边的示例代码是用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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.MethodCall class MainActivity: FlutterActivity() { private var TAG = "| zrong.life,tools |" // 通道名称 private var CHANNEL = "zrong.life/tools" // native端的Intent数据 private var intentMap = mutableMapOf<String, String>() 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<String, String>() if (uri != null) { val set = uri.queryParameterNames for (name in set) { val value = uri.getQueryParameter(name) paramsMap[name.toString()] = value.toString() } } intentMap = paramsMap channel.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的方法。
我们在项目里面
1 2 3 4 5 6 7 8 9 10 | import 'package:flutter/services.dart'; class Tools { static const MethodChannel _channel = MethodChannel("zrong.life/tools"); Future<Map> getIntent() async { Map result = await _channel.invokeMethod("getIntent"); return result; } } |
这样,在APP启动时,在任意的页面我们引入这个
综上,我们创建推送的intent就是
1 2 3 4 | { param1: a, param2: b } |
扩展方法
因为我司的项目还集成了腾讯即时通讯IM,需要用到离线消息推送的功能(在这里特别感谢蒋具宏大神提供的腾讯即时通讯IM的flutter插件)。当我仔细阅读两个服务的离线推送文档,发现两者其实是一样的。所以,我决定尝试一下,果真!行的通!不需要任何的配置,就直接可以跑通了!本人亲测是可行的。因为IM需要设置离线推送的TOKEN,所以,正好我们可以借用信鸽插件来实现。代码如下:
1、我们打开插件目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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、插件目录
1 2 3 4 | static Future<String> get getOtherToken async { final String token = await _channel.invokeMethod("getOtherToken"); return token; } |
然后在项目中使用
** 这些都是我在项目开发中遇到的一些问题以及解决的办法,现在把它记录下来,也是一种学习的方式吧。如果您发现了bug或者有代码优化或者更好的方法请您留言,我会及时回复的。一起学习flutter,关注本站。 **
其他
1、导入的依赖中的[VERSION]指的是腾讯移动推送安卓SDK的版本号,版本号可以在腾讯移动推送Android SDK发布动态页面查看
2、配置OPPO的推送ChannelID
由于OPPO的IM消息离线通知需要配置ChannelID,所以我们还需要修改一下我们的代码
在我们的项目代码中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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.GREEN notificationManager.createNotificationChannel(channel) } } |
然后在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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的离线消息推送也全部完成了
原文地址