有奖捉虫:云通信与企业服务文档专题,速来> HOT

概述

即时通信 IM 的终端用户需要随时都能够得知最新的消息,而由于移动端设备的性能与电量有限,当 App 处于后台时,为了避免维持长连接而导致的过多资源消耗,即时通信 IM 推荐您使用各厂商提供的系统级推送通道来进行消息通知,系统级的推送通道相比第三方推送拥有更稳定的系统级长连接,可以做到随时接受推送消息,且资源消耗大幅降低。
下面是自主集成推送方式指引,仅支持普通消息推送功能。如果您想快速集成推送能力,并有需求场景是将营销广告、通知、新闻咨询等推送给所有用户或者指定群体,还需要消息推送的全链路排查工具、推送记录和各类型指标统计数据,旨在查看和提升推送触达率、点击率和转化率等各类指标,请关注接入推送插件
注意
在没有主动退出登录的情况下,应用退后台、手机锁屏、或者应用进程被用户主动杀掉三种场景下,如果想继续接收到 IM 消息提醒,可以接入即时通信 IM 离线推送。
如果应用主动调用 logout 退出登录,或者多端登录被踢下线,即使接入了 IM 离线推送,也收不到离线推送消息。

跑通离线推送功能

TUIKitDemo 已经按照如下步骤接入了离线推送功能,文档中已有源码指引链接可供参考。

步骤1:注册应用到厂商推送平台

离线推送功能依赖厂商原始通道,您需要将自己的应用注册到各个厂商的推送平台,得到 AppID 和 AppKey 等参数。目前国内支持的手机厂商有:小米华为荣耀OPPOVIVO魅族,境外支持 Google FCM

步骤2:IM 控制台配置

登录腾讯云 即时通信 IM 控制台 ,添加各个厂商推送证书,并将您在步骤1中获取的各厂商的 AppId、AppKey、AppSecret 等参数配置给 IM 控制台的推送证书,其中单击后续动作参见步骤3。
小米
华为
荣耀
OPPO
vivo
魅族
Google FCM
厂商推送平台
IM 控制台配置







厂商推送平台
IM 控制台配置



注:Client ID 对应 AppID,Client Secret 对应 AppSecret




厂商推送平台
IM 控制台配置







厂商推送平台
IM 控制台配置







厂商推送平台
IM 控制台配置







厂商推送平台
IM 控制台配置







厂商推送平台
IM 控制台配置







注意
对于小米厂商,如果在厂商开发者官网配置了 ChannelID,需要在 即时通信 IM 控制台 配置同样的 ChannelID,否则可能推送不成功。

步骤3:配置离线推送跳转界面

收到离线推送后,通知栏会显示推送信息如图所示,单击通知栏会打开应用并进入配置的跳转界面。请您参见下面的步骤,配置单击通知消息后跳转的 Activity。



控制台配置 各个厂商的跳转界面配置方式有所不同,具体如下:
厂商
单击后后续动作
应用内指定界面
小米
打开应用内指定页面
intent://您配置的 hostname/您配置的 path#Intent;scheme=您配置的协议,也就是您定义的 scheme;launchFlags=0x4000000;component=您应用跳转界面的完整类名;endTUIKitDemo 配置的是:intent://com.tencent.qcloud/detail#Intent;scheme=pushscheme;launchFlags=0x4000000;component=com.tencent.qcloud.tim.tuikit/com.tencent.qcloud.tim.demo.main.MainActivity;end
华为
打开应用内指定页面
intent://您配置的 hostname/您配置的 path#Intent;scheme=您配置的协议,也就是您定义的 scheme;launchFlags=0x4000000;component=您应用跳转界面的完整类名;endTUIKitDemo 配置的是:intent://com.tencent.qcloud/detail#Intent;scheme=pushscheme;launchFlags=0x4000000;component=com.tencent.qcloud.tim.tuikit/com.tencent.qcloud.tim.demo.main.MainActivity;end
荣耀
打开应用内指定页面
intent://您配置的 hostname/您配置的 path#Intent;scheme=您配置的协议,也就是您定义的 scheme;launchFlags=0x4000000;component=您应用跳转界面的完整类名;endTUIKitDemo 配置的是:intent://com.tencent.qcloud/detail#Intent;scheme=pushscheme;launchFlags=0x4000000;component=com.tencent.qcloud.tim.tuikit/com.tencent.qcloud.tim.demo.main.MainActivity;end
魅族
打开应用内指定页面
您应用跳转界面的完整类名TUIKitDemo 配置的是:com.tencent.qcloud.tim.demo.main.MainActivity
OPPO
打开应用内指定页面
您应用跳转界面的完整类名TUIKitDemo 配置的是:activity:com.tencent.qcloud.tim.demo.main.MainActivity
vivo
打开应用内指定页面
intent://您配置的 hostname/您配置的 path#Intent;scheme=您配置的协议,也就是您定义的 scheme;launchFlags=0x4000000;component=您应用跳转界面的完整类名;endTUIKitDemo 配置的是:intent://com.tencent.qcloud/detail#Intent;scheme=pushscheme;launchFlags=0x4000000;component=com.tencent.qcloud.tim.tuikit/com.tencent.qcloud.tim.demo.main.MainActivity;end
Google FCM
不需要配置
默认会跳转至应用的 Launcher 界面
清单文件配置 在应用工程清单文件 AndroidManifest.xml 中完成跳转界面的相关配置,需要注意的是,该配置必须与您在 IM 控制台推送证书的单击后续动作配置保持一致。
<!-- TUIKitDemo 配置的跳转界面是 MainActivity,所以这里填 com.tencent.qcloud.tim.demo.main.MainActivity。集成到您的应用后,需要替换您的应用界面完整类名 -->
<activity
android:name="您应用跳转界面的完整类名"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize|stateHidden">

