第三方推送集成


说明

当app在后台运行时环信IM SDK默认通过一个后台服务保持一条连接环信服务器的长连接,但Android为了解决系统待机性能差的问题,随着Android版本的升级逐渐禁止了app级别的后台服务的运行.所以在一些版本比较高的Android系统上会有接收不到消息的情况.为了提高消息的到达率,我们SDK增加了对第三方推送服务的支持,包括小米推送,华为推送,Google FCM推送.

各推送使用条件:
  • 小米推送:在小米系统上可用
  • 华为推送:在华为系统上可用
  • Google FCM:需要Google Play Service和能连接Google服务器的网络
  • 如果未设置第三方推送或者不满足使用第三方推送的条件,环信IM SDK会通过一些保活手段尽可能的保持与环信服务器的长连接,已确保消息的及时送达

建议:如果你的App有海外使用场景,建议开启FCM推送;由于各推送使用条件不同,建议尽可能同时支持小米和华为推送。

消息推送流程:

消息推送流程图

  1. 判断设备支持哪种推送,(app配置了第三方推送并且满足该推送的使用条件)
  2. 根据集成第三方推送SDK获取推送token
  3. 上传证书名称(环信服务器用来判断使用哪种推送通道)和推送token到环信服务器
  4. 向某设备发送消息时,环信服务器会先判断目标设备是否在线,如果目标设备不在线,则判断目标设备使用了哪种推送通道(根据目标设备上传的证书名称),使用该推送通道通过第三方推送服务器将消息推送至目标设备
环信服务器应具备的能力:
  • 拥有向你的app发送推送消息的能力
    • 开发者通过环信后台配置App的推送证书,推送证书会要求填写证书名称(或者appKey),证书名称是环信服务器用来判断目标设备使用哪种推送通道的唯一条件,所以证书名称必须与Android终端设备上传的证书名称一致
  • 跟终端Android设备一一对应的推送token
    • 需Android设备上报
  • 推送token属于哪个推送通道
    • 需Android设备上报
Android设备需要做的事:
  • 判断当前设备支持哪种推送通道
  • 通过集成第三方推送SDK获取推送token
  • 上传证书名称(注意跟通过环信后台配置的证书名称保持一致)和推送token至环信服务器(该步骤必须在环信SDK登录成功后)

Google FCM推送集成

SDK 3.4.2 版本开始默认优先使用FCM推送,在一些带Google Play Service的华为设备上,开发者可通过设置EMOptions#setUseFCM(false)关闭FCM推送的使用,来达到使用华为推送的目的。

说明

  • FCM 使用主要针对海外用户;
  • FCM 要求设备安装有 Google Play 服务、 Google Play 商店 和 能连接Google服务器的网络。

服务端

1.登录Firebase管理后台

2.在Firebase欢迎界面点击 Add Project,输入相应内容并点击 Create Project

3.在Firebase欢迎界面选择 Add Firebase to your Android App

4.选择应用类型后需要输入包名、项目昵称、SHA-1,然后点击 Register App

5.进入引导页,如下图,点击按钮下载google-services.json文件到本地。注意该json文件在Android项目中的放置位置。

6.跳过引导页,点击Cloud Messaging tab页,复制Server Key和Sender ID。

7.登录环信管理后台,选择你的应用—选择推送证书—新增证书,证书的名称要求填上方复制Sender ID,证书秘钥填写上方复制的Server Key。

移动端

1.添加Google Play Service相关依赖库

用于检查设备是否支持Google FCM推送,该步骤在FCM官方集成文档上不存在,由于国内特殊的使用环境,所以我们增加了该配置用于辅助检测

compile 'com.google.android.gms:play-services-base:11.4.0'该行配置添加到项目相应的build.gradle文件中,SDK demo中的配置在easeui/build.gradle中,如下:

dependencies {
    // 添加此行
    compile 'com.google.android.gms:play-services-base:11.4.0'
}

注意:Google推送相关依赖库版本必须对应(该文档中均为:11.4.0),否则可能会出现类冲突的错误。

2.在project-level的build.gradle中添加FCM相关库文件配置:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // 添加此行
        classpath 'com.google.gms:google-services:3.1.1'
    }
}

allprojects {
    repositories {
        // 添加此行
        maven { url 'https://maven.google.com' }
    }
}

3.在app-level的build.gradle中添加FCM相关库文件配置:

dependencies {
// 添加此行,Google Firebase cloud messaging
    compile 'com.google.firebase:firebase-messaging:11.4.0'
}

