原文地址
现在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端要复杂一点点,但是!看完我这篇文章,你就会觉得安卓端的实现也是很简单的一件事情。
准备工作
- 腾讯云需要注册一个账号,并且开通腾讯移动推送服务(这项是收费的服务,需要氪金!!!)
- 需要在各大手机厂商开通推送服务(因为离线推送是要走厂商通道)。目前腾讯移动推送支持小米、华为、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.MethodCall
class 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 = 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的方法。
我们在项目里面lib
文件夹下新建一个dart文件,我这边就要直接叫tools.dart
。写入以下内容
import 'package:flutter/services.dart';
class Tools {
static const MethodChannel _channel = MethodChannel("zrong.life/tools");
Future
这样,在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.GREEN
notificationManager.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的离线消息推送也全部完成了
原文地址