<!-- 离线推送打开应用内页面 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- TUIKitDemo 配置的是:pushscheme://com.tencent.qcloud/detail -->
<data
android:host="您配置的 hostname"
android:path="您配置的 path"
android:scheme="您配置的协议,也就是您定义的 scheme" />
</intent-filter>

</activity>

步骤4:配置厂商推送规则

应用离线参数配置 步骤2推送证书添加成功之后,IM 控制台会为您分配一个对应厂商的证书 ID,请您进行本地保存。在登录完成后注册推送服务时候,将获取的厂商 token和该证书 ID 通过接口 setOfflinePushConfig 给到后台。以小米为例: 推送证书 ID 如下:



本地保存证书 ID 和推送参数:
public class PrivateConstants {
/****** 小米离线推送参数start ******/
// 在腾讯云控制台上传第三方推送证书后分配的证书 ID
public static final long XM_PUSH_BUZID = 您应用分配的证书 ID;
// 小米开放平台分配的应用APPID及APPKEY
public static final String XM_PUSH_APPID = "您应用分配的 APPID";
public static final String XM_PUSH_APPKEY = "您应用分配的 APPKEY";
/****** 小米离线推送参数end ******/
}
清单文件配置厂商推送权限相关 清单文件中需要添加各个厂商的推送规则以及推送继承类,具体如下:
小米
华为
荣耀
OPPO
vivo
魅族
Google FCM
<!-- 注意:TUIKitDemo 的 applicationId 是 com.tencent.qcloud.tim.tuikit,这里的 “xxxx” 需要替换您的应用的 applicationId。 -->

<!-- ********小米推送权限设置******** -->
<permission
android:name="xxxx.permission.MIPUSH_RECEIVE"
android:protectionLevel="signature" />

<uses-permission android:name="xxxx.permission.MIPUSH_RECEIVE" />

<!-- ********小米推送service和receiver设置start******** -->
<service
android:name="com.xiaomi.push.service.XMPushService"
android:enabled="true"
android:process=":pushservice" />
<service
android:name="com.xiaomi.push.service.XMJobService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":pushservice" />
<!-- 注:此service必须在3.0.1版本以后(包括3.0.1版本)加入 -->
<service
android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
android:enabled="true"
android:exported="true" />
<service
android:name="com.xiaomi.mipush.sdk.MessageHandleService"
android:enabled="true" />
<!-- 注:此service必须在2.2.5版本以后(包括2.2.5版本)加入 -->
<receiver
android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name="com.xiaomi.push.service.receivers.PingReceiver"
android:exported="false"
android:process=":pushservice">
<intent-filter>
<action android:name="com.xiaomi.push.PING_TIMER" />
</intent-filter>
</receiver>
<!-- 自实现小米推送的接收广播 -->
<receiver
android:name="xxxx.XiaomiMsgReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.ERROR" />
</intent-filter>
</receiver>
<!-- ********小米推送service和receiver设置end******** -->
<!-- 注意:TUIKitDemo 的 applicationId 是 com.tencent.qcloud.tim.tuikit,这里的 “xxxx” 需要替换您的应用的 applicationId。 -->

<!-- ********华为推送权限设置******** -->
<permission
android:name="xxxx.permission.PROCESS_PUSH_MSG"
android:protectionLevel="signatureOrSystem" />
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" />
<uses-permission android:name="xxxx.permission.PROCESS_PUSH_MSG" />

<!-- ********华为推送设置start******** -->
<service
android:name="xxxx.HUAWEIHmsMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT"/>
</intent-filter>
</service>
<!-- ********华为推送设置end******** -->
<!-- ********荣耀推送设置start******** -->
<service
android:name="xxxx.MyHonorMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.hihonor.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- ********荣耀推送设置end******** -->
<!-- ********OPPO 推送权限设置******** -->
<uses-permission android:name="com.coloros.mcs.permission.RECIEVE_MCS_MESSAGE" />
<uses-permission android:name="com.heytap.mcs.permission.RECIEVE_MCS_MESSAGE" />

<!-- ********OPPO 推送 start******** -->
<service
android:name="com.heytap.msp.push.service.CompatibleDataMessageCallbackService"
android:permission="com.coloros.mcs.permission.SEND_MCS_MESSAGE">
<intent-filter>
<action android:name="com.coloros.mcs.action.RECEIVE_MCS_MESSAGE" />
</intent-filter>
</service>
<!-- 兼容Q以下版本 -->
<service
android:name="com.heytap.msp.push.service.DataMessageCallbackService"
android:permission="com.heytap.mcs.permission.SEND_PUSH_MESSAGE">
<intent-filter>
<action android:name="com.heytap.mcs.action.RECEIVE_MCS_MESSAGE" />
<action android:name="com.heytap.msp.push.RECEIVE_MCS_MESSAGE" />
</intent-filter>
</service>
<!-- 兼容Q版本 -->
<!-- ********OPPO 推送 end******** -->
<!-- ********vivo推送设置start******** -->
<service
android:name="com.vivo.push.sdk.service.CommandClientService"
android:exported="true" />
<activity
android:name="com.vivo.push.sdk.LinkProxyClientActivity"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<!-- push应用定义消息receiver声明 -->
<receiver android:name="xxxx.VIVOPushMessageReceiverImpl">
<intent-filter>
<!-- 接收push消息 -->
<action android:name="com.vivo.pushclient.action.RECEIVE" />
</intent-filter>
</receiver>
<!-- ********vivo推送设置end******** -->
<!-- 注意:TUIKitDemo 的 applicationId 是 com.tencent.qcloud.tim.tuikit,这里的 “xxxx” 需要替换您的应用的 applicationId。 -->

<!-- 兼容flyme3.0配置权限 -->
<permission
android:name="xxxx.permission.C2D_MESSAGE"
android:protectionLevel="signature" />

<uses-permission android:name="com.meizu.c2dm.permission.RECEIVE" />
<uses-permission android:name="xxxx.permission.C2D_MESSAGE" />

