Android的消息推送长期以来都是工程实践里的麻烦事。海外可以依赖FCM,但国内没有统一推送服务,第三方推送SDK通常需要自己维持长连接。系统一旦进入Doze模式、清理后台或者开启省电策略,进程和连接很容易被冻结或杀掉。厂商通道正好补上这个短板,它由手机系统服务维护链路,应用进程不在时也能收到消息并展示通知。因此,稳定到达的核心就从单纯保活进程,变成了厂商通道接入和系统权限配置的组合。

为什么厂商通道能解决到达率问题
Android从6.0开始引入Doze和App Standby机制,系统会根据应用使用频率限制后台运行。到Android 12以后,后台启动Activity和Service的限制进一步收紧。第三方推送SDK依赖的TCP长连接一旦断开,客户端进程又处于缓存状态,服务器就只能等待应用下次被用户主动打开才能恢复连接。此时如果用户清理了任务列表,或者系统因内存压力回收进程,推送就会长时间失效。
厂商通道的链路不同。以华为、小米为例,系统内部运行着厂商自己的推送服务,这些服务由系统框架管理,拥有比普通应用更高的优先级。消息从厂商服务器下发到手机系统服务后,系统服务会根据包名匹配目标应用,然后通过广播或直接拉起进程的方式触发通知展示。即使应用进程完全不存在,通知栏仍然可以显示消息。这就是厂商通道到达率明显高于普通第三方通道的根本原因。
但厂商通道不是万能的。不同厂商对通知样式、点击行为、透传消息都有各自的限制。部分通道只允许展示通知栏消息,不允许应用在后台处理数据;还有厂商要求应用签名、包名、消息类别完全匹配。因此在实际项目中,厂商通道通常作为第一优先级,第三方长连接作为回退方案,两者配合才能覆盖更多设备。
主流厂商通道接入流程
国内主流厂商通道包括华为、小米、OPPO、vivo和荣耀。接入思路基本一致:先在厂商开发者平台创建应用,填写包名和应用签名证书的SHA256指纹,获取AppID、AppKey或AppSecret等参数;然后在工程中引入对应SDK,声明权限和Service组件;最后在应用启动时初始化推送并注册Token。下面以华为HMS Push为例展示关键代码。
华为推送需要在项目根目录的build.gradle中配置华为Maven仓库地址,并添加agcp插件。如果使用较新的Android Gradle Plugin,建议检查agcp插件的兼容版本,避免构建阶段出现插件冲突。
buildscript {
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
dependencies {
classpath 'com.huawei.agconnect:agcp:1.6.0.300'
}
}
应用模块中需要应用插件并引入HMS Push依赖。同时要把从华为AGC后台下载的agconnect-services.json文件放到app目录下,否则初始化时会读取不到配置。
apply plugin: 'com.huawei.agconnect'
android {
...
}
dependencies {
implementation 'com.huawei.hms:push:6.11.0.300'
}
AndroidManifest中需要声明网络权限和唤醒锁权限,并注册华为的推送消息服务。注意组件名必须与自定义Service类保持一致,否则系统无法正确回调。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application>
<service
android:name=".HmsMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT"/>
</intent-filter>
</service>
</application>
初始化阶段开启自动初始化,并获取推送Token。拿到Token后需要上传到业务服务器,服务端后续才能根据设备Token调用厂商接口下发消息。
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
HmsMessaging.getInstance(this).setAutoInitEnabled(true);
HmsMessaging.getInstance(this).getToken()
.addOnSuccessListener(token -> {
Log.i("PushToken", token);
uploadTokenToServer(token);
})
.addOnFailureListener(e -> {
Log.e("PushToken", "get token failed", e);
});
}
}
除了华为,小米、OPPO、vivo和荣耀的接入方式类似,但工程配置和后台要求略有差异。下面用表格归纳关键信息。
| 厂商 | SDK/服务 | 关键配置 | 注意事项 |
|---|---|---|---|
| 华为 | HMS Push | agconnect-services.json、AppId | Debug包和Release包证书指纹通常要分开配置 |
| 小米 | MiPush | AppId、AppKey | 需要声明XMPushReceiver,部分消息类型需在后台申请 |
| OPPO | OPush | AppKey、AppSecret、ChannelID | 通知栏消息必须指定已创建的ChannelID |
| vivo | vPush | AppId、AppKey | 正式推送能力需要应用上架后申请,审核较严格 |
| 荣耀 | HonorPush | AppId、AppKey | 与华为体系独立,荣耀设备无法使用HMS Push |
保活配置与系统权限
接入厂商通道后,通知到达率会大幅提升,但仍然有一些系统级开关会限制应用接收通知或响应点击。最常见的三个配置是电池优化白名单、自启动权限和后台弹出界面权限。这些配置不能通过普通代码强制开启,但可以检测状态并引导用户跳转到对应设置页。
电池优化白名单主要用于保护第三方通道和后台任务。如果应用被加入电池优化白名单,系统在待机和省电模式下不会轻易冻结应用的任务。使用PowerManager可以检测是否在白名单中,并引导用户关闭限制。代码示例如下。
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
自启动权限在国产ROM上尤其重要。华为、小米、OPPO、vivo等系统默认会拦截应用开机自启动和后台拉起,如果不允许自启动,即使用户点击通知也可能无法启动应用。不同厂商的设置页面不一样,可以通过包名和组件名直接跳转。下面是一段兼容多个厂商的跳转逻辑。
private void openAutoStartSetting() {
String manufacturer = Build.MANUFACTURER.toLowerCase();
Intent intent = new Intent();
if (manufacturer.contains("huawei")) {
intent.setClassName("com.huawei.systemmanager",
"com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
} else if (manufacturer.contains("xiaomi")) {
intent.setComponent(new ComponentName("com.miui.securitycenter",
"com.miui.permcenter.autostart.AutoStartManagementActivity"));
} else if (manufacturer.contains("oppo")) {
intent.setClassName("com.coloros.safecenter",
"com.coloros.safecenter.permission.startup.StartupAppListActivity");
} else if (manufacturer.contains("vivo")) {
intent.setClassName("com.vivo.permissionmanager",
"com.vivo.permissionmanager.activity.BgStartUpManagerActivity");
} else if (manufacturer.contains("honor")) {
intent.setClassName("com.hihonor.systemmanager",
"com.hihonor.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
}
try {
startActivity(intent);
} catch (Exception e) {
startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + getPackageName())));
}
}
后台弹出界面权限在OPPO和vivo上比较容易被忽略。如果该权限未开启,点击通知后Activity不会弹出,页面像没有响应一样。这个权限没有公开的API可以检测,只能跳转到应用详情页,让用户手动打开。
除了系统设置,代码层还可以使用WorkManager完成周期性Token同步。厂商Token可能因为卸载重装、清除数据或系统升级而变化,如果服务器一直保存旧token,推送就会丢失。通过WorkManager每12小时检查一次token并上报,可以降低这种风险。实现如下。
PeriodicWorkRequest tokenSyncRequest =
new PeriodicWorkRequest.Builder(TokenSyncWorker.class, 12, TimeUnit.HOURS)
.setConstraints(new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"push_token_sync",
ExistingPeriodicWorkPolicy.KEEP,
tokenSyncRequest
);
还有一类做法是使用前台服务保活。但Android 14以后前台服务类型管理更严格,推送用途需要声明specialUse,应用市场审核时也需要提供详细说明。除非确实需要长时间实时连接,否则不建议把前台服务作为主要保活手段。
聚合推送方案与多级回退
如果应用需要覆盖华为、小米、OPPO、vivo、荣耀等多个渠道,分别集成原生SDK会让工程配置变得复杂,还要维护多个厂商后台的应用信息。聚合推送平台如个推、极光、友盟等封装了各厂商通道,开发者只需接入一个SDK,平台服务端会自动选择目标设备对应的厂商通道下发消息。
聚合推送的典型初始化流程如下,完成注册后拿到平台客户端ID,再由服务端通过该ID进行厂商通道推送。
PushAgent pushAgent = PushAgent.getInstance(context);
pushAgent.register(new IRegisterCallback() {
@Override
public void onRegisterResult(int code, String message) {
if (code == 0) {
String clientId = pushAgent.getClientId();
uploadClientId(clientId);
}
}
});
但聚合方案也有代价。数据需要经过第三方平台,免费版可能有限制,部分实时性要求高的场景可能不如直连厂商服务。因此架构上最好设计多级回退。服务端给每条设备记录多个通道信息,下发时先选择厂商token;如果厂商通道返回无效或未注册,再回退到平台长连接;如果用户关闭通知权限,则降级为应用内弹窗或站内信。这样可以兼顾到达率和可控性。
回退逻辑还需要处理海外设备。没有厂商服务的设备自然无法注册厂商token,此时必须保留第三方通道,否则用户完全收不到消息。token注册时可以把手机品牌、Android版本、厂商通道状态一并上报,服务端根据这些信息决定走哪条链路。
测试方法与常见问题
推送功能上线前必须在真机上验证。重点场景包括:锁屏后等待十分钟、清理最近任务、重启手机、开启省电模式。如果这些场景下通知都能正常展示,说明厂商通道配置基本生效。各厂商开发者平台一般提供调试工具,例如华为的Push Debugger和小米的推送测试页面,可以模拟不同消息类型。
排查问题时,优先确认三个点:一是Token是否成功获取,日志里的token长度是否符合厂商规范;二是通知权限是否被用户关闭;三是自启动和后台弹出界面是否开启。很多时候推送不到不是代码问题,而是系统设置被拦截。可以把这些状态做成内置的调试页面,方便测试和客服排查。
另外还要注意厂商通道对消息分类的要求。例如OPPO和vivo对公信消息和私信消息有不同渠道限制,华为对通知栏点击动作有额外配置。接入时不要只看官方快速开始文档,还要仔细阅读消息分类和模板限制,避免上线后才发现某些消息类型被厂商静默丢弃。