Firebase高优先级消息无法唤醒Android 6+的Doze模式设备

58
我已经将我的项目从使用GCM迁移到了Firebase。设备最近被唤醒或者刚被休眠时,推送通知可以正常接收。但是如果我把设备放置一小时左右,没有任何推送发送,直到我唤醒设备。
Android文档指出,如果需要唤醒设备以传递消息,则应使用优先级设置为高的FireBase。它还表示设备管理应用程序不受Doze限制的影响,而我的应用程序正是设备管理应用程序。
我想提一下当我将项目从GCM迁移到FCM时,我只在Firebase控制台中指定了包名称,而没有指定指纹。 我尝试过以下方法:
  1. 将优先级设置为高
{
  "time_to_live": 300000,
  "delay_while_idle": false,
  "android": {
    "priority": "high"
  },
  "data": {
    "message": "PING_DEVICE",
    "time": "21/01/2018 16:20:28",
    "pushguid": "10062"
  },
  "registration_ids": [
    "eOMT........"
  ]
}

存活时间设置,以便消息最终会传递。delay_while_idle被设置为false,在2016年9月之后,FCM将忽略此设置。

设备管理员应用程序不受Doze影响,我的应用程序是设备管理员应用程序,但我还将该应用程序明确添加到“设置->电池->优化”中的Doze白名单中。这是通过设置应用程序手动完成的,而不是在代码中以编程方式完成的。

我让我的设备休眠了3个小时,没有推送消息。我还使用adb将设备置于Doze模式。当adb将设备置于Doze模式时,无法接收到推送消息;当adb将设备退出Doze模式时,推送消息可以收到。

进一步想法我尚未尝试。

我的推送是数据消息。这是因为我不希望推送消息出现在设备的通知栏上,并要求用户点击它来执行功能。用户与设备管理员应用程序没有任何交互。因此,数据消息由

onMessageReceived(RemoteMessage remoteMessage)

我相信通知消息可以唤醒设备,这正是我所需要的,但我希望应用程序处理推送,而不是用户。那么,我能否拥有既是通知又是数据的消息,但由onMessageRecieved来处理功能?

是否有人遇到过类似的情况或有任何解决方案?

[EDIT1]我找到了下面的链接,它说您可以发送既是通知又是数据的消息,但如果应用程序在后台运行,则会显示通知,但只有在用户点击通知后才执行数据。这不是我想要的,因为我希望数据立即在onMessageRecived中执行。

notification with data

[EDIT2]我已将以下代码和权限添加到应用程序中。应用现在会要求用户将应用程序加入Doze白名单,所以我选择了是。然后,我通过adb将设备放入Doze并发送了一条推送。直到我把设备从doze模式中拿出来之前,什么也没有发生。因此,不幸的是,这不起作用。

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Intent intent = new Intent();
            String packageName = getPackageName();
            PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
            if (!pm.isIgnoringBatteryOptimizations(packageName)) {
                intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                intent.setData(Uri.parse("package:" + packageName));
                startActivity(intent);
            }
        }

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

[EDIT3]

我进行了进一步的测试,试图排除我的网络应用程序代码对问题的影响。我通过adb将设备置于Doze状态,然后使用FireBase控制台发送推送。推送成功地传递了这告诉我,存在一个问题,即我的网络应用程序代码向fcm终端发送所有推送信息。我今晚会拿到代码并晚些时候发布。

[EDIT4] 我刚刚进行了更多的测试。我将设备置于Doze状态,然后使用Firebase控制台发送包含2个键值对的数据消息。当设备处于Doze状态且应用程序在前台(在屏幕上)时,推送消息会生效,执行onMessageReceived操作。这很好。但是,如果应用程序在后台,则仅显示通知。我从文档中了解到,数据消息通过Intent分派到启动器活动,但我的启动器应用程序不处理推送。处理推送的类称为MyAndroidFirebaseMsgService,它扩展了FirebaseMessagingService。

如果应用程序在后台,我是否必须将Intent路由到此类?这似乎有些奇怪。在GCM中从未出现过这种情况。

此外,我不希望应用程序因推送而启动,因为这非常具有侵入性,因为设备用户可能正在使用其他应用程序。我的应用程序也是一个设备管理器应用程序,因此99%的时间没有用户交互,它只是在设备上执行策略的客户端。

[edit5]