<!-- ********魅族推送设置start******** -->
<receiver android:name="xxxx.MEIZUPushReceiver">
<intent-filter>
<!-- 接收push消息 -->
<action android:name="com.meizu.flyme.push.intent.MESSAGE" />
<!-- 接收register消息 -->
<action android:name="com.meizu.flyme.push.intent.REGISTER.FEEDBACK" />
<!-- 接收unregister消息 -->
<action android:name="com.meizu.flyme.push.intent.UNREGISTER.FEEDBACK" />
<!-- 兼容低版本Flyme3推送服务配置 -->
<action android:name="com.meizu.c2dm.intent.REGISTRATION" />
<action android:name="com.meizu.c2dm.intent.RECEIVE" />

<category android:name="com.tencent.qcloud.tim.demo.thirdpush" />
</intent-filter>
</receiver>
<!-- ********魅族推送设置end******** -->
<!-- 注意:TUIKitDemo 的 applicationId 是 com.tencent.qcloud.tim.tuikit,这里的 “xxxx” 需要替换您的应用的 applicationId。 -->

<!-- ********海外google云消息传递start******** -->
<service
android:name="xxxx.GoogleFCMMsgService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- ********海外google云消息传递end******** -->
vivo 和荣耀适配 根据 vivo 和荣耀厂商接入指引,需要将 APPID 和 APPKEY 添加到清单文件中,否则会出现编译问题:
方法1
方法2
android {
...
defaultConfig {
...
manifestPlaceholders = [
"VIVO_APPKEY" : "您应用分配的证书 APPKEY",
"VIVO_APPID" : "您应用分配的证书 APPID"
"HONOR_APPID" : "您应用分配的证书 APPID"
]
}
}
// vivo begin
<receiver android:name="com.tencent.qcloud.tim.demo.thirdpush.VIVOPushMessageReceiverImpl">
<intent-filter>
<!-- 接收push消息 -->
<action android:name="com.vivo.pushclient.action.RECEIVE" />
</intent-filter>
</receiver>

<meta-data tools:replace="android:value"
android:name="com.vivo.push.api_key"
android:value="您应用分配的证书 APPKEY" />
<meta-data tools:replace="android:value"
android:name="com.vivo.push.app_id"
android:value="您应用分配的证书 APPID" />
// vivo end

// honor begin
<service
android:name="com.tencent.qcloud.tim.tuiofflinepush.oempush.MyHonorMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.hihonor.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>

<meta-data tools:replace="android:value"
android:name="com.hihonor.push.app_id"
android:value="您应用分配的证书 APPID" />
// honor end
华为和 Google FCM 适配 华为和 Google FCM 需要按照厂商方法,集成对应的 plugin 和 json 配置文件。
1.1 下载配置文件添加到工程根目录。
华为
Google FCM






1.2 在项目级 build.gradle 文件中 buildscript -> dependencies 下添加以下配置:
classpath 'com.google.gms:google-services:4.2.0'
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
在项目级 build.gradle 文件中 allprojects -> repositories 下添加以下配置:
mavenCentral()
// 配置HMS Core SDK的Maven仓地址。
maven {url 'https://developer.huawei.com/repo/'}
添加后效果如下:
repositories {
...
// 配置HMS Core SDK的Maven仓地址。
maven {url 'https://developer.huawei.com/repo/'}
}

dependencies {
...
classpath 'com.google.gms:google-services:4.2.0'
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
}
1.3 在应用级 build.gradle 文件中添加下方配置。
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.huawei.agconnect'
1.4 单击项目右上角 Sync Now 同步项目。

步骤5:集成厂商推送 SDK