// 此行添加在文件末尾
apply plugin: 'com.google.gms.google-services'

注意:Google推送相关依赖库版本必须对应(该文档中均为:11.4.0),否则可能会出现类冲突的错误。

4.放置下载的google-services.json在app-level的根目录下

5.实现一个继承自FirebaseInstanceIdService的自定义service,该类用于监听FCM token的创建和更新。一个设备对应一个FCM token,该token用于服务端向该设备推送消息,所以该token创建或更新后需及时上传至环信服务器。

自定义FirebaseInstanceIdService:

public class EMFCMTokenRefreshService extends FirebaseInstanceIdService {
    private static final String TAG = "FCMTokenRefreshService";

    @Override
    public void onTokenRefresh() {
        super.onTokenRefresh();
        String token = FirebaseInstanceId.getInstance().getToken();
        Log.i(TAG, "onTokenRefresh: " + token);
        // Important, send the fcm token to the server
        EMClient.getInstance().sendFCMTokenToServer(token);
    }
}

AndroidManifest.xml:

<service android:name=".fcm.EMFCMTokenRefreshService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
    </intent-filter>
</service>

6.实现一个继承自FirebaseMessagingService的自定义service,该类用于FCM在后台进行接收应用推送消息的处理。并把该service注册到AndroidManifest.xml中。

自定义FirebaseMessagingService:

public class EMFCMMSGService extends FirebaseMessagingService {
    private static final String TAG = "EMFCMMSGService";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        if (remoteMessage.getData().size() > 0) {
            String message = remoteMessage.getData().get("alert");
            Log.i(TAG, "onMessageReceived: " + message);
        }
    }
}

AndroidManifest.xml:

<service android:name=".fcm.EMFCMMSGService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

7.通过EMOptions#setUseFCM(true)接口设置允许使用FCM推送,SDK会进行FCM推送条件的检查,并在FCM推送条件满足的情况下使用FCM推送。接口使用可参考Demo中的DemoHelper。

8.通过EMOptions#setFCMNumber(String SenderID)接口设置证书名称(注意必须与通过环信后台设置的证书名称保持一致)。接口使用可参考Demo中的DemoHelper。

推送服务测试

为了确保推送服务的成功集成,可按如下步骤进行测试,

  1. 运行app并进行登录
  2. 杀掉该app进程(通过Android任务列表滑动结束进程,不是按Home键让app进入后台;通过 设置→应用→强行停止 结束app进程,app无法接收到FCM推送,详情请查阅Google官方对该操作的解释。)
  3. 向该登录账号发送消息,app会收到通过推送服务送达的消息

华为HMS推送集成

创建华为应用

首先就是去华为开发者后台创建应用,并开启 push 服务,并上传对应的证书指纹,具体可以看下华为官方介绍: 华为HMS消息推送服务集成

上传推送证书

注册完整后,需要在环信开发者后台上传推送证书,选择你的应用—>推送证书—>Huawei—>新增证书,然后输入你在华为开发者后台创建的应用的APPIDAPP SECRET以及程序的包名

SDK3.4.x 华为推送重大更新

为了方便用户自己升级华为推送相关sdk,环信 SDK 在3.4.x之后的版本中将华为推送的集成从SDK转移到应用层,SDK提供上传华为推送token的接口供用户调用,方便华为推送升级时用户自行升级,以后的版本就需要开发者自己去集成华为推送相关功能,然后调用下边的方法将 token 发送到环信服务器:

// 上传 token 方法,token 是通过广播接收器接收到的
EMClient.getInstance().sendHMSPushTokenToServer("华为appId", "注册华为的 token");

PS:需要注意,此方法必须是登录成功后才能调用,所以请求华为 token 需要放在登录成功之后,所以我们请求华为推送 token 一般放在 MainActivity 类中,环信 IM 的 Demo也已经集成了华为最新推送 SDK,开发者也可以参考 demo 进行集成,token 的获去就是在广播接收器中,Demo 中有实现HMSPushReceiver类,可以看下 demo 的代码

这是华为官方集成文档,开发者可以自己根据华为官方文档进行集成华为推送 华为消息推送服务集成官方文档

Demo 中将华为的 HMSAgent 做成了一个 module 进行引用(这里没有对华为 HMSAgent 进行任何封装和修改),开发者可以直接进行使用,也可以直接下载华为官方最新的HMSAgent自己进行集成,如果使用 demo 中的 module 需要修改以下几个地方:

