AlarmManager 在睡眠模式下不触发

AlarmManager not firing when in sleep mode

正如标题所说,我的 AlarmManager 经常不触发,或者更确切地说,似乎会触发 on-pause,因此警报会延迟触发(我正在使用 SetExactAndAllowWhileIdle),有时会非常高延迟破坏了我的应用程序的目的。

我的应用程序有一个前台进程,我已经将该应用程序添加到 IgnoreBatteryOptimizations 并禁用了 OEM 电池优化。

我的 Samsung S7 Edges 上有这些问题,但在模拟器上(也测试了打瞌睡)一切正常,所以我知道这是由 OEM 引起的问题,但有人知道吗如何解决?

下面是一些相关代码:

报警广播接收器:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using Android.Content;
using Android.Util;

namespace CGSJDSportsNotification.Droid {
    public partial class JDMonitoring {
        [BroadcastReceiver]
        public partial class BackgroundWorker : BroadcastReceiver {
            Helper H { get; } = new Helper(60 * SharedSettings.Entries.Get.Int32("searchRefresh"));

            public override void OnReceive(Context context, Intent intent) {
                FetchTickets();
            }

            async void FetchTickets() {
                Log.Debug("jd_foo", "STARTED");

                // Reinitialize the Browser class
                if (H.browser == null) {
                    Log.Debug("jd_foo", "BROWSER == NULL");

                    H.wc = new Browser.CustomWebViewClient();
                    H.wc.OnPageStart += BrowserOnPageStarted;
                    H.wc.OnPageLoaded += BrowserOnPageLoaded;
                    await H.BrowserInit();

                    Log.Debug("jd_foo", "BROWSER_INIT_SUCCESS");
                }

                if (await H.Net_Or_FgServiceAreNotActive())
                    goto CleanUpWebView;

                Log.Debug("jd_foo", "NET_OR_FGSERVICE_OK");

                UserNotification.Remove(12029); // Removes the 'No Internet' notification when the connection is back
                UserNotification.Remove(503); // Removes the 'Server took too long to respond' notification when is back
                UserNotification.Remove(0); // Notification warning group id

                // Resets the process cycle
                H.ProcessPhase = 0;
                H.InDepthProcess = false;

                H.browser.LoadUrl(H.UrlQueuePage);

                Log.Debug("jd_foo", "PREPARING_WHILE_PHASE_0");

                // Waits for the log-in page to fully load
                while (H.ProcessPhase == 0) {
                    if (await H.Net_Or_FgServiceAreNotActive())
                        goto CleanUpWebView;

                    await Task.Delay(100);
                }

                Log.Debug("jd_foo", "WHILE_PHASE_O_PASSED");

                bool? loginPage = await H.IsOnLoginPage();

                Log.Debug("jd_foo", $"ON_LOGIN_PAGE = {loginPage}");

                if (loginPage == true) {
                    bool? loginResponse = await H.Login();
                    Log.Debug("jd_foo", $"LOGIN_RESPONSE = {loginResponse}");

                    if (loginResponse == false) {
                        if (await H.Net_Or_FgServiceAreNotActive())
                            goto CleanUpWebView;

                        H.BackgroundWorkerReset();
                        goto CleanUpWebView;

                    } else if (loginResponse == null)
                        // If Login function returns null means that there's no internet connection or the server is down
                        goto CleanUpWebView;

                } else if (loginPage == false) {
                    if (await H.Net_Or_FgServiceAreNotActive())
                        goto CleanUpWebView;

                    // If at this point ProcessPhase is 1 this means that the browser session was cleaned and the we loggedin again
                    if (H.ProcessPhase == 1)
                        H.browser.LoadUrl(H.UrlQueuePage);
                } else
                    // If null is returned, this means that there's no internet connection or the server is down
                    goto CleanUpWebView;

                Log.Debug("jd_foo", "PREPARING_WHILE_PROCESS_PHASE_1");

                // Waits for the queue page to fully load
                while (H.ProcessPhase == 1) {
                    if (await H.Net_Or_FgServiceAreNotActive())
                        goto CleanUpWebView;

                    await Task.Delay(100);
                }

                Log.Debug("jd_foo", "WHILE_PHASE_1_PASSED");

                if (await H.JDServerIsOk() == false)
                    goto CleanUpWebView;

                Log.Debug("jd_foo", "JD_SERVER_OK");

                // Checks if the queue HTML table element is present
                if (await H.browser.ElementExists("document.getElementsByClassName('ticket-list collection-as-table')[0]")) {
                    Log.Debug("jd_foo", "QUEUE_TABLE_OK");

                    List<Ticket> tkts = await H.GetTickets();

                    Log.Debug("jd_foo", "GOT_TKTS_OK");

                    string country = SharedSettings.Entries.Get.String("searchCountrySelected").ToLower();

                    // Fetch all the tickets inside the time frame chosen by the user
                    // Starts from the end of the list in order to show the recent tkts on top
                    for (int i = tkts.Count - 1; i >= 0; i--) {
                        Ticket t = tkts[i];

                        // Checks only the tkts with the 'open' or 'new' status
                        //if (t.Status.ToLower() == "open" || t.Status.ToLower() == "new") {
                        if (t.Status.Length > 0) { //-- Used for testing
                            // The first and fastest step is to check the tkt title
                            if (t.Title.ToLower().Contains(country)) {
                                H.DisplayNotification(t.ID, t.LastUpdated, t.Title, t.Status, t.Owner, country, t.Link);
                            } else if (H.TktIsWithoutCountry(t.Title)) {
                                if (await H.JDServerIsOk() == false)
                                    goto CleanUpWebView;

                                // Country not found in the title, we must dig deeper by opening the tkt link
                                H.InDepthProcess = true;

                                H.browser.LoadUrl(t.Link);

                                // Waits for the tkt url to load
                                while (H.ProcessPhase == 2) {
                                    if (await H.Net_Or_FgServiceAreNotActive())
                                        goto CleanUpWebView;

                                    await Task.Delay(100);
                                }

                                // Gets the page's body
                                string pBody = await H.browser.EvalJS("document.body.textContent");

                                // If the selected country by the user is present somewhere into the tkt page body
                                if (pBody.IndexOf(country, StringComparison.OrdinalIgnoreCase) > -1) {
                                    H.DisplayNotification(t.ID, t.LastUpdated, t.Title, t.Status, t.Owner, country, t.Link);
                                } else if (H.TktIsWithoutCountry(pBody)) {
                                    // If the country string isn't present and the user has chosen to check also for tkts with unknown countries
                                    if (SharedSettings.Entries.Get.Bool("searchUnknownCountry"))
                                        H.DisplayNotification(t.ID, t.LastUpdated, t.Title, t.Status, t.Owner, "unknown", t.Link);
                                }

                                // Ready for the next ticket
                                H.ProcessPhase = 2;
                            }
                        }
                    }
                }

                Log.Debug("jd_foo", "STARTING_CLEAN_UP");
            CleanUpWebView:
                // Tries to free the memory asap
                H.FreeMemory();
                // Reschedules the alarm
                H.BackgroundWorkerReset();

                Log.Debug("jd_foo", "CLEAN_UP_OK");
            }