集成 SDK 在应用的 build.gradle 文件中添加厂商推送 SDK。
dependencies {
......
// 华为
implementation 'com.tencent.timpush:huawei:7.7.5282'
// Google FCM
implementation 'com.tencent.timpush:fcm:7.7.5282'
// 小米
implementation 'com.tencent.timpush:xiaomi:7.7.5282'
// OPPO
implementation 'com.tencent.timpush:oppo:7.7.5282'
// vivo
implementation 'com.tencent.timpush:vivo:7.7.5282'
// honor
implementation 'com.tencent.timpush:honor:7.7.5282'
// 魅族
implementation 'com.tencent.timpush:meizu:7.7.5282'
}
添加推送类 引入厂商推送类,各个厂商推送方式有区别,具体如下:
小米
华为
荣耀
OPPO
vivo
魅族
Google FCM
package xxx.xxx.xxx;
import android.content.Context; import android.text.TextUtils; import com.tencent.qcloud.tim.tuiofflinepush.utils.TUIOfflinePushErrorBean; import android.util.Log; import com.xiaomi.mipush.sdk.ErrorCode; import com.xiaomi.mipush.sdk.MiPushClient; import com.xiaomi.mipush.sdk.MiPushCommandMessage; import com.xiaomi.mipush.sdk.MiPushMessage; import com.xiaomi.mipush.sdk.PushMessageReceiver; import java.util.List; import java.util.Map; public class XiaomiMsgReceiver extends PushMessageReceiver { private static final String TAG = XiaomiMsgReceiver.class.getSimpleName(); private String mRegId; @Override public void onReceivePassThroughMessage(Context context, MiPushMessage miPushMessage) { Log.d(TAG, "onReceivePassThroughMessage is called. "); } @Override public void onNotificationMessageClicked(Context context, MiPushMessage miPushMessage) { Log.d(TAG, "onNotificationMessageClicked miPushMessage " + miPushMessage.toString()); if (OEMPushSetting.mPushCallback == null) { return; } Map<String, String> extra = miPushMessage.getExtra(); String ext = extra.get("ext"); if (TextUtils.isEmpty(ext)) { Log.w(TAG, "onNotificationMessageClicked: no extra data found"); return; }
// IM 控制台点击后续动作选择为"打开应用",点击通知栏会回调此处
} @Override public void onNotificationMessageArrived(Context context, MiPushMessage miPushMessage) { Log.d(TAG, "onNotificationMessageArrived is called. "); } @Override public void onReceiveRegisterResult(Context context, MiPushCommandMessage miPushCommandMessage) { Log.d(TAG, "onReceiveRegisterResult is called. " + miPushCommandMessage.toString()); String command = miPushCommandMessage.getCommand(); List<String> arguments = miPushCommandMessage.getCommandArguments(); String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null); Log.d(TAG, "cmd: " + command + " | arg: " + cmdArg1 + " | result: " + miPushCommandMessage.getResultCode() + " | reason: " + miPushCommandMessage.getReason()); if (MiPushClient.COMMAND_REGISTER.equals(command)) { if (miPushCommandMessage.getResultCode() == ErrorCode.SUCCESS) { mRegId = cmdArg1; } } Log.d(TAG, "regId: " + mRegId); // 获取 token 后调用接口上报 IM 后台
} @Override public void onCommandResult(Context context, MiPushCommandMessage miPushCommandMessage) { super.onCommandResult(context, miPushCommandMessage); } }
package xxx.xxx.xxx; import com.huawei.hms.push.HmsMessageService; import com.huawei.hms.push.RemoteMessage; import com.tencent.qcloud.tim.tuiofflinepush.TUIOfflinePushConfig; import com.tencent.qcloud.tim.tuiofflinepush.utils.TUIOfflinePushErrorBean; import android.util.Log; public class HUAWEIHmsMessageService extends HmsMessageService { private static final String TAG = HUAWEIHmsMessageService.class.getSimpleName(); @Override public void onMessageReceived(RemoteMessage message) { Log.i(TAG, "onMessageReceived message=" + message); } @Override public void onMessageSent(String msgId) { Log.i(TAG, "onMessageSent msgId=" + msgId); } @Override public void onSendError(String msgId, Exception exception) { Log.i(TAG, "onSendError msgId=" + msgId); } @Override public void onNewToken(String token) { Log.i(TAG, "onNewToken token=" + token); // 获取 token 后调用接口上报 IM 后台
} @Override public void onTokenError(Exception exception) { Log.i(TAG, "onTokenError exception=" + exception); } @Override public void onMessageDelivered(String msgId, Exception exception) { Log.i(TAG, "onMessageDelivered msgId=" + msgId); } }