<application>
    <!-- 接入HMSSDK 需要注册的appid参数。value的值中“10492024”用实际申请的appid替换,来源于开发者联盟网站应用的权益详情。格式 android:value="appid=xxxxxx"-->
    <meta-data
        android:name="com.huawei.hms.client.appid"
        android:value="appid=10492024" />
    <!-- 接入HMSSDK 需要注册的provider,authorities 一定不能与其他应用一样,所以这边 com.hyphenate.chatuidemo 要替换上您应用的包名-->
    <provider
        android:name="com.huawei.hms.update.provider.UpdateProvider"
        android:authorities="com.hyphenate.chatuidemo.hms.update.provider"
        android:exported="false"
        android:grantUriPermissions="true" />
    <!-- 接入HMSSDK 需要注册的provider,authorities 一定不能与其他应用一样,所以这边 com.hyphenate.chatuidemo 要替换上您应用的包名-->
    <provider
        android:name="com.huawei.updatesdk.fileprovider.UpdateSdkFileProvider"
        android:authorities="com.hyphenate.chatuidemo.updateSdk.fileProvider"
        android:exported="false"
        android:grantUriPermissions="true"/>
        ...
</application>

Demo在集成华为推送时将调用华为推送的几个方法都放在了HMSPushHelper类中,开发者可以进行参考使用

配置完这些之后,在满足条件的华为设备上就可以使用华为推送接收离线推送通知了; 这里的满足条件是指:华为设备必须安装2.6.+以上的华为移动服务,以及开启当前 app 的自启动权限

故障排查

当开发者做完这些之后如果在华为设备上还是收不到推送,可以看下控制台的输出,或者环信 sdcard 上保存的日志,是否有一下日志输出:

// 当设备的华为移动服务版本比较低的时候,无法启用华为推送,会有以下输出
huawei mobile services is not available. please upgrade
// 当注册 token 时,华为开发者后台证书不对应,或者没有开通 Push 服务,当所有的都确认没问题后,如果还是有这样的问题,这个需要联系华为技术支持查看下是否生效
hms service connection suspended. error: 6xxx 或 9xxxxxxxx

后边的 error 为华为官方错误码,更多错误码请参考华为官方错误表:表3-1 HMS 通用错误码及处理方式

解绑 token

使用第三方推送时需要在退出登录时解绑设备 token,调用EMClient#getInstance()#logout(true)或者EMClient#getInstance()#logout(true,callback)方法,如果是被踢的情况下,则要求设置为 false。


推送的配置选项

用户可以在消息扩展中增加特定的字段来实现消息的推送配置。

发送静默消息(不推送)

(Android 发消息)

EMMessage message = EMMessage.createSendMessage(EMMessage.Type.TXT);
EMTextMessageBody txtBody = new EMTextMessageBody("test");
message.setTo("6006");
// 设置自定义扩展字段
message.setAttribute("em_ignore_notification", true);
// 设置消息回调
message.setMessageStatusCallback(new EMCallBack() {...});
// 发送消息
EMClient.getInstance().chatManager().sendMessage(message);

强制推送

(Android 发消息)

EMMessage message = EMMessage.createSendMessage(EMMessage.Type.TXT);
EMTextMessageBody txtBody = new EMTextMessageBody("test");
message.setTo("6006");
// 设置自定义扩展字段
message.setAttribute("em_force_notification", true);
// 设置消息回调
message.setMessageStatusCallback(new EMCallBack() {...});
// 发送消息
EMClient.getInstance().chatManager().sendMessage(message);

自定义推送提示

(Android 发消息)

// 这里只是一 TXT 消息为例,IMAGE FILE 等类型的消息设置方法相同
EMMessage message = EMMessage.createSendMessage(EMMessage.Type.TXT);
EMTextMessageBody txtBody = new EMTextMessageBody("消息内容");
message.setTo("6006");
// 设置自定义推送提示
JSONObject extObject = new JSONObject();
try {
    extObject.put("em_push_name", "离线推送标题");
    extObject.put("em_push_content", "离线推送内容部分");
} catch (JSONException e) {
    e.printStackTrace();
}
// 将推送扩展设置到消息中
message.setAttribute("em_apns_ext", extObject);
// 设置消息回调
message.setMessageStatusCallback(new EMCallBack() {...});
// 发送消息
EMClient.getInstance().chatManager().sendMessage(message);