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.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 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.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的离线消息推送也全部完成了

原文地址

你可能感兴趣的:(flutter接入推送功能,包含IOS端APNs推送,Android端的推送(支持离线推送))