package xxx.xxx.xxx;
import android.text.TextUtils; import com.hihonor.push.sdk.HonorMessageService; import com.hihonor.push.sdk.bean.DataMessage; import android.util.Log; public class MyHonorMessageService extends HonorMessageService { private static final String TAG = MyHonorMessageService.class.getSimpleName(); @Override public void onNewToken(String token) { Log.i(TAG, "onNewToken token=" + token); if (TextUtils.isEmpty(token)) { return; } // 获取 token 后调用接口上报 IM 后台
} @Override public void onMessageReceived(DataMessage dataMessage) { Log.i(TAG, "onMessageReceived message=" + dataMessage); } }
package xxx.xxx.xxx;
import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; import com.heytap.msp.push.callback.ICallBackResultService; import com.tencent.qcloud.tim.tuiofflinepush.utils.TUIOfflinePushErrorBean; import android.util.Log; public class OPPOPushImpl implements ICallBackResultService { private static final String TAG = OPPOPushImpl.class.getSimpleName(); @Override public void onRegister(int responseCode, String registerID) { Log.i(TAG, "onRegister responseCode: " + responseCode + " registerID: " + registerID); if (responseCode != 0) { // 打印报错 } else { // 获取 token 后调用接口上报 IM 后台 } } @Override public void onUnRegister(int responseCode) { Log.i(TAG, "onUnRegister responseCode: " + responseCode); } @Override public void onSetPushTime(int responseCode, String s) { Log.i(TAG, "onSetPushTime responseCode: " + responseCode + " s: " + s); } @Override public void onGetPushStatus(int responseCode, int status) { Log.i(TAG, "onGetPushStatus responseCode: " + responseCode + " status: " + status); } @Override public void onGetNotificationStatus(int responseCode, int status) { Log.i(TAG, "onGetNotificationStatus responseCode: " + responseCode + " status: " + status); } @Override public void onError(int i, String s) { Log.i(TAG, "onError code: " + i + " string: " + s); } }

package xxx.xxx.xxx;
import android.content.Context; import android.util.Log; import com.vivo.push.model.UPSNotificationMessage; import com.vivo.push.sdk.OpenClientPushMessageReceiver; import java.util.Map; public class VIVOPushMessageReceiverImpl extends OpenClientPushMessageReceiver { private static final String TAG = VIVOPushMessageReceiverImpl.class.getSimpleName(); private static String sExt = ""; @Override public void onNotificationMessageClicked(Context context, UPSNotificationMessage upsNotificationMessage) { Log.i(TAG, "onNotificationMessageClicked upsNotificationMessage " + upsNotificationMessage.toString()); Map<String, String> extra = upsNotificationMessage.getParams(); sExt = extra.get("ext"); } public static String getParams() { String tmp = sExt; sExt = ""; return tmp; } @Override public void onReceiveRegId(Context context, String regId) { Log.i(TAG, "onReceiveRegId = " + regId); } }
package xxx.xxx.xxx;
import android.content.Context; import android.content.Intent; import com.meizu.cloud.pushsdk.MzPushMessageReceiver; import com.meizu.cloud.pushsdk.handler.MzPushMessage; import com.meizu.cloud.pushsdk.notification.PushNotificationBuilder; import com.meizu.cloud.pushsdk.platform.message.PushSwitchStatus; import com.meizu.cloud.pushsdk.platform.message.RegisterStatus; import com.meizu.cloud.pushsdk.platform.message.SubAliasStatus; import com.meizu.cloud.pushsdk.platform.message.SubTagsStatus; import com.meizu.cloud.pushsdk.platform.message.UnRegisterStatus; import android.util.Log; public class MEIZUPushReceiver extends MzPushMessageReceiver { private static final String TAG = MEIZUPushReceiver.class.getSimpleName(); @Override public void onMessage(Context context, String s) { Log.i(TAG, "onMessage method1 msg = " + s); } @Override public void onMessage(Context context, String message, String platformExtra) { Log.i(TAG, "onMessage method2 msg = " + message + ", platformExtra = " + platformExtra); } @Override public void onMessage(Context context, Intent intent) { String content = intent.getExtras().toString(); Log.i(TAG, "flyme3 onMessage = " + content); } @Override public void onUpdateNotificationBuilder(PushNotificationBuilder pushNotificationBuilder) { super.onUpdateNotificationBuilder(pushNotificationBuilder); } @Override public void onNotificationClicked(Context context, MzPushMessage mzPushMessage) { Log.i(TAG, "onNotificationClicked mzPushMessage " + mzPushMessage.toString()); } @Override public void onNotificationArrived(Context context, MzPushMessage mzPushMessage) { super.onNotificationArrived(context, mzPushMessage); } @Override public void onNotificationDeleted(Context context, MzPushMessage mzPushMessage) { super.onNotificationDeleted(context, mzPushMessage); } @Override public void onNotifyMessageArrived(Context context, String s) { super.onNotifyMessageArrived(context, s); } @Override public void onPushStatus(Context context, PushSwitchStatus pushSwitchStatus) {} @Override public void onRegisterStatus(Context context, RegisterStatus registerStatus) { Log.i(TAG, "onRegisterStatus token = " + registerStatus.getPushId()); // 获取 token 后调用接口上报 IM 后台
} @Override public void onUnRegisterStatus(Context context, UnRegisterStatus unRegisterStatus) {} @Override public void onSubTagsStatus(Context context, SubTagsStatus subTagsStatus) {} @Override public void onSubAliasStatus(Context context, SubAliasStatus subAliasStatus) {} @Override public void onRegister(Context context, String s) {} @Override public void onUnRegister(Context context, boolean b) {} }
package xxx.xxx.xxx;
import com.google.firebase.messaging.FirebaseMessagingService; import android.util.Log; public class GoogleFCMMsgService extends FirebaseMessagingService { private static final String TAG = GoogleFCMMsgService.class.getSimpleName(); @Override public void onNewToken(String token) { super.onNewToken(token); Log.i(TAG, "onNewToken google fcm onNewToken : " + token); // 获取 token 后调用接口上报 IM 后台
} }
推送服务注册 应合规要求,在用户同意隐私协议登录成功后,分别初始化注册各个厂商推送服务,并在注册结果回调处保存注册成功后的 token,并调用 setOfflinePushConfig 接口上报推送 token 至后台。部分厂商在注册后,调用一些接口也会返回 token,可以再次同步更新下,具体参见以下代码。
public void init() {
...

if (BrandUtil.isBrandXiaoMi()) {
// 小米离线推送
MiPushClient.registerPush(this, PrivateConstants.XM_PUSH_APPID, PrivateConstants.XM_PUSH_APPKEY);
} else if (BrandUtil.isBrandHonor()) {
HonorMessaging.getInstance(context).turnOnPush().addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(com.hihonor.push.sdk.tasks.Task<Void> task) {
if (task.isSuccessful()) {
TUIOfflinePushLog.i(TAG, "Honor turnOn push successfully.");
} else {
TUIOfflinePushLog.i(TAG, "Honor turnOn push failed." + task.getException());
}
}
});

new Thread(new Runnable() {
@Override
public void run() {
try {
String pushToken = HonorInstanceId.getInstance(context).getPushToken();
TUIOfflinePushLog.i(TAG, "Honor get pushToken " + pushToken);

//判断pushToken是否为空
if(!TextUtils.isEmpty(token)) {
// 该 token 需要保存并调用 setOfflinePushConfig 接口上报 IMSDK
String pushToken = token;
}
} catch (com.hihonor.push.sdk.common.data.ApiException e) {
TUIOfflinePushLog.i(TAG, "Honor get pushToken failed, " + e);
}
}
}).start();
}
else if (BrandUtil.isBrandHuawei()) {
// 华为离线推送,设置是否接收Push通知栏消息调用示例
HmsMessaging.getInstance(this).turnOnPush().addOnCompleteListener(new com.huawei.hmf.tasks.OnCompleteListener<Void>() {
@Override
public void onComplete(com.huawei.hmf.tasks.Task<Void> task) {
if (task.isSuccessful()) {
DemoLog.i(TAG, "huawei turnOnPush Complete");
} else {
DemoLog.e(TAG, "huawei turnOnPush failed: ret=" + task.getException().getMessage());
}
}
});

new Thread() {
@Override
public void run() {
try {
// read from agconnect-services.json
String appId = AGConnectServicesConfig.fromContext(MainActivity.this).getString("client/app_id");
String token = HmsInstanceId.getInstance(MainActivity.this).getToken(appId, "HCM");
DemoLog.i(TAG, "huawei get token:" + token);
if(!TextUtils.isEmpty(token)) {
// 该 token 需要保存并调用 setOfflinePushConfig 接口上报 IMSDK
String pushToken = token;
}
} catch (ApiException e) {
DemoLog.e(TAG, "huawei get token failed, " + e);
}
}
}.start();
} else if (MzSystemUtils.isBrandMeizu(this)) {
// 魅族离线推送
PushManager.register(this, PrivateConstants.MZ_PUSH_APPID, PrivateConstants.MZ_PUSH_APPKEY);
} else if (BrandUtil.isBrandVivo()) {
// vivo离线推送
PushClient.getInstance(getApplicationContext()).initialize();

DemoLog.i(TAG, "vivo support push: " + PushClient.getInstance(getApplicationContext()).isSupport());
PushClient.getInstance(getApplicationContext()).turnOnPush(new IPushActionListener() {
@Override
public void onStateChanged(int state) {
if (state == 0) {
String regId = PushClient.getInstance(getApplicationContext()).getRegId();
DemoLog.i(TAG, "vivopush open vivo push success regId = " + regId);

// 该 token 需要保存并调用 setOfflinePushConfig 接口上报 IMSDK
String pushToken = token;
} else {
// 根据vivo推送文档说明,state = 101 表示该vivo机型或者版本不支持vivo推送,链接:https://dev.vivo.com.cn/documentCenter/doc/156
DemoLog.i(TAG, "vivopush open vivo push fail state = " + state);
}
}
});
} else if (HeytapPushManager.isSupportPush()) {
// oppo离线推送
OPPOPushImpl oppo = new OPPOPushImpl();
oppo.createNotificationChannel(this);
// oppo接入文档要求,应用必须要调用init(...)接口,才能执行后续操作。
HeytapPushManager.init(this, false);
HeytapPushManager.register(this, PrivateConstants.OPPO_PUSH_APPKEY, PrivateConstants.OPPO_PUSH_APPSECRET, oppo);
} else if (BrandUtil.isGoogleServiceSupport()) {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new com.google.android.gms.tasks.OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
DemoLog.w(TAG, "getInstanceId failed exception = " + task.getException());
return;
}

// Get new Instance ID token
String token = task.getResult().getToken();
DemoLog.i(TAG, "google fcm getToken = " + token);

// 该 token 需要保存并调用 setOfflinePushConfig 接口上报 IMSDK
String pushToken = token;
}
});
}
}
以华为为例,在注册结果回调处保存注册成功后的 token,并调用 setOfflinePushConfig 接口上报给后台。
public class HUAWEIHmsMessageService extends HmsMessageService {

...

@Override
public void onNewToken(String token) {
DemoLog.i(TAG, "onNewToken token=" + token);

// 该 token 需要保存并调用 setOfflinePushConfig 接口上报 IMSDK
String pushToken = token;
}

...
}
推送证书和 token 上报后台 调用 setOfflinePushConfig 接口上报推送 token。构造 V2TIMOfflinePushConfig 类,需设置 businessID 为对应厂商的证书 ID,上报注册厂商推送服务获取的 token。
V2TIMOfflinePushConfig v2TIMOfflinePushConfig = null;
// 需要设置 businessID 为对应厂商的证书 ID,上报注册厂商推送服务获取的 token。
v2TIMOfflinePushConfig = new V2TIMOfflinePushConfig(businessID, token);
V2TIMManager.getOfflinePushManager().setOfflinePushConfig(v2TIMOfflinePushConfig, new V2TIMCallback() {
@Override
public void onError(int code, String desc) {
DemoLog.d(TAG, "setOfflinePushToken err code = " + code);
}

@Override
public void onSuccess() {
DemoLog.d(TAG, "setOfflinePushToken success");
}
});