            public async void BrowserOnPageStarted(object sender, string url) {
                if (await H.Net_Or_FgServiceAreNotActive())
                    return;
            }

            async void BrowserOnPageLoaded(object sender, string url) {
                if (await H.Net_Or_FgServiceAreNotActive())
                    return;
                
                // When the login page has loaded
                if (url.StartsWith(H.UrlLoginPage)) {
                    // Login again in order to access the tkt page
                    if (H.InDepthProcess)
                        await H.Login();
                    else
                        // First login
                        H.ProcessPhase = 1;

                    // Saves the login session
                    Android.Webkit.CookieManager.Instance.Flush();
                }

                // When the queue page has loaded
                if (url.Equals(H.UrlQueuePage))
                    H.ProcessPhase = 2;

                // When the tkt page has loaded
                if (url.StartsWith("https://support.jdplc.com/rt4/Ticket/Display.html?id=") && H.InDepthProcess) {
                    while (await H.browser.EvalJS("document.getElementById('TitleBox--_Helpers_TicketHistory------SGlzdG9yeQ__---0')") == null) {
                        if (await H.Net_Or_FgServiceAreNotActive() || await H.JDServerIsOk() == false)
                            return;

                        await Task.Delay(1000);
                    }

                    H.ProcessPhase = 3;
                }
            }
        }
    }
}

报警辅助方法:

public static void BackgroundWorkerFire(int alarmMilliseconds) {
((AlarmManager)(new ContextWrapper(AndroidContext)).GetSystemService(Context.AlarmService))
    .SetExactAndAllowWhileIdle(
        AlarmType.ElapsedRealtimeWakeup,
        SystemClock.ElapsedRealtime() + alarmMilliseconds,
        PendingIntent.GetBroadcast(
        AndroidContext,
        98,
        new Intent(
            AndroidContext,
            typeof(JDMonitoring.BackgroundWorker)),
    PendingIntentFlags.UpdateCurrent));
}

