本文将介绍如何在 Android 工程中集成 TIMPush。
前提条件
资源 | 获取位置 | 用途 |
SDKAppID | 腾讯云控制台 > 推送服务 Push > 概览 | 调用 registerPush 注册推送服务。 |
客户端密钥 | 腾讯云控制台 > 推送服务 Push > 概览 > 客户端密钥 | 调用 registerPush 注册推送服务。Push 的客户端密钥和 Chat 的客户端密钥不同,请勿混用。 |
timpush-configs.json | 腾讯云控制台下载 | 放入 Android 应用模块的 assets 目录,TIMPush 注册时会读取该配置文件。 |
Android 工程包名 | Android 工程 applicationId | 需要与厂商平台应用包名保持一致。 |
目标厂商配置 | 对应厂商配置文档 | 确认厂商侧已开通推送服务,并已在腾讯云控制台添加厂商证书。 |
厂商配置文件 | 厂商开放平台 | 华为、荣耀、Google FCM 等厂商需要将对应 JSON 配置文件添加到工程中。 |
测试真机 | 目标厂商设备 | 厂商通道建议使用对应厂商真机验证。设备厂商与配置厂商不一致时,验证结果可能误导。 |
TIMPush 版本号 VERSION | Gradle 依赖版本。本文使用 VERSION 占位,请替换为实际版本号。建议使用最新版本;如使用 7.7.5283 以下版本,荣耀通道配置可能有差异。 |
本文示例中的
VERSION、SDKAppID、AppKey、厂商 AppID、厂商 AppKey 均为占位符,请勿在代码仓库中提交真实密钥。集成 TIMPush
请按下述操作步骤依次完成放置配置文件、配置 Gradle、补充 Manifest 与混淆。
下载并添加 TIMPush 配置文件
完成厂商配置后,在腾讯云控制台下载
timpush-configs.json,下载路径为:
下载后,将该文件添加到应用模块的
assets 目录。推荐存储路径:app/src/main/assets/timpush-configs.json,其中 app 是你项目的应用模块,可替换成实际名称。如果工程中没有 assets 目录,请在 src/main/ 下手动创建。注意:
TIMPush 注册推送时会读取该文件中的厂商证书配置,不要放在 MainActivity 同级目录、工程根目录或 res 目录。如果文件缺失或放错目录,SDK 无法获得对应厂商通道配置,可能导致注册或收消息失败。
添加厂商配置文件
部分厂商还需要把平台生成的配置文件添加到 Android 工程中。配置文件通常包含厂商项目、应用包名、证书指纹或服务账号等信息,厂商插件会在后续 Gradle 构建时读取这些文件。请在厂商配置阶段下载最新配置文件,并按厂商要求放置。
厂商 | 配置文件 | 说明 |
华为 | agconnect-services.json | 在华为 AppGallery Connect 下载。修改项目、应用信息、证书指纹或服务配置后,建议重新下载。 |
荣耀 | mcs-services.json | 在荣耀开发者服务平台下载。修改项目、应用信息或开发服务设置后,需要重新下载。 |
Google FCM | google-services.json | 在 Firebase 控制台下载。 |
推荐放置路径如下(与应用模块的
build.gradle / build.gradle.kts 同级),其中 app 是你项目的应用模块,可替换成实际名称:app/agconnect-services.json # 华为app/mcs-services.json # 荣耀app/google-services.json # Google FCM
注意:
修改厂商平台项目、应用信息、证书指纹或开发服务设置后,厂商配置文件需要重新下载并替换。
配置 Gradle 仓库
配置仓库的目的是让 Gradle 能下载 TIMPush、厂商通道 SDK 和厂商插件。腾讯云 Maven 仓库用于解析腾讯云相关依赖;华为、荣耀 SDK 依赖厂商 Maven 仓库。如果仓库缺失,常见表现是 Gradle Sync 报依赖找不到。
Android 工程可能使用 Groovy DSL 或 Kotlin DSL,请先根据文件后缀判断当前工程使用的 DSL 类型:
文件 | DSL 类型 |
settings.gradle、build.gradle | Groovy DSL |
settings.gradle.kts、build.gradle.kts | Kotlin DSL |
请根据工程使用的 Gradle 版本选择配置位置。如果不确定工程使用的 Gradle 版本,可在项目根目录
gradle/wrapper/gradle-wrapper.properties 中查看 distributionUrl 对应的版本号。注意:
不要把 Groovy 示例直接复制到
.kts 文件中。Kotlin DSL 使用双引号、括号和 uri(...) 写法。在项目级
settings.gradle(Groovy DSL)或 settings.gradle.kts(Kotlin DSL)中,同时在 pluginManagement > repositories 和 dependencyResolutionManagement > repositories 中添加仓库。pluginManagement {repositories {gradlePluginPortal()mavenCentral()maven { url "https://mirrors.tencent.com/nexus/repository/maven-public/" }maven { url "https://developer.huawei.com/repo/" }maven { url "https://developer.hihonor.com/repo" }}}dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()maven { url "https://mirrors.tencent.com/nexus/repository/maven-public/" }maven { url "https://developer.huawei.com/repo/" }maven { url "https://developer.hihonor.com/repo" }}}
pluginManagement {repositories {gradlePluginPortal()mavenCentral()maven { url = uri("https://mirrors.tencent.com/nexus/repository/maven-public/") }maven { url = uri("https://developer.huawei.com/repo/") }maven { url = uri("https://developer.hihonor.com/repo") }}}dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()maven { url = uri("https://mirrors.tencent.com/nexus/repository/maven-public/") }maven { url = uri("https://developer.huawei.com/repo/") }maven { url = uri("https://developer.hihonor.com/repo") }}}
Gradle 7.0 版本通常使用 Groovy DSL。如你的工程使用 Kotlin DSL,请将单引号改为双引号,
url "..." 改为 url = uri("...")。1. 在项目级
build.gradle 的 buildscript > repositories 中添加插件仓库。buildscript {repositories {mavenCentral()maven { url "https://mirrors.tencent.com/nexus/repository/maven-public/" }maven { url "https://developer.huawei.com/repo/" }maven { url "https://developer.hihonor.com/repo" }}dependencies {classpath 'com.google.gms:google-services:4.3.15'classpath 'com.huawei.agconnect:agcp:1.6.0.300'classpath 'com.hihonor.mcs:asplugin:2.0.1.300'}}
2. 在
settings.gradle 的 dependencyResolutionManagement > repositories 中添加依赖仓库。dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()maven { url "https://mirrors.tencent.com/nexus/repository/maven-public/" }maven { url "https://developer.huawei.com/repo/" }maven { url "https://developer.hihonor.com/repo" }}}
Gradle 7.0 以下版本通常使用 Groovy DSL。如你的工程使用 Kotlin DSL,请将单引号改为双引号,
url "..." 改为 url = uri("...")。在项目级
build.gradle 的 buildscript > repositories 和 allprojects > repositories 中添加仓库。buildscript {repositories {mavenCentral()maven { url "https://mirrors.tencent.com/nexus/repository/maven-public/" }maven { url "https://developer.huawei.com/repo/" }maven { url "https://developer.hihonor.com/repo" }}dependencies {classpath 'com.google.gms:google-services:4.3.15'classpath 'com.huawei.agconnect:agcp:1.6.0.300'classpath 'com.hihonor.mcs:asplugin:2.0.1.300'}}allprojects {repositories {google()mavenCentral()maven { url "https://mirrors.tencent.com/nexus/repository/maven-public/" }maven { url "https://developer.huawei.com/repo/" }maven { url "https://developer.hihonor.com/repo" }}}
配置厂商插件
华为、荣耀和 Google FCM 需要按厂商要求集成对应插件。插件的作用是在构建期读取上一步放置的厂商配置文件,并把厂商要求的资源、Manifest 信息或初始化配置合并进应用包。
Gradle 7.0 / 7.0 以下:上方仓库示例的
buildscript > dependencies 已包含 classpath,跳过本节。Gradle 7.1 及以上:仓库已挪到
settings.gradle,需要在项目级 build.gradle / build.gradle.kts 中追加 buildscript > dependencies 声明 classpath。项目级插件依赖示例:
buildscript {dependencies {classpath 'com.google.gms:google-services:4.3.15'classpath 'com.huawei.agconnect:agcp:1.6.0.300'classpath 'com.hihonor.mcs:asplugin:2.0.1.300'}}
buildscript {dependencies {classpath("com.google.gms:google-services:4.3.15")classpath("com.huawei.agconnect:agcp:1.6.0.300")classpath("com.hihonor.mcs:asplugin:2.0.1.300")}}
应用级插件配置示例:
apply plugin: 'com.google.gms.google-services'apply plugin: 'com.huawei.agconnect'apply plugin: 'com.hihonor.mcs.asplugin'
plugins {id("com.google.gms.google-services")id("com.huawei.agconnect")id("com.hihonor.mcs.asplugin")}
只集成某个厂商时,只添加该厂商需要的插件和配置文件即可。例如未接入 FCM 时,不需要添加
com.google.gms.google-services 插件。说明:
荣耀官方示例统一使用
buildscript > dependencies > classpath 声明 asplugin,未提供 plugins {} 块写法。如果项目级 build.gradle.kts 用 plugins {} 块声明 AGP(新建 Kotlin DSL 项目的默认方式),请在 buildscript > dependencies 中额外补上 com.android.tools.build:gradle 和 org.jetbrains.kotlin:kotlin-gradle-plugin 的 classpath(原 plugins {} 块保留不动)。应用级 plugins { id(...) } 可省略版本号(由项目级 classpath 提供),或改写为 apply plugin: "..."。集成 TIMPush 依赖
在应用模块的
build.gradle 或 build.gradle.kts 中添加 TIMPush 依赖。基础包提供 TIMPush 注册、监听和通用能力;厂商包用于对接对应厂商离线推送通道。只集成基础包而不集成目标厂商包时,设备可能无法通过该厂商通道收到离线推送。dependencies {implementation 'com.tencent.timpush:timpush:VERSION'implementation 'com.tencent.liteav.tuikit:tuicore:VERSION'implementation 'com.tencent.timpush:huawei:VERSION'implementation 'com.tencent.timpush:xiaomi:VERSION'implementation 'com.tencent.timpush:oppo:VERSION'implementation 'com.tencent.timpush:vivo:VERSION'implementation 'com.tencent.timpush:honor:VERSION'implementation 'com.tencent.timpush:meizu:VERSION'implementation 'com.tencent.timpush:fcm:VERSION'}
dependencies {implementation("com.tencent.timpush:timpush:VERSION")implementation("com.tencent.liteav.tuikit:tuicore:VERSION")implementation("com.tencent.timpush:huawei:VERSION")implementation("com.tencent.timpush:xiaomi:VERSION")implementation("com.tencent.timpush:oppo:VERSION")implementation("com.tencent.timpush:vivo:VERSION")implementation("com.tencent.timpush:honor:VERSION")implementation("com.tencent.timpush:meizu:VERSION")implementation("com.tencent.timpush:fcm:VERSION")}
请按实际目标厂商添加依赖。
目标厂商 | 依赖 |
华为 | com.tencent.timpush:huawei:VERSION |
小米 | com.tencent.timpush:xiaomi:VERSION |
OPPO | com.tencent.timpush:oppo:VERSION |
vivo | com.tencent.timpush:vivo:VERSION |
荣耀 | com.tencent.timpush:honor:VERSION |
魅族 | com.tencent.timpush:meizu:VERSION |
Google FCM | com.tencent.timpush:fcm:VERSION |
添加依赖后,请在 Android Studio 中单击 Sync Now,或选择 File > Sync Project with Gradle Files 触发 Gradle 同步。
注意:
TIMPush、TUICore 和各厂商通道包建议使用相同 VERSION,避免依赖冲突。项目已接入 IM SDK 或 TUIKit 时,也需要确认相关 SDK 版本兼容。验证:Gradle Sync 成功完成,无报错。如果报依赖找不到,请检查
VERSION 是否正确、厂商 Maven 仓库是否缺失、插件 classpath 是否配置到正确文件。配置 manifestPlaceholders 和 AndroidManifest.xml
vivo 和荣耀需要将厂商分配的 AppID / AppKey 配置到清单文件中。厂商 SDK 会从 Manifest 中读取这些值来识别当前应用;缺失或填写错误可能导致编译失败或厂商注册失败。您可以选择
manifestPlaceholders 或直接在 AndroidManifest.xml 中配置 meta-data。在
manifestPlaceholders 里添加条目:android {defaultConfig {manifestPlaceholders = ["VIVO_APPKEY": "您应用分配的证书 APPKEY","VIVO_APPID" : "您应用分配的证书 APPID","HONOR_APPID": "您应用分配的证书 APPID"]}}
android {defaultConfig {manifestPlaceholders["VIVO_APPKEY"] = "您应用分配的证书 APPKEY"manifestPlaceholders["VIVO_APPID"] = "您应用分配的证书 APPID"manifestPlaceholders["HONOR_APPID"] = "您应用分配的证书 APPID"}}
在
AndroidManifest.xml 中配置 meta-data:<application><!-- vivo begin --><meta-datatools:replace="android:value"android:name="com.vivo.push.api_key"android:value="您应用分配的证书 APPKEY" /><meta-datatools:replace="android:value"android:name="com.vivo.push.app_id"android:value="您应用分配的证书 APPID" /><!-- vivo end --><!-- honor begin --><meta-datatools:replace="android:value"android:name="com.hihonor.push.app_id"android:value="您应用分配的证书 APPID" /><!-- honor end --></application>
如果使用
tools:replace,请确认 manifest 根节点包含 tools 命名空间:<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"></manifest>
设置混淆规则
如果应用开启了代码混淆,请在
proguard-rules.pro 中加入 TIMPush 相关类不混淆规则。该配置用于避免 Release 包中 TIMPush 相关类、回调或厂商适配逻辑被混淆后无法正常调用:-keep class com.tencent.qcloud.** { *; }-keep class com.tencent.timpush.** { *; }
完成后,请重新构建 Release 包,并在真机上验证注册和收消息链路。
验证:App 可在目标厂商真机上正常安装和启动,无崩溃。如果启动崩溃,请检查
Application 声明、配置文件位置和 Manifest 合并错误。同时确认测试真机厂商与目标厂商通道一致(如不要在华为设备上验证荣耀通道)。注册推送
registerPush
registerPush 用于注册当前设备的推送 token。注册成功后,后台才能根据
registrationID 或 userID 向该设备下发离线推送。其中 appKey 的取值会影响注册方式:appKey = Push Key:注册 TIMPush 独立推送能力。Push Key 客户端密钥。appKey = null:复用 IM 登录态注册推送,必须在 IM login 成功后调用。请先根据业务场景确认调用顺序:
场景 | 调用顺序 | 后台可用推送标识 | 说明 |
仅使用 TIMPush | App 每次冷启动后调用 registerPush(appKey = Push Key) | registrationID | 适用于营销 / 活动 / 通知推送,不接入 IM SDK。 |
IM SDK + TIMPush 先注册 Push 再登录 | registerPush(appKey = Push Key) → IM login | 登录前: registrationID;登录后: registrationID + userID | 适用于希望用户未登录时也能收到营销推送的场景。 |
IM SDK + TIMPush 先登录再注册 Push | IM login → registerPush(appKey = null) | 登录后: userID注册后: userID + registrationID,此时 registrationID = userID | 适用于希望用户登录后能收到 Chat 离线消息和营销推送的场景。 |
注意:
1. 如果用户退出 IM SDK,同时集成了 IM SDK + TIMPush 的场景下已建立的
userID 与 registrationID 推送关系都会失效,需要重新完成对应注册。2. Chat 应用的密钥仅用于 IM 登录,不能作为
registerPush 的 appKey。App 冷启动、用户同意隐私政策后调用
registerPush(appKey = Push Key)。如果工程已有自定义 Application,将注册逻辑放在该类中;如果没有,需新建并在 AndroidManifest.xml 中声明。import android.app.Application;import android.util.Log;import com.tencent.timpush.TIMPushCallback;import com.tencent.timpush.TIMPushManager;public class App extends Application {private static final String TAG = "TIMPush";@Overridepublic void onCreate() {super.onCreate();registerTIMPush();}private void registerTIMPush() {int sdkAppId = 0; // TODO: Replace with your SDKAppID.String appKey = "你的客户端密钥"; // TODO: Replace with your Push Key.TIMPushManager.getInstance().registerPush(this, sdkAppId, appKey, new TIMPushCallback<Object>() {@Overridepublic void onSuccess(Object data) {Log.d(TAG, ">>>>> registerPush success, data = " + data);}@Overridepublic void onError(int errCode, String errMsg, Object data) {Log.e(TAG, ">>>>> registerPush failed, errCode = " + errCode+ ", errMsg = " + errMsg);}});}}
import android.app.Applicationimport android.util.Logimport com.tencent.timpush.TIMPushCallbackimport com.tencent.timpush.TIMPushManagerclass App : Application() {override fun onCreate() {super.onCreate()registerTIMPush()}private fun registerTIMPush() {val sdkAppId = 0 // TODO: Replace with your SDKAppID.val appKey = "你的客户端密钥" // TODO: Replace with your Push Key.TIMPushManager.getInstance().registerPush(this, sdkAppId, appKey, object : TIMPushCallback<Any?>() {override fun onSuccess(data: Any?) {Log.d("TIMPush", ">>>>> registerPush success, data = $data")}override fun onError(errCode: Int, errMsg: String?, data: Any?) {Log.e("TIMPush", ">>>>> registerPush failed, errCode = $errCode, errMsg = $errMsg")}})}}
在
AndroidManifest.xml 中声明 Application:<applicationandroid:name=".App"...></application>
请在 IM
login 成功回调中调用 registerPush(appKey = null)。import android.content.Context;import android.util.Log;import com.tencent.imsdk.v2.V2TIMCallback;import com.tencent.imsdk.v2.V2TIMManager;import com.tencent.timpush.TIMPushCallback;import com.tencent.timpush.TIMPushManager;public void loginIMAndRegisterPush(Context context) {int sdkAppId = 0; // TODO: Replace with your SDKAppID.String userID = "<YOUR_USER_ID>";String userSig = "<YOUR_USER_SIG>";V2TIMManager.getInstance().login(userID, userSig, new V2TIMCallback() {@Overridepublic void onSuccess() {TIMPushManager.getInstance().registerPush(context, sdkAppId, null, new TIMPushCallback<Object>() {@Overridepublic void onSuccess(Object data) {Log.d("TIMPush", ">>>>> registerPush success, data = " + data);}@Overridepublic void onError(int errCode, String errMsg, Object data) {Log.e("TIMPush", ">>>>> registerPush failed, errCode = " + errCode+ ", errMsg = " + errMsg);}});}@Overridepublic void onError(int code, String desc) {Log.e("TIMPush", ">>>>> IM login failed, code = " + code + ", desc = " + desc);}});}
import android.content.Contextimport android.util.Logimport com.tencent.imsdk.v2.V2TIMCallbackimport com.tencent.imsdk.v2.V2TIMManagerimport com.tencent.timpush.TIMPushCallbackimport com.tencent.timpush.TIMPushManagerfun loginIMAndRegisterPush(context: Context) {val sdkAppId = 0 // TODO: Replace with your SDKAppID.val userID = "<YOUR_USER_ID>"val userSig = "<YOUR_USER_SIG>"V2TIMManager.getInstance().login(userID, userSig, object : V2TIMCallback {override fun onSuccess() {TIMPushManager.getInstance().registerPush(context, sdkAppId, null, object : TIMPushCallback<Any?>() {override fun onSuccess(data: Any?) {Log.d("TIMPush", ">>>>> registerPush success, data = $data")}override fun onError(errCode: Int, errMsg: String?, data: Any?) {Log.e("TIMPush", ">>>>> registerPush failed, errCode = $errCode, errMsg = $errMsg")}})}override fun onError(code: Int, desc: String?) {Log.e("TIMPush", ">>>>> IM login failed, code = $code, desc = $desc")}})}
验证:
1.
registerPush 的 onSuccess 回调被触发。2. 登录腾讯云控制台 > 即时通信 IM > 推送服务 Push > 推送排查,按当前场景输入
registrationID 或 userID,确认 token 已上传。3. 如触发
onError,可按 错误码 查询 code 含义。自定义 registrationID(可选)
注意:
混用场景下,自定义
registrationID 必须与 IM 登录使用的 userID 完全一致,否则会产生账号互踢导致推送丢失。配置消息触达统计(可选)
如果业务需要统计消息触达或点击数据,请在厂商配置阶段完成回执和点击统计相关配置。该配置用于让厂商通道把触达或点击结果回传给腾讯云侧,便于在控制台查看推送效果。不同厂商支持情况不同:
厂商 | 配置说明 |
华为 | 如需统计触达或点击数据,请配置回执地址 https://api.im.qcloud.com/v3/offline_push_report/huawei。华为推送证书 ID <= 11344 时使用华为推送 v2 接口,不支持触达和点击回执;如需支持统计,请重新生成并更新证书 ID。 |
荣耀 | 如需统计触达或点击数据,请配置回执地址 https://api.im.qcloud.com/v3/offline_push_report/honor。 |
vivo | 如需统计触达或点击数据,请配置回执地址 https://api.im.qcloud.com/v3/offline_push_report/vivo,并按控制台要求配置回执 ID。 |
魅族 | 如需统计触达或点击数据,请打开回执开关并配置回执地址 https://api.im.qcloud.com/v3/offline_push_report/meizu。 |
FCM | 当前腾讯云快速接入文档说明 FCM 暂不支持推送统计功能。 |
注意:
回执地址不配置或配置错误,都会影响触达统计。
测试推送
完成上述集成步骤后,需要通过发送测试消息验证链路是否打通。发送消息前请确认:
1. Android 13 及以上已允许通知权限;
2. Android 8.0 及以上目标通知渠道已开启(包括横幅、锁屏、声音开关);
3. App 已置于后台。
发送测试消息可以采用下面几种方法:
仅集成 TIMPush 的用户,建议优先使用腾讯云控制台接入测试能力验证离线推送。
操作路径:腾讯云控制台 > 推送服务 Push > 接入测试。在接入测试页面,可以指定
registrationID 或 userID 发送离线推送测试。V2TIMOfflinePushInfo pushInfo = new V2TIMOfflinePushInfo();pushInfo.setTitle("推送标题");pushInfo.setDesc("推送内容");pushInfo.setExt("业务自定义 ext".getBytes());V2TIMManager.getMessageManager().sendMessage(v2TIMMessage,userID,null,V2TIMMessage.V2TIM_PRIORITY_DEFAULT,false,pushInfo,new V2TIMSendCallback<V2TIMMessage>() {@Overridepublic void onProgress(int progress) {}@Overridepublic void onError(int code, String desc) {Log.e("TIMPush", ">>>>> sendMessage failed, code = " + code + ", desc = " + desc);}@Overridepublic void onSuccess(V2TIMMessage message) {Log.d("TIMPush", ">>>>> sendMessage success, msgID = " + message.getMsgID());}});
sendMessage 属于 IMSDK 消息发送能力。仅集成 TIMPush 的用户不需要为了验证离线推送而额外接入完整 Chat 初始化、登录和消息发送流程。验证:App 置于后台后发送测试消息,设备能收到离线推送通知。如果手机通知栏开启权限,通知栏会弹出离线推送消息弹框。如果收不到,请参见下文「收不到推送排障流程」逐步排查。
处理通知点击跳转
通知点击跳转需要三步配合完成:控制台配置点击动作、发送推送时携带跳转参数、客户端注册监听并解析参数。三步缺一则跳转不生效。
配置控制台点击动作
在腾讯云控制台配置推送证书的「点击后续动作」,勾选「打开应用内指定页面」:
操作路径:腾讯云控制台 > 推送服务 Push > 基础配置 > 对应厂商证书 > 编辑 > 点击后续动作,选择打开应用内指定界面,并保持默认填充值不修改。
发送推送时携带 ext
发送离线推送时,通过
ext 字段携带跳转所需的业务信息(如目标页面、会话 ID 等)。ext 是一个字符串,结构由业务自定义,推荐使用 JSON 格式便于客户端解析。下文示例统一使用如下结构演示:# conversationType 为 1 表示单聊(conversationID 填消息发送方 userID),为 2 表示群聊(conversationID 填 groupID)。{"conversationID":"user_A","conversationType":1}
通过 REST API 发送推送时,在请求体的
Ext 字段中设置 JSON 字符串:{"MsgBody": [],"OfflinePushInfo": {"PushFlag": 0,"Title": "离线推送标题","Desc": "离线推送内容","Ext": "{\\"conversationID\\":\\"user_A\\",\\"conversationType\\":1}"}}
控制台接入测试页面同样支持设置
Ext 字段,填入 JSON 字符串即可。如果你接入了 TUIKit,TUIKit 内置的消息发送链路会自动用
OfflinePushExtInfo 组装 ext,无需手动设置。下方示例适用于自行调用 IM SDK 发送消息的场景。import android.util.Log;import com.tencent.imsdk.v2.V2TIMManager;import com.tencent.imsdk.v2.V2TIMMessage;import com.tencent.imsdk.v2.V2TIMOfflinePushInfo;import com.tencent.imsdk.v2.V2TIMSendCallback;V2TIMOfflinePushInfo pushInfo = new V2TIMOfflinePushInfo();pushInfo.setTitle("推送标题");pushInfo.setDesc("推送内容");// TODO: ext 由业务自定义,按需替换为您的目标页面、会话 ID 等参数。String ext = "{\\"conversationID\\":\\"user_A\\",\\"conversationType\\":1}";pushInfo.setExt(ext.getBytes());V2TIMMessage message = V2TIMManager.getMessageManager().createTextMessage("Hello TIMPush");V2TIMManager.getMessageManager().sendMessage(message,"<TARGET_USER_ID>", // 单聊填对端 userID,群聊该参数填 nullnull, // 群聊填 groupID,单聊该参数填 nullV2TIMMessage.V2TIM_PRIORITY_DEFAULT,false,pushInfo,new V2TIMSendCallback<V2TIMMessage>() {@Overridepublic void onProgress(int progress) {}@Overridepublic void onSuccess(V2TIMMessage msg) {Log.d("TIMPush", ">>>>> sendMessage success, msgID = " + msg.getMsgID());}@Overridepublic void onError(int code, String desc) {Log.e("TIMPush", ">>>>> sendMessage failed, code = " + code + ", desc = " + desc);}});
import android.util.Logimport com.tencent.imsdk.v2.V2TIMManagerimport com.tencent.imsdk.v2.V2TIMMessageimport com.tencent.imsdk.v2.V2TIMOfflinePushInfoimport com.tencent.imsdk.v2.V2TIMSendCallbackval pushInfo = V2TIMOfflinePushInfo()pushInfo.title = "推送标题"pushInfo.desc = "推送内容"// TODO: ext 由业务自定义,按需替换为您的目标页面、会话 ID 等参数。pushInfo.ext = "{\\"conversationID\\":\\"user_A\\",\\"conversationType\\":1}".toByteArray()val message = V2TIMManager.getMessageManager().createTextMessage("Hello TIMPush")V2TIMManager.getMessageManager().sendMessage(message,"<TARGET_USER_ID>", // 单聊填对端 userID,群聊该参数填 nullnull, // 群聊填 groupID,单聊该参数填 nullV2TIMMessage.V2TIM_PRIORITY_DEFAULT,false,pushInfo,object : V2TIMSendCallback<V2TIMMessage> {override fun onProgress(progress: Int) {}override fun onSuccess(msg: V2TIMMessage) {Log.d("TIMPush", ">>>>> sendMessage success, msgID = ${msg.msgID}")}override fun onError(code: Int, desc: String?) {Log.e("TIMPush", ">>>>> sendMessage failed, code = $code, desc = $desc")}})
客户端注册监听并解析 ext
请在
Application.onCreate() 中调用 addPushListener 注册 TIMPushListener,并在 onNotificationClicked 中解析 ext 后跳转到业务页面。以下示例只演示解析
ext 的核心逻辑,跳转部分用 TODO 占位,请按自身业务补充。如果你接入了 TUIKit 并使用 OfflinePushExtInfo 组装的 ext,可改为 new Gson().fromJson(ext, OfflinePushExtInfo.class) 解析后再跳转。import android.app.Application;import android.text.TextUtils;import android.util.Log;import com.tencent.timpush.TIMPushListener;import com.tencent.timpush.TIMPushManager;import org.json.JSONObject;public class App extends Application {private static final String TAG = "TIMPush";@Overridepublic void onCreate() {super.onCreate();TIMPushManager.getInstance().addPushListener(new TIMPushListener() {@Overridepublic void onNotificationClicked(String ext) {Log.d(TAG, ">>>>> TIMPush notification clicked, ext = " + ext);if (TextUtils.isEmpty(ext)) {return;}// 1. 解析 ext。JSON 结构由业务自定义,需与发送端约定一致。String conversationID;int conversationType;try {JSONObject json = new JSONObject(ext);conversationID = json.optString("conversationID");conversationType = json.optInt("conversationType");} catch (Exception e) {Log.e(TAG, ">>>>> parse ext failed: " + e.getMessage());return;}// 2. TODO: 根据业务字段跳转到目标页面。// 若使用了 Chat / TUIKit,建议在用户登录成功后再跳转;// 冷启动场景可先把参数缓存起来,登录回调中再跳转。}});}}
import android.app.Applicationimport android.text.TextUtilsimport android.util.Logimport com.tencent.timpush.TIMPushListenerimport com.tencent.timpush.TIMPushManagerimport org.json.JSONObjectclass App : Application() {override fun onCreate() {super.onCreate()TIMPushManager.getInstance().addPushListener(object : TIMPushListener() {override fun onNotificationClicked(ext: String?) {Log.d("TIMPush", ">>>>> TIMPush notification clicked, ext = $ext")if (TextUtils.isEmpty(ext)) return// 1. 解析 ext。JSON 结构由业务自定义,需与发送端约定一致。val conversationID: Stringval conversationType: Inttry {val json = JSONObject(ext)conversationID = json.optString("conversationID")conversationType = json.optInt("conversationType")} catch (e: Exception) {Log.e("TIMPush", ">>>>> parse ext failed: ${e.message}")return}// 2. TODO: 根据业务字段跳转到目标页面。// 若使用了 Chat / TUIKit,建议在用户登录成功后再跳转;// 冷启动场景可先把参数缓存起来,登录回调中再跳转。}})}}
验证:点击通知后
onNotificationClicked 被调用,且 ext 内容与发送时设置的一致。如果回调未触发,请检查:控制台点击动作是否选择了「打开应用内指定界面」并保持默认填充值、监听器是否在 Application.onCreate() 中注册、发送消息时是否设置了 ext。说明:
旧版本工程中可能仍使用
TUICore.registerEvent 回调或 LocalBroadcastManager 广播处理点击通知。这两种方式在当前版本仍可工作,但新接入用户建议优先使用 addPushListener。如需从旧方案迁移,删除旧的事件注册或广播注册代码,改为在 Application.onCreate() 中调用 addPushListener 即可,回调中获取到的 ext 内容与旧方案一致。配置消息分类(可选)
厂商离线通道通常有消息分类机制。消息分类会影响推送及时性、单设备每日接收数量、夜间展示、声音、横幅和厂商通道权限。
注意:
只有 IM 聊天、订单、交易、账号变更等用户预期强、需要及时触达的消息,才建议申请或使用高优先级分类。营销、广告、活动、资讯类消息不应滥用高优分类,否则可能被厂商降级、限频或冻结权限。
发送离线推送时,可以通过
V2TIMOfflinePushInfo 设置厂商消息分类。API 设置优先级通常高于腾讯云控制台证书默认配置。厂商 | API | 说明 |
小米 | 设置小米渠道 channelID。 | |
华为 | 设置华为推送消息分类。 | |
OPPO(新规则) | 设置 OPPO 新消息分类,例如 IM 消息使用 IM。 | |
OPPO(新规则) | 设置 OPPO 通知栏消息提醒等级。使用前需要先设置 category;如需 16,需先向 OPPO 申请强提醒能力。 | |
OPPO(新规则) | ||
OPPO(旧规则) | 仅适用于此前已开通 OPPO 私信通道权限的存量应用,用于设置已创建并登记的私信通道 ID。 | |
vivo | 设置 vivo 推送消息类别。 | |
荣耀 | 设置荣耀消息分类, NORMAL 表示服务通讯类消息,LOW 表示资讯营销类消息。 | |
魅族 | 设置魅族消息分类, 0 表示公信消息,1 表示私信消息。 | |
FCM | 设置 FCM 通道 Android 8.0 及以上通知渠道 ID。 |
通用示例:
V2TIMOfflinePushInfo pushInfo = new V2TIMOfflinePushInfo();pushInfo.setAndroidXiaoMiChannelID("your_xiaomi_channel_id");pushInfo.setAndroidHuaWeiCategory("IM");pushInfo.setAndroidVIVOCategory("IM");pushInfo.setAndroidHonorImportance("NORMAL");pushInfo.setAndroidMeizuNotifyType(1);pushInfo.setAndroidFCMChannelID("your_fcm_channel_id");
OPPO 新规则配置
OPPO 新接入应用建议使用新消息分类规则。IM 聊天、音视频通话等需要及时触达的消息,通常使用
category=IM,并按 OPPO 要求申请通讯与服务不限量权益或私信模板。V2TIMOfflinePushInfo pushInfo = new V2TIMOfflinePushInfo();// New OPPO message classification rule. Use IM for chat and call messages.pushInfo.setAndroidOPPOCategory("IM");// Optional. 1 = notification bar, 2 = notification bar + lock screen + sound + vibration.// Use 16 only after OPPO strong reminder capability is approved.pushInfo.setAndroidOPPONotifyLevel(2);
如需使用 OPPO 私信模板,可通过
V2TIMOfflinePushInfo.setVendorParams(String vendorParams) 携带厂商扩展参数。根据腾讯云离线推送消息属性设置文档,vendorParams 是 JSON 字符串,且 oppoTitleParam、oppoContentParam 等字段本身也需要序列化为 JSON 字符串。Map<String, Object> vendorParams = new HashMap<>();vendorParams.put("oppoTemplateId", "OPPO 私信模板 ID");Map<String, Object> titleParams = new HashMap<>();titleParams.put("title", "推送标题");vendorParams.put("oppoTitleParam", new Gson().toJson(titleParams));Map<String, Object> contentParams = new HashMap<>();contentParams.put("desc", "推送内容");vendorParams.put("oppoContentParam", new Gson().toJson(contentParams));pushInfo.setVendorParams(new Gson().toJson(vendorParams));
vendorParams 扩展参数能力要求 IMSDK 8.7 及以上版本。oppoTemplateId 必须是 OPPO 私信申请得到的模板 ID,不支持开发者自拟。oppoTitleParam 和 oppoContentParam 中的 key 需要与 OPPO 模板中的占位符一致。OPPO 旧规则配置
旧规则仅适用于此前已开通 OPPO 私信通道权限的存量应用。如果应用仍按旧规则使用私信通道,请确保客户端创建的通道 ID 已在 OPPO 推送运营后台登记,并与发送离线推送时设置的
channelID 保持一致。V2TIMOfflinePushInfo pushInfo = new V2TIMOfflinePushInfo();// Old OPPO private channel rule for existing apps only.pushInfo.setAndroidOPPOChannelID("your_oppo_private_channel_id");
新接入应用不要把
setAndroidOPPOChannelID 当作 OPPO 新消息分类的必配项。新规则优先使用 setAndroidOPPOCategory,并按需配置 setVendorParams 和 setAndroidOPPONotifyLevel。收不到推送排障流程
如果发送测试消息后设备没有收到推送,请按以下流程逐步排查。每一步确认通过后再进入下一步,可以快速定位问题所在环节。
步骤1:确认 registerPush 是否成功
检查
registerPush 回调结果:onSuccess 触发:注册成功,进入第二步。onError 触发:注册失败。查看错误日志中的三层错误码:TIMPush 错误码(如
800006):在 错误码 中查询。厂商原始异常(如
6003: certificate fingerprint error):优先根据此信息排查厂商侧配置。常见原因:
timpush-configs.json 缺失或位置错误、厂商证书未添加、包名或 SHA256 指纹不匹配、厂商配置文件未放置。步骤2:确认控制台能查到设备
能查到设备且状态正常:进入第三步。
查不到设备:
registerPush 可能实际未成功,或使用了错误的标识。重新确认 registrationID / userID 值是否正确。步骤3:确认 App 状态
离线推送只在 App 处于后台或已杀进程时触发:
App 在前台:将 App 切到后台或杀掉进程,重新发送测试消息。
App 已在后台:进入第四步。
步骤4:确认通知权限和通知渠道
Android 13 及以上:确认 App 已获得通知权限(系统设置 > 应用 > 通知 > 允许通知)。
Android 8.0 及以上:确认目标通知渠道的横幅、锁屏、声音开关已打开(系统设置 > 应用 > 通知 > 通知类别)。
通知总开关打开不代表所有 channel 都已开启;需要进入具体通知类别检查。
步骤5:确认厂商通道配置
测试真机厂商是否与目标厂商通道一致(如不要在华为设备上测试荣耀配置)。
厂商平台应用包名是否与 Android 工程
applicationId 完全一致。厂商证书是否已在腾讯云控制台添加且未过期。
华为/荣耀:测试包签名(debug 或 release)的 SHA-256 必须已在厂商控制台「项目设置 > 常规 > SHA 证书指纹」中添加,指纹不一致会报
6003: certificate fingerprint error。步骤6:确认消息分类和厂商限制
是否超出厂商单日推送限额(如 vivo 运营消息每日限制、小米公信消息限额)。
是否在夜间发送(部分厂商运营消息夜间不下发)。
是否未申请消息分类导致走默认策略被限频。
推送服务是否到期(试用到期后自动停止服务)。
步骤7:确认通知展示问题
如果收到了推送(有声音或状态栏图标)但没有横幅或锁屏展示:
检查系统通知类别中的「横幅通知」和「锁屏通知」开关。
确认通知渠道不是"静默通知"模式。
部分厂商定制系统可能额外限制通知展示方式。