步骤6:前后台状态同步

如果您的应用退到后台,收到新消息时需要在手机通知栏进行展示,请您调用 IMSDK 的 doBackground() 接口,将应用的状态同步给 IM 后台;当应用回到前台时,请您调用 IMSDK 的 doForeground() 接口,将应用的状态同步给 IM 后台。
// 应用切到后台时
V2TIMManager.getOfflinePushManager().doBackground(totalCount, new V2TIMCallback() {
@Override
public void onError(int code, String desc) {
DemoLog.e(TAG, "doBackground err = " + code + ", desc = " + desc);
}

@Override
public void onSuccess() {
DemoLog.i(TAG, "doBackground success");
}
});
// 应用切回前台时
V2TIMManager.getOfflinePushManager().doForeground(new V2TIMCallback() {
@Override
public void onError(int code, String desc) {
DemoLog.e(TAG, "doForeground err = " + code + ", desc = " + desc);
}

@Override
public void onSuccess() {
DemoLog.i(TAG, "doForeground success");
}
});

步骤7:发消息时设置离线推送参数

调用 sendMessage 发送消息时,您可以通过 V2TIMOfflinePushInfo 设置离线推送参数,可以参照 ChatProvider 的 sendMessage() 方法:
OfflineMessageContainerBean containerBean = new OfflineMessageContainerBean();
OfflineMessageBean entity = new OfflineMessageBean();
entity.content = message.getExtra().toString();
entity.sender = message.getFromUser();
entity.nickname = chatInfo.getChatName();
entity.faceUrl = TUIChatConfigs.getConfigs().getGeneralConfig().getUserFaceUrl();
containerBean.entity = entity;

V2TIMOfflinePushInfo v2TIMOfflinePushInfo = new V2TIMOfflinePushInfo();
v2TIMOfflinePushInfo.setExt(new Gson().toJson(containerBean).getBytes());
// OPPO必须设置ChannelID才可以收到推送消息,这个channelID需要和控制台一致
v2TIMOfflinePushInfo.setAndroidOPPOChannelID("tuikit");

final V2TIMMessage v2TIMMessage = message.getTimMessage();
String msgID = V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, isGroup ? null : userID, isGroup ? groupID : null,
V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, v2TIMOfflinePushInfo, new V2TIMSendCallback<V2TIMMessage>() {
@Override
public void onProgress(int progress) {

}

@Override
public void onError(int code, String desc) {
TUIChatUtils.callbackOnError(callBack, TAG, code, desc);
}

@Override
public void onSuccess(V2TIMMessage v2TIMMessage) {
TUIChatLog.v(TAG, "sendMessage onSuccess:" + v2TIMMessage.getMsgID());
message.setMsgTime(v2TIMMessage.getTimestamp());
TUIChatUtils.callbackOnSuccess(callBack, message);
}
});