public void BackgroundWorkerReset(int seconds = -1) {
    BackgroundWorkerStop();
    BackgroundWorkerFire(seconds == -1 ? RefreshRate : 1000 * seconds);
}

未及时触发示例:

06-27 21:19:54.962 25634 25634 D jd_foo  : STARTED
06-27 21:19:54.962 25634 25634 D jd_foo  : BROWSER == NULL
06-27 21:19:55.766 25634 25634 D jd_foo  : BROWSER_INIT_SUCCESS
06-27 21:19:55.842 25634 25634 D jd_foo  : NET_OR_FGSERVICE_OK
06-27 21:19:55.851 25634 25634 D jd_foo  : PREPARING_WHILE_PHASE_0
06-27 21:19:59.699 25634 25634 D jd_foo  : WHILE_PHASE_O_PASSED
06-27 21:19:59.791 25634 25634 D jd_foo  : ON_LOGIN_PAGE = True
06-27 21:20:05.093 25634 25634 D jd_foo  : LOGIN_RESPONSE = True
06-27 21:20:05.093 25634 25634 D jd_foo  : PREPARING_WHILE_PROCESS_PHASE_1
06-27 21:20:05.795 25634 25634 D jd_foo  : WHILE_PHASE_1_PASSED
06-27 21:20:05.803 25634 25634 D jd_foo  : JD_SERVER_OK
06-27 21:20:05.814 25634 25634 D jd_foo  : QUEUE_TABLE_OK
06-27 21:20:06.411 25634 25634 D jd_foo  : GOT_TKTS_OK
06-27 21:20:08.666 25634 25634 D jd_foo  : STARTING_CLEAN_UP
06-27 21:20:08.714 25634 25634 D jd_foo  : CLEAN_UP_OK
06-27 21:33:37.282 25634 25634 D jd_foo  : STARTED
06-27 21:33:37.284 25634 25634 D jd_foo  : BROWSER == NULL
06-27 21:33:37.415 25634 25634 D jd_foo  : BROWSER_INIT_SUCCESS
06-27 21:33:37.429 25634 25634 D jd_foo  : NET_OR_FGSERVICE_OK
06-27 21:33:37.437 25634 25634 D jd_foo  : PREPARING_WHILE_PHASE_0
06-27 21:33:39.572 25634 25634 D jd_foo  : WHILE_PHASE_O_PASSED
06-27 21:33:39.599 25634 25634 D jd_foo  : ON_LOGIN_PAGE = True
06-27 21:33:44.871 25634 25634 D jd_foo  : LOGIN_RESPONSE = True
06-27 21:33:44.871 25634 25634 D jd_foo  : PREPARING_WHILE_PROCESS_PHASE_1
06-27 21:33:44.872 25634 25634 D jd_foo  : WHILE_PHASE_1_PASSED
06-27 21:33:44.890 25634 25634 D jd_foo  : JD_SERVER_OK
06-27 21:33:44.909 25634 25634 D jd_foo  : QUEUE_TABLE_OK
06-27 21:33:45.732 25634 25634 D jd_foo  : GOT_TKTS_OK
06-27 21:33:47.383 25634 25634 D jd_foo  : STARTING_CLEAN_UP
06-27 21:33:47.452 25634 25634 D jd_foo  : CLEAN_UP_OK
06-27 21:48:37.318 25634 25634 D jd_foo  : STARTED
06-27 21:48:37.318 25634 25634 D jd_foo  : BROWSER == NULL
06-27 21:48:37.442 25634 25634 D jd_foo  : BROWSER_INIT_SUCCESS
06-27 21:48:37.463 25634 25634 D jd_foo  : NET_OR_FGSERVICE_OK
06-27 21:48:37.470 25634 25634 D jd_foo  : PREPARING_WHILE_PHASE_0
06-27 21:48:39.523 25634 25634 D jd_foo  : WHILE_PHASE_O_PASSED
06-27 21:48:39.578 25634 25634 D jd_foo  : ON_LOGIN_PAGE = True
06-27 21:48:44.943 25634 25634 D jd_foo  : LOGIN_RESPONSE = True
06-27 21:48:44.944 25634 25634 D jd_foo  : PREPARING_WHILE_PROCESS_PHASE_1
06-27 21:48:44.944 25634 25634 D jd_foo  : WHILE_PHASE_1_PASSED
06-27 21:48:44.962 25634 25634 D jd_foo  : JD_SERVER_OK
06-27 21:48:44.980 25634 25634 D jd_foo  : QUEUE_TABLE_OK
06-27 21:48:46.221 25634 25634 D jd_foo  : GOT_TKTS_OK
06-27 21:48:47.869 25634 25634 D jd_foo  : STARTING_CLEAN_UP
06-27 21:48:47.931 25634 25634 D jd_foo  : CLEAN_UP_OK
06-27 22:03:37.396 25634 25634 D jd_foo  : STARTED
06-27 22:03:37.398 25634 25634 D jd_foo  : BROWSER == NULL
06-27 22:03:37.565 25634 25634 D jd_foo  : BROWSER_INIT_SUCCESS
06-27 22:03:37.581 25634 25634 D jd_foo  : NET_OR_FGSERVICE_OK
06-27 22:03:37.586 25634 25634 D jd_foo  : PREPARING_WHILE_PHASE_0
06-27 22:03:39.581 25634 25634 D jd_foo  : WHILE_PHASE_O_PASSED
06-27 22:03:39.620 25634 25634 D jd_foo  : ON_LOGIN_PAGE = True
06-27 22:03:44.860 25634 25634 D jd_foo  : LOGIN_RESPONSE = True
06-27 22:03:44.861 25634 25634 D jd_foo  : PREPARING_WHILE_PROCESS_PHASE_1
06-27 22:03:44.861 25634 25634 D jd_foo  : WHILE_PHASE_1_PASSED
06-27 22:03:44.879 25634 25634 D jd_foo  : JD_SERVER_OK
06-27 22:03:44.893 25634 25634 D jd_foo  : QUEUE_TABLE_OK
06-27 22:03:45.754 25634 25634 D jd_foo  : GOT_TKTS_OK
06-27 22:03:47.549 25634 25634 D jd_foo  : STARTING_CLEAN_UP
06-27 22:03:47.625 25634 25634 D jd_foo  : CLEAN_UP_OK
06-27 22:18:37.288 25634 25634 D jd_foo  : STARTED
06-27 22:18:37.289 25634 25634 D jd_foo  : BROWSER == NULL
06-27 22:18:37.422 25634 25634 D jd_foo  : BROWSER_INIT_SUCCESS
06-27 22:18:37.438 25634 25634 D jd_foo  : NET_OR_FGSERVICE_OK
06-27 22:18:37.443 25634 25634 D jd_foo  : PREPARING_WHILE_PHASE_0
06-27 22:18:39.343 25634 25634 D jd_foo  : WHILE_PHASE_O_PASSED
06-27 22:18:39.400 25634 25634 D jd_foo  : ON_LOGIN_PAGE = True
06-27 22:18:44.643 25634 25634 D jd_foo  : LOGIN_RESPONSE = True
06-27 22:18:44.644 25634 25634 D jd_foo  : PREPARING_WHILE_PROCESS_PHASE_1
06-27 22:18:44.644 25634 25634 D jd_foo  : WHILE_PHASE_1_PASSED
06-27 22:18:44.651 25634 25634 D jd_foo  : JD_SERVER_OK
06-27 22:18:44.658 25634 25634 D jd_foo  : QUEUE_TABLE_OK
06-27 22:18:45.483 25634 25634 D jd_foo  : GOT_TKTS_OK
06-27 22:18:47.087 25634 25634 D jd_foo  : STARTING_CLEAN_UP
06-27 22:18:47.167 25634 25634 D jd_foo  : CLEAN_UP_OK

如您所见,第一次发射是在 21:19,然后是 21:33,然后是 21:48。
但是应该每 10 分钟被解雇一次

我真的不明白为什么:/

P.S: 我刚才也注意到,即使我使用 AlarmType.ElapsedRealtimeWakeup)

屏幕也不会唤醒

[编辑]
现在我已经测试了更长的时间,看起来第一次触发后的警报每 15 分钟触发一次,这对我的应用程序来说不是大问题,但为什么呢?

来自Android documentation

为了减少滥用,对特定应用程序发出这些警报的频率有限制。在正常的系统操作下,它发送这些警报的时间不会超过大约每分钟一次(此时将发送每个此类未决警报);在低功耗空闲模式下,此持续时间可能会明显更长,例如 15 分钟

所以,这很正常,“精确”并不那么精确,它的名称具有误导性。