解决Oppo/Vivo/MIUI等定制OS下FCM数据消息APP被杀不送达问题
这个问题我太熟了!国内的Oppo、Vivo、MIUI这些定制ROM为了省电和系统流畅,对第三方APP的后台进程限制特别严格——纯Data类型的FCM推送必须依赖APP进程存活才能触发onMessageReceived,一旦APP被杀进程彻底退出,系统根本不会唤醒你的APP来处理推送。下面给你几个实用的解决办法:
1. 引导用户开启「自启动权限」
这是最基础也是最关键的一步!国内定制ROM几乎都有自启动管理开关,只有开启了自启动,APP被杀后才能被系统唤醒处理推送。你需要在APP里做引导,告诉用户怎么开启,甚至可以直接跳转到对应品牌的设置页面:
比如跳转自启动设置的代码片段:
private void goToAutoStartSetting() { Intent intent = new Intent(); String manufacturer = android.os.Build.MANUFACTURER; switch (manufacturer) { case "xiaomi": intent.setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")); break; case "oppo": intent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")); break; case "vivo": intent.setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")); break; case "huawei": intent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity")); break; default: // 其他品牌跳转到应用详情页 intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); break; } try { startActivity(intent); } catch (ActivityNotFoundException e) { // 找不到对应页面时跳转到系统设置 intent.setAction(Settings.ACTION_SETTINGS); startActivity(intent); } }
2. 开启「后台弹出/悬浮窗权限」
部分ROM(比如MIUI、Oppo)会限制后台APP弹出通知,即使你的APP收到了Data推送,也没法在系统通知栏显示。所以要引导用户开启这个权限,不同品牌的叫法可能不一样:
- MIUI:后台弹出权限
- Oppo/Vivo:悬浮窗权限
- 华为:悬浮窗权限
可以在APP首次启动或者推送功能说明页里,明确告诉用户开启这个权限的必要性,必要时直接跳转设置页面。
3. 混合使用Notification + Data Payload
纯Data推送的局限性就是必须APP存活,你可以改成同时发送Notification Payload和Data Payload:
- 当APP在前台/后台时,依然会触发
onMessageReceived,你可以自定义处理通知逻辑; - 当APP被杀时,系统会直接显示Notification Payload里的通知,用户点击通知后会唤醒APP,这时你可以在启动的Activity里读取Data Payload的内容。
示例推送JSON格式:
{ "to": "DEVICE_TOKEN", "notification": { "title": "推送标题", "body": "推送内容" }, "data": { "key1": "value1", "key2": "value2" } }
在onMessageReceived里的处理逻辑可以调整为:
@Override public void onMessageReceived(RemoteMessage remoteMessage) { // 先读取Data Payload的内容 Map<String, String> data = remoteMessage.getData(); String customKey = data.get("key1"); // 如果APP在前台,自定义通知样式;后台/被杀时系统会自动处理Notification Payload if (isAppForeground()) { showCustomNotification(data); } } // 判断APP是否在前台的工具方法 private boolean isAppForeground() { ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses(); if (appProcesses == null) return false; for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { if (appProcess.processName.equals(getPackageName()) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { return true; } } return false; }
4. 集成厂商自有推送服务
这是最彻底的解决方案!国内各大厂商都有自己的系统级推送服务(比如小米MiPush、华为Push、Oppo Push、Vivo Push),这些服务是系统级的,即使APP被杀也能收到推送,而且送达率更高。
你可以在APP里做品牌判断,根据用户的手机品牌自动切换推送通道:
- 小米手机优先用MiPush发送
- Oppo手机优先用Oppo Push发送
- 其他品牌手机继续用FCM发送
虽然集成多个SDK会增加开发工作量,但能从根本上解决定制ROM的推送兼容性问题。
5. 谨慎使用前台服务保活
如果以上方法都满足不了需求,可以尝试用前台服务把APP进程变成前台进程,这样系统不会轻易杀掉它。不过注意Android 8.0+要求前台服务必须显示通知,不能隐藏,所以要给用户说明这个通知的用途(比如“为了及时接收推送,请保持此通知”)。
示例前台服务代码:
public class KeepAliveService extends Service { private static final int NOTIFICATION_ID = 1001; private static final String CHANNEL_ID = "keep_alive_channel"; @Override public void onCreate() { super.onCreate(); // 创建通知渠道(Android 8.0+必须) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "后台保活通知", NotificationManager.IMPORTANCE_LOW); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 创建前台通知 Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("APP正在后台运行") .setContentText("为了及时接收推送,请保持此通知") .setSmallIcon(R.drawable.ic_notification) .setPriority(NotificationCompat.PRIORITY_LOW) .build(); // 启动前台服务 startForeground(NOTIFICATION_ID, notification); return START_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
记得在AndroidManifest里注册服务,并申请前台服务权限:
<service android:name=".KeepAliveService" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
内容的提问来源于stack exchange,提问作者Rohan Shinde




