As the title says, my AlarmManager often does not fire, or better said, seems to go on-pause, and thus the alarm will be fired with delays (I'm using SetExactAndAllowWhileIdle), sometimes really high delays which breaks the purpose of my app.
My app has a foreground process, I've already added the app to IgnoreBatteryOptimizations and also disabled the OEM battery optimization.
I've these problem on my Samsung S7 Edges, but on the emulator (tested also with doze activated) everything works fine, so I'm aware that this is an issue caused by the OEMs, but does someone know how to resolve this?
Below some relevant code:
Alarm BroadcastReceiver:
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; } } } } }
Alarm helper methods:
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); }
Example of not firing in time:
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
As you can see the first firing was at 21:19, then at 21:33 and then at 21:48.
But should have been fired every 10 minutes
I really don't understand why :/
P.S: I've also noticed just now, that the screen does not wake up, even if I'm using AlarmType.ElapsedRealtimeWakeup)
[EDIT]
Now that I've tested a little bit longer, looks like that the alarm after the first firing, is fired every 15 minutes, this is not a big issue for my app, but why?
From Android documentation:
To reduce abuse, there are restrictions on how frequently these alarms will go off for a particular application. Under normal system operation, it will not dispatch these alarms more than about every minute (at which point every such pending alarm is dispatched); when in low-power idle modes this duration may be significantly longer, such as 15 minutes.
So, that's normal, "exact" is not so exact, it's name is misleading.