internal static void SendNotification (  Dictionary<string, string> nameValues ,  List<string> theregIDs , string sPushName)
         {     
            string stringregIds =  string.Join("\",\"", theregIDs) ;

             JavaScriptSerializer js = new JavaScriptSerializer();
            string keyValueJson = js.Serialize(nameValues);

            string TIME_TO_LIVE = "604800";

            string DELAY_WHILE_IDLE = "false";

            string ENDPOINTADDRESS = @"https://fcm.googleapis.com/fcm/send";


            postData = String.Concat("{\"time_to_live\":", TIME_TO_LIVE,  ",\"delay_while_idle\": ", DELAY_WHILE_IDLE,  ",  \"android\":{\"priority\":\"high\" } ,\"data\": { \"message\" : " + "\"" + sPushName + "\",\"time\": " + "\"" + System.DateTime.Now.ToString() + "\""
                , keyValueJson
               , "},\"registration_ids\":[\"" + stringregIds + "\"]}");


            WebRequest myWebRequest = null;
            WebResponse myWebResponse = null;
            try
            {
                myWebRequest = WebRequest.Create(ENDPOINTADDRESS);                         
                myWebRequest.Method = "post";
                myWebRequest.ContentType = "application/json";
                //  myWebRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
                myWebRequest.Headers.Add("Authorization: key=" + Our_Api_Key);
                myWebRequest.Headers.Add("Sender:id=" + Our_Sender_Id);

                Byte[] BA = Encoding.UTF8.GetBytes(postData);
                myWebRequest.ContentLength = BA.Length;

                using (Stream dataStreamOut = myWebRequest.GetRequestStream())
                {
                    dataStreamOut.Write(BA, 0, BA.Length);

                }

                using (myWebResponse = myWebRequest.GetResponse())
                {
                    using (Stream dataStream = myWebResponse.GetResponseStream())
                    {
                        using (StreamReader tReader = new StreamReader(dataStream))
                        {
                            strServerResponse = tReader.ReadToEnd(); 
                        }

                    }
                }


            }
            catch (WebException ex)
            {



            }

         }//

谢谢


@PeterHaddad 你好,我还没有在较低版本的安卓上测试过。我将获取的所有手机都是Android 6+ 版本的。我会请求我的商业伙伴给我提供他将数据发送到 FCM 端点的代码片段。请问有特别的位置或顺序可以设置优先级和数据吗? - turtleboy
关于您的EDIT3,Firebase控制台只允许发送推送通知(据我所记)。如果您的应用程序没有运行,这些通知将由Google Play服务处理,并代表您发布通知。它不会启动您的应用程序,也不会触发onMessageReceived回调。您收到了那个回调吗? - Eugen Pechanec
@EugenPechanec 我在S6、S7和Xcover3上得到了相同的结果。所有设备都是Android6+。我尝试发送的消息始终是数据消息。在原帖中,我提供了Web应用程序发送到FCM端点的代码片段。我相信这使用了FCM http API。您建议的测试方法是什么?Firebase控制台能否纯粹地发送数据消息? - turtleboy
@PeterHaddad 嗨,我刚刚做了一些测试。我将设备放入待机模式,然后使用 FireBase 控制台发送了一个带有两个键值对的数据消息。当设备处于待机模式且在前台(屏幕上)时,推送消息会传递并执行 onMessageReceived 方法。这非常好。然而,如果应用程序在后台运行,则只会显示通知。根据文档,我理解数据消息通过 Intent 分发到启动器活动,但我的启动器应用程序无法处理推送消息。 - turtleboy
1
你找到解决方案了吗? - sebasira
显示剩余14条评论
8个回答

27

在遇到类似问题后,我设法解决了它。

我通过postman发送以下json数据:

{
  "data": {
    "body": "Test body from curl"
  },
  "registration_ids": ["Token"],
  "webpush": {
    "headers": {
      "Urgency": "high"
    }
  },
  "android": {
    "priority": "high"
  },
  "priority": 10
}

对我来说,似乎最后一个"priority":10是解决问题的关键。

在Firebase文档中,我找不到任何相关参考,但在已弃用的GCM文档中有使用。https://developers.google.com/cloud-messaging/concept-options