步骤8:解析离线推送消息

收到离线推送的通知栏消息,点击会自动跳转到您在 步骤3 配置的跳转界面,可以在界面启动的 onResume() 方法中,调用 getIntent().getExtras() 获取透传的离线推送参数再自定义跳转。具体可以参照 TUIKitDemo 的 handleOfflinePush() 方法。
private void handleOfflinePush() {
// 根据登录状态,判断是否需要重新登录 IM
// 1. 如果登录状态为 V2TIMManager.V2TIM_STATUS_LOGOUT,跳转到登录界面,重新登录 IM
if (V2TIMManager.getInstance().getLoginStatus() == V2TIMManager.V2TIM_STATUS_LOGOUT) {
Intent intent = new Intent(MainActivity.this, SplashActivity.class);
if (getIntent() != null) {
intent.putExtras(getIntent());
}
startActivity(intent);
finish();
return;
}

// 2. 否则,说明 App 只是处于退后台的状态,直接解析离线推送参数
final OfflineMessageBean bean = OfflineMessageDispatcher.parseOfflineMessage(getIntent());
if (bean != null) {
setIntent(null);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (manager != null) {
manager.cancelAll();
}

if (bean.action == OfflineMessageBean.REDIRECT_ACTION_CHAT) {
if (TextUtils.isEmpty(bean.sender)) {
return;
}
TUIUtils.startChat(bean.sender, bean.nickname, bean.chatType);
}
}
}

注意
FCM 单击通知栏的消息会默认跳转至应用的默认 Launcher 界面,您可以在界面启动的 onResume() 方法中,通过调用 getIntent().getExtras() 获取透传的离线推送参数再自定义跳转。
以上完成后,当您的应用退到后台或者进程被杀掉时,消息会进行离线推送通知栏展示,可单击通知栏跳转到设定的应用界面,完成实现离线推送功能。

离线推送自定义推送铃音

Android 8.0 以前系统设置,接口调用 setAndroidSound()setIOSSound()

1. 定制的铃音资源文件,Android 添加到工程 raw 目录下,iOS 链接进 Xcode 工程。
2. 消息指定使用自定义的铃音。
V2TIMOfflinePushInfo v2TIMOfflinePushInfo = new V2TIMOfflinePushInfo();
v2TIMOfflinePushInfo.setAndroidSound("铃音名称");
v2TIMOfflinePushInfo.setIOSSound("铃音名称.mp3");

String msgID = V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, isGroup ? null : userID, isGroup ? groupID : null,
V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, v2TIMOfflinePushInfo, new V2TIMSendCallback<V2TIMMessage>() {
@Override
public void onProgress(int progress) {
TUIChatUtils.callbackOnProgress(callBack, progress);
}

@Override
public void onError(int code, String desc) {
TUIChatUtils.callbackOnError(callBack, TAG, code, desc);
}

@Override
public void onSuccess(V2TIMMessage v2TIMMessage) {

}
});
注意
IMSDK 6.1.2155 及以上版本支持。
接口支持华为、小米、FCM 和 APNS。

Android 8.0 及以后系统设置需要通过 channel 实现。

华为 与 ANPS 华为、APNS 仍然调用 setAndroidSound()setIOSSound() 来设置离线推送铃音提示。
小米
1.1 登录厂商控制台 创建 channel 并做好配置,其中铃音文件需要添加到您本地 Android Studio 工程的 raw 目录下。



1.2 发送消息指定自定义铃音的 channel ID,详见 setAndroidXiaoMiChannelID
V2TIMOfflinePushInfo v2TIMOfflinePushInfo = new V2TIMOfflinePushInfo();
v2TIMOfflinePushInfo.setAndroidXiaoMiChannelID("厂商申请的 channel ID");

String msgID = V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, isGroup ? null : userID, isGroup ? groupID : null,
V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, v2TIMOfflinePushInfo, new V2TIMSendCallback<V2TIMMessage>() {
@Override
public void onProgress(int progress) {
TUIChatUtils.callbackOnProgress(callBack, progress);
}

@Override
public void onError(int code, String desc) {
TUIChatUtils.callbackOnError(callBack, TAG, code, desc);
}

@Override
public void onSuccess(V2TIMMessage v2TIMMessage) {

}
});
FCM
1.1 创建需要自定义铃音的 channel。
您需要在代码中先默认创建好带自定义铃音的 channel,铃音文件需要添加到您本地 Android Studio 工程的 raw 目录下,并记录下 channel ID 的名称。
1.2 发送消息指定自定义铃音的 channel ID,详见 setAndroidFCMChannelID
V2TIMOfflinePushInfo v2TIMOfflinePushInfo = new V2TIMOfflinePushInfo();
v2TIMOfflinePushInfo.setAndroidFCMChannelID(PrivateConstants.fcmPushChannelId);

String msgID = V2TIMManager.getMessageManager().sendMessage(v2TIMMessage, isGroup ? null : userID, isGroup ? groupID : null,
V2TIMMessage.V2TIM_PRIORITY_DEFAULT, false, v2TIMOfflinePushInfo, new V2TIMSendCallback<V2TIMMessage>() {
@Override
public void onProgress(int progress) {
TUIChatUtils.callbackOnProgress(callBack, progress);
}

@Override
public void onError(int code, String desc) {
TUIChatUtils.callbackOnError(callBack, TAG, code, desc);
}

@Override
public void onSuccess(V2TIMMessage v2TIMMessage) {

}
});
注意
IMSDK 7.0.3754 及以上版本支持。
FCM 自定义铃声或者设置 channnel id 仅支持证书模式。




常见问题

收不到离线推送怎么排查?

OPPO 手机

OPPO 手机收不到推送一般有以下几种情况:
按照 OPPO 推送官网要求,在 Android 8.0 及以上系统版本的 OPPO 手机上必须配置 ChannelID,否则推送消息无法展示。配置方法可以参见 setAndroidOPPOChannelID
OPPO 安装应用通知栏显示默认关闭,需要确认下开关状态。

