You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

解决Oppo/Vivo/MIUI等定制OS下FCM数据消息APP被杀不送达问题

解决FCM纯Data推送在国内定制ROM被杀后无法接收的问题

这个问题我太熟了!国内的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 PayloadData 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

火山引擎 最新活动