1
我添加了WebPush、Android和优先级,现在它在三星、摩托罗拉和华为设备上的Android 8上运行良好,当设备进入睡眠状态并未连接充电器时...感谢分享这个配置!(我使用了一个较旧的PHP依赖项,sly/notification-pusher,它使用了zendframework类https://github.com/zendframework/ZendService_Google_Gcm/blob/master/src/Gcm/Message.php,在我的版本中尚未设置优先级字段) - WiRa
我遇到了以下错误:在“message”处未知名称“priority”:找不到字段。 - Arjun
@Arjun,我也遇到了同样的错误。你是如何解决的? - Gautam Jain
非常感谢!已经寻找了好几天。 - samuel zaffran

21
TL;DR - 根据FCM的遗留HTTP协议和HTTP v1协议的JSON负载结构正确设置通知优先级。根据您的情况或实现,可能已经有足够的帖子回答了,但我想根据FCM在其文档中提供的遗留HTTP和HTTP v1协议之间的区别提供更多背景信息,但是在设置通知优先级时,这两个协议API之间存在微妙的差异。我们的团队遇到了同样的问题,在启用Doze的Android 6+设备上没有收到推送通知,尽管我们的服务器似乎在与原始问题提供的负载类似的FCM API负载中正确设置了优先级。我们依赖于Amazon SNS将有效负载转发到FCM,并且从我们的服务器发送到Amazon SNS的有效负载将根据AndroidConfig JSON对象设置优先级。
{
    "android": {
        "priority": "high"
    }
}

然而,这只适用于HTTP v1协议。我们没有意识到亚马逊SNS可能仍在使用旧版HTTP协议,其中优先级必须在JSON负载的顶层设置:
{
    "priority": "high", // legacy HTTP protocol (this can also be set to 10)
    "android": {
        "priority": "high" // HTTP v1 protocol
    }
}

因此,只有在将传统的HTTP优先级参数设置为“高”或10时,通知优先级才会生效并启用在Doze模式下接收推送通知。
为了了解背景,以下是向FCM发送消息时每个协议的API端点:
- 传统HTTP:https://fcm.googleapis.com/fcm/send - 需要顶层"priority"参数(请参见此处) - HTTP v1:https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send - 需要带有"priority"参数的"android"对象(请参见此处

真的,这个问题我也曾经苦恼过很久,另外你需要在Android清单文件中启用REQUEST_IGNORE_BATTERY_OPTIMIZATIONS吗? - Pulkit
@Pulkit 我不需要添加那个标志,只需将遗留版和v1都设置为“高”。然后使用Azure通知中心进行测试,消息在应用程序“关闭”时被接收 - Pixel 4用于测试。 - Philip Patrick
谢谢。对于使用 fcm http legacy 发送高优先级消息的例子,将 "priority": "high" 设置在顶层即可在 Android 上完成。 { "to": "在应用程序中由 FCM 分配的设备令牌放在这里", "priority": "high","notification": { "title": "在这里放标题", "body": "在这里放正文" }, "data": { "customkey1": "自定义数据1", "customkey2": "自定义数据2" } } - Fabio Pagano

8

你无能为力。

这是一个已知问题,由一些OEM(如魅族或华硕)实施的电池优化引起。当应用在应用切换器中被滑动时,该应用程序被视为已强制停止,这不是默认的Android行为。不幸的是,这会导致您的应用程序的FCM服务停止运行。在待机模式下,类似的效果也可以被高优先级消息引起。

Firebase团队正在努力改进他们的行为,但实际的修复必须来自OEM方面。

检查您的应用程序是否受到任何OEM电池管理功能的影响的一种方法如下:

1)将OEM设备连接到adb

2)在设备上运行您的应用程序

3)从设备的最近屏幕上滑动该应用程序

4)运行命令:adb shell dumpsys package MY-PACKAGE | grep stopped

如果它显示stopped=true,则可以安全地假定OEM具有此类机制,并且您的应用程序受到相同的影响。


嗨,不,它没有停止。hero2lte:/ $ dumpsys package devreach.co.uk.devreach | grep stopped 用户0:ceDataInode = 397339 installed = true hidden = false suspended = false stopped = false notLaunched = false enabled = 0 - turtleboy
我还有一个应用程序是在另一台设备上构建的,但可以在同一设备上运行。这是一个独立的系统,仍然使用GCM来进行推送传递。如果我将设备放入Doze模式,推送消息就能正常传递。我已经阅读了其他帖子中的内容,有些人更新了Android Studio和Gradle构建,这会导致当设备处于Doze模式时无法接收到推送。在此之前,它们都很好用。因此,我仍然不确定问题出在哪里。 - turtleboy