Google FCM

收不到推送需要确认下 IM 控制台是否正确上传证书。排查路径参照文档 “IM 控制台配置 - Google FCM”,对照示意图看下是否添加正确。

发送消息为自定义消息

自定义消息的离线推送和普通消息不太一样,自定义消息的内容我们无法解析,不能确定推送的内容,所以默认不推送,如果您有推送需求,需要您在 sendMessage 的时候设置 offlinePushInfodesc 字段,推送的时候会默认展示 desc 信息。

设备通知栏设置影响

离线推送的直观表现就是通知栏提示,所以同其他通知一样受设备通知相关设置的影响,以华为为例:
“手机设置-通知-锁屏通知-隐藏或者不显示通知”,会影响锁屏状态下离线推送通知显示。
“手机设置-通知-更多通知设置-状态栏显示通知图标”,会影响状态栏下离线推送通知的图标显示。
“手机设置-通知-应用的通知管理-允许通知”,打开关闭会直接影响离线推送通知显示。
“手机设置-通知-应用的通知管理-通知铃声” 和 “手机设置-通知-应用的通知管理-静默通知”,会影响离线推送通知铃音的效果。

按照流程接入完成,还是收不到离线推送

首先在 IM 控制台通过 离线测试工具 自测下是否可以正常推送。 推送异常情况,设备状态异常,需要检查下 IM 控制台配置各项参数是否正确,再者需要检查下代码初始化注册逻辑,包括厂商推送服务注册和 IM 设置离线推送配置相关逻辑是否正确设置。 推送异常情况,设备状态正常,需要看下是否需要正确填写 channel ID 或者后台服务是否正常。
离线推送依赖厂商能力,一些简单的字符可能会被厂商过滤不能透传推送。
如果离线推送消息出现推送不及时或者偶尔收不到情况,需要看下厂商的推送限制。

跳转界面不成功怎么排查?

单击离线推送消息的通知栏,跳转到指定界面,原理是后台根据您在控制台配置的各个厂商的跳转方式和界面参数,根据厂商接口规则,传递给厂商服务器,单击时候进行对应界面启动跳转。对应界面启动还依赖清单文件的配置,必须和控制台配置的相对应,才能正确启动和跳转。
1. 首先需要重点排查下控制台和清单文件相关配置是否对应且正确,可参见 TUIKitDemo 的配置,注意部分厂商提供接口方式存在差异。
2. 如果跳转到了配置的界面,需要再看下配置界面内离线消息的解析和界面重定向是否正常。

厂商推送限制

1. 国内厂商都有消息分类机制,不同类型也会有不同的推送策略。如果想要推送及时可靠,需要按照厂商规则设置自己应用的推送类型为高优先级的系统消息类型或者重要消息类型。反之,离线推送消息会受厂商推送消息分类影响,与预期会有差异。
2. 另外,一些厂商对于应用每天的推送数量也是有限制的,可以在厂商控制台查看应用每日限制的推送数量。 如果离线推送消息出现推送不及时或者偶尔收不到情况,需要考虑下这里:
华为:华为推送从 EMUI 10.0版本开始将通知消息智能分成两个级别:服务与通讯资讯营销。EMUI 10.0之前的版本没有对通知消息进行分类,只有一个级别,消息全部通过“默认通知”渠道展示,等价于 EMUI 10.0的服务与通讯。资讯营销类消息的每日推送数量自2023年01月05日起根据应用类型对推送数量进行上限管理,服务与通讯类消息每日推送数量不受限。另外,消息分类还和自分类权益有关:
无自分类权益,推送消息厂商还会进行二次智能分类 。
有申请自分类权益,且推送消息携带 category 字段,消息会按照自定义的分类进行推送,详见 setAndroidHuaWeiCategory。 具体请参见 厂商描述1厂商描述2
荣耀:荣耀手机推送和系统版本有关。
当前荣耀通道仅支持国内 Magic UI 4.0 及以上和海外 Magic UI 4.2 及以上荣耀设备使用。
低于上述版本的荣耀设备可以按照华为厂商接入推送。 具体请参见 厂商描述
vivo:推送服务将于 2023 年 4 月 3 日起,优化消息分类规则,并调整不同消息类别的数量限制。
关于消息分类优化,增加二级分类 category 字段,并根据不同二级分类配置不同推送速度。接口详见 setAndroidVIVOCategory 。category 默认值可前往 IM 控制台配置
关于消息数量限制调整,系统消息可申请不限量权限,不限制单应用单用户单日接收数量;运营消息单日可推送量级 = 通知开启的有效用户数 * 倍数,默认倍数为 2 倍,新闻资讯类应用为 3 倍。单应用单用户单日接收数量限制为 2 条,新闻资讯类应用为 5 条。 具体请参见 厂商描述1厂商描述2
OPPO:将推送消息分为私信消息类和公信消息类,推送效果和策略不同。其中私信消息是针对用户有一定关注度,且希望能及时接收的信息,私信通道权益需要邮件申请。公信通道推送数量有限制。 具体请参见 厂商描述1厂商描述2
小米:将推送消息分为“私信消息”和“公信消息”两个类别,默认通道为公信消息。公信消息的单日推送数量将进行上限管理,公信消息适用于推送热点新闻、新品推广、平台公告、社区话题、有奖活动等,多为用户普适性的内容。私信消息适用于推送聊天消息、个人订单变化、快递通知、交易提醒、IOT系统通知等与私人通知相关的内容,通知消息的推送数量不受限制。消息分类管理实现需要在厂商控制台进行 channel 申请及接入。 具体请参见 厂商描述1厂商描述2
魅族:推送消息数量有限制。 具体请参见 厂商描述
FCM:推送上行消息频率有限制。 具体请参见 厂商描述