4
在开发应用程序时,我也遇到了这个问题。然后我在Github上找到了一个与此有关的问题,解决了我的问题。就是说,
在运行Android 6.0+的设备上,Doze模式会在手机处于空闲状态且未充电时终止所有后台连接,包括与Pushy的后台连接。
只要设备被用户移动或唤醒,后台连接就会恢复,并且任何待处理的通知都将在几秒钟内传递,前提是它们还没有过期。
为了向处于Doze模式的设备发送通知,您的应用程序可以在其AndroidManifest.xml中声明REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限,并显示一个系统对话框,询问用户将您的应用程序加入电池优化白名单,而无需离开应用程序。
这将有效地保持与Pushy的后台连接处于活动状态,即使在Doze模式下,设备也能够接收通知。
您可以在这里查看此问题 https://github.com/ToothlessGear/node-gcm/issues/231 希望这对您有所帮助!

嗨,我已经将我的应用程序加入电池优化白名单中。在问题中,我提到了设置 -> 电池 -> 优化。这与你所建议的是否不同?此外,我的应用程序是设备管理器应用程序,文档说明这些类型的应用程序不受Doze影响。 - turtleboy
让我们在聊天中继续这个讨论。点击此处进入聊天室 - Aditya
如果您不想在收到推送通知时打开应用程序,那么现在有什么问题吗? - Aditya
问题在于当我的系统在Doze模式下发送推送时,直到设备退出Doze模式才会收到推送。我不想生成通知,我希望onReceiveMessage能够处理数据,而且没有用户干预。毕竟这是一个设备管理应用程序。当我直接从Firebase控制台发送数据推送时,只有当应用程序在前台时,设备才会在onMessagereceived中处理推送,而不是在后台时。因此,我需要知道为什么控制台可以在Doze模式下联系设备,而我的系统不能,并且为什么在后台时没有任何东西可以联系它。 - turtleboy
所以我需要在Doze模式下接收推送,并且使用onMessageReceived在后台处理数据。 - turtleboy
显示剩余14条评论

3
看起来在不通知的情况下,无法将数据字段设置为高优先级。以下是来自文档的引用:
“高优先级消息通常应导致用户与您的应用程序或其通知进行交互。如果 FCM 检测到它们不会产生这种交互的模式,则可能会降低您的消息优先级。”

在接收到高优先级的 FCM 通知时,是否可以显示高优先级的通知? - Prasanna Anbazhagan
通常情况下,谷歌假设我们都想要他们的默认用例。没有人可以拥有一个安全敏感的应用程序,在这种应用程序中,数据通知可能会擦除用户数据......或通过获取GPS信号来挽救生命。 - milosmns
嗨,伙计,我们能避免降低优先级吗? - famfamfam
是的,确认添加优先级为高的通知会唤醒我的应用程序! - chenop

1

time_to_live 设置为 0 对我来说解决了问题。

我认为这是因为一个非常小的 time_to_live 会告诉 FCM,这条消息只值得立即传递。因此,为了尽快传递它,它将忽略电池优化,如 Android P 的“应用待机桶”。 但要注意,设置较小的 time_to_live 可能意味着在某些情况下根本不会传递通知。我认为你不应该将其应用于所有类型的推送通知。

有关 time_to_live 的更多详细信息:https://firebase.google.com/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message


这是您的解决方案,不适用于所有人,我个人认为如此。您已经在许多设备上测试过了吗? - famfamfam

1

不要使用"android":{"priority":"high"},改为如下方式

    {
      "time_to_live": 300000,
      "delay_while_idle": false,
      "data": {
               "message": "PING_DEVICE",
               "time": "21/01/2018 16:20:28",
               "pushguid": "10062"
               },
      "priority": "high"  
}

-1

感谢大家的回复。我们最终解决了问题。

我们登录到 Firebase 控制台,发现由于代码的年龄,我们没有使用在控制台生成的设置/配置文件。抱歉,我忘记它的名称了。这个文件包含了在推送发送到 Google 时使用的设置等内容。一旦我们在请求中使用了该文件,我的应用程序就可以唤醒处于休眠状态的手机。

谢谢


2
嗨,我在 FCM 控制台中没有找到你说的东西,你能帮忙吗? - famfamfam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接