3

I'd like to set up a network task in an Android app using AlarmManager.setInexactRepeating(...).

I have registered my BroadcastReceiver in AndroidManifest.xml with the following entry:

    <receiver
        android:name="com.vandenmars.colortrek.analytics.CTAUploadService"
        android:process=":sync"/>

Now, if I simply run my network request with a blocking HttpURLConnection from onReceive(), I get a NetworkOnMainThreadException!?!?

Weird... I thought the whole point of the AlarmManager was to call onReceive on a background-thread...

Which leads me to question 1:

Is it possible to force AlarmManager to call onReceive on a background thread rather than a Main thread? Do I need to declare something differently in AndroidManifest.xml?

I've tried to just work around this issue by setting up an AsyncTask for my network request inside onReceive and the exception goes away. The problem I'm having now is that some Android devices take forever to time out a network request that should fail because of an intermittent network issue or server problem. So, now (if I set the repeat-interval to something tiny) I have a bunch of AsyncTasks piling up in a queue that are trying to complete the same request and hang, waiting for the first request to fail. And when the network issue is resolved, all pending AsyncTasks break loose one after the other in a gigantic deluge of network requests.

To solve this, I tried simply keeping a flag in an instance variable around in my code that prevents another AsyncTask from being scheduled before the previous one completes. Unfortunately, this didn't work as expected, as every invocation from the AlarmManager created a fresh instance of my BroadcastReceiver, resetting the flag...

It does work if I make this flag static to attach it to the class itself rather than an instance of it, but this leads me to question 2:

Can I rely on this approach or is it ever possible that a BroadcastReceiver is called in such a way that there are two processes (?) or so of it active that have different "instances" of static variables?

And finally question 3:

If I can't rely on this approach, is there a good alternative?

I'd rather not have to hit the disk with this flag since I don't want to use up flash-write-cycles if I don't have to...

PS: I realize that there are other ways to do networking in the background, like GCM, JobScheduler, SyncAdapters, ... that may fix this issue for me or be otherwise considered "the right way to do it". Unfortunately they all have some form of issue (sketchy permissions, weird system changes, API level requirements, ...) that make them useless to me. So, I do need to use AlarmManager for this.

Community
  • 1
  • 1
Markus A.
  • 12,349
  • 8
  • 52
  • 116
  • "...is there a good alternative?" - Yes. Create an [`IntentService`](http://developer.android.com/reference/android/app/IntentService.html) to handle your network transactions, and start it in `onReceive()`, acquiring a `WakeLock` if necessary. – Mike M. Nov 24 '15 at 03:11
  • @MikeM. Is it possible to have the AlarmManager call the IntentService directly in such a way that it doesn't call it again before the first call completes, or do I need to do this myself from the BroadcastReceiver? – Markus A. Nov 24 '15 at 03:33
  • Sure. Set your alarm with a `PendingIntent.getService()` instead. `IntentService`s will only process one Intent at a time, so you won't have different instances "overlapping". – Mike M. Nov 24 '15 at 03:46
  • @MikeM. Do you know if overlapping Intents will then be queued for later execution or will they be dropped? (I would want them to be dropped if it's configurable...) – Markus A. Dec 01 '15 at 01:54
  • Yes, they will be queued. The [`IntentService` docs](http://developer.android.com/intl/es/reference/android/app/IntentService.html) explain it more thoroughly. However, if you decide that you do want to use an `IntentService`, you can determine if the Service is still processing a previous task before issuing a new one, and drop it if necessary. One possibility is to signal the calling object that a task is done at the end of `onHandleIntent()` through, e.g., `LocalBroadcastManager` or an event bus. – Mike M. Dec 01 '15 at 02:20
  • Another option is to check your app's running Service classes using `ActivityManager`, as described in [this post](http://stackoverflow.com/questions/7440473). – Mike M. Dec 01 '15 at 02:20

1 Answers1

0

One of your questions have been answered already: https://stackoverflow.com/a/9687668/1738090

However, you've asked several questions, so maybe I can help out here a little since I've had some recent experience with AlarmManager.

Is it possible to force AlarmManager to call onReceive on a background thread rather than a Main thread? Do I need to declare something differently in AndroidManifest.xml?

Yes, you can create a Service which implements a custom Handler. The Handler can be used to fire off an AlarmManager PendingIntent. The Broadcast Receiver that you use for this intent (for catching the AlarmService trigger) can then either receive and execute logic in the background within the handler or it can notify the UI of the trigger event. If you need more help on this part, let me know.

Can I rely on this approach or is it ever possible that a BroadcastReceiver is called in such a way that there are two processes (?) or so of it active that have different "instances" of static variables?

If you're worried about there being multiple instances of an alarm intent running behind the scenes, then I would highly advise making sure that you retain the instance of your PendingIntent which is used in the AlarmManager .set() method call. Although I haven't used setInexactRepeating() before, I know that if you're making multiple calls to the .set() method call with the same retained intent, then it will just reset the Alarm Service. The service knows to keep track of the intent instance and reset the alarm rather than creating multiple alarm processes. I would also recommend looking into the newer APIs which help with stricter delivery guarantees: setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent)

If I can't rely on this approach, is there a good alternative?

From what it sounds like, you are trying to send/receive data in the background of your application. And with your questions, it sounds like you are trying to manually handle scheduling when your app updates it's data behind the scenes.

I HIGHLY HIGHLY recommend using a SyncAdapter in this case. Aside from an easier development approach, there are lots of benefits to using SyncAdapters instead of your IntentService + BroadcastReceiver approach: http://developer.android.com/training/sync-adapters/index.html (including improved battery life, in general)

Hope this helps!

Community
  • 1
  • 1
w3bshark
  • 2,700
  • 1
  • 30
  • 40
  • I would LOVE to use a SyncAdapter. It would be by far the most elegant solution, but unfortunately it's a total mess... I will never be able to explain to my users why my app (which doesn't otherwise have any user accounts) needs permission to "set passwords" and creates an account under System Settings and even shows up in the System Settings' Add Account list... It's really sad that SyncAdapters require so many other weird modifications to the system... I would be really wary of an app that did all this to my phone... – Markus A. Nov 24 '15 at 03:38
  • I'm not so much worried that my alarm would be scheduled twice in parallel (so that if I schedule it with an interval of 5 minutes, it would execute twice or 3 times every 5 minutes). It's more the case when my alarm takes longer to execute than the interval, so it still gets called every 5 minutes, but it takes 20 minutes to run, so for every one that actually runs, 3 more get added to the execution queue. Does your handler-suggestion allow me to have the AlarmManager skip one or more executions if the previous one is still running? – Markus A. Nov 24 '15 at 03:43
  • I agree about your comments on the SyncAdapter. I don't understand why they require all of that account setup. To your second question, my handler suggestion could allow you to skip one or more. You could very easily keep track of an array of intents that were broadcasted to the Alarm Service and simply stop any further received calls after the first one has been received. – w3bshark Nov 24 '15 at 03:47
  • However, there are other alternatives to SyncAdapter that I think you should investigate before making the decision to stick with a Service + Handler: Take a look at [JobScheduler](http://developer.android.com/reference/android/app/job/JobScheduler.html) and it's pre-Lollipop alternative [GcmNetworkManager](https://developers.google.com/cloud-messaging/network-manager) – w3bshark Nov 24 '15 at 03:49
  • :) Those are the things I mentioned in my question already. Unfortunately JobScheduler requires AP level 21 and GcmNetworkManager requires Google Play Services v7.5+, which was only released this March. While GPS is pretty good about updating itself, it's still not something you can rely on as many people have updates turned off due to limits on their data plans or stability concerns... – Markus A. Nov 24 '15 at 03:55
  • Keeping track of intents is kind-of what I was trying to do with the `static` flag, but for that to work, I would need to have a guarantee that (using the terms very losely) static variables in Android are guaranteed to be singletons (my second question)... Or which way would you use to "easily keep track" of the calls? – Markus A. Nov 24 '15 at 04:01
  • Good point again haha. Play Services aren't always updated (or even installed). Honestly, I'm not sure how the static variables would be kept. It's getting to that point to where I'm tired and I can't think without having the code in front of me. Before I sign off though, have you been introduced to the [Volley library](http://developer.android.com/training/volley/requestqueue.html) ? Take a look at the article there along with its use of the singleton RequestQueue. I use Volley in several apps and highly recommend it as yet another alternative to your intent service + receiver implementation. – w3bshark Nov 24 '15 at 04:12
  • 1
    Just a reminder, you can't actually turn off updates to Google Play services (it auto-updates in the background) so any device that has the Google Play Store and an internet connection will shortly have the latest Google Play services. – ianhanniballake Nov 24 '15 at 04:15
  • Woah! Hey, Ian. Hope the Android Dev Summit talks went well! I'm following along to your video lectures on Udacity with the Nanodegree program. (sorry @MarkusA. for stealing your post to talk to @ianhanniballake) – w3bshark Nov 24 '15 at 04:20
  • @ianhanniballake That's definitely good news! But I'm sure there are other situations in which GPS for whatever reason isn't available or updated (no data-plan, not using Google Play Store, API level <9, ...) and I usually try to avoid any such requirements and dependencies as much as I can. – Markus A. Nov 24 '15 at 04:32
  • *shrugs* 1.4 billion devices with an up to date Google Play services is a pretty good upper bound :) – ianhanniballake Nov 24 '15 at 04:33
  • @ianhanniballake I just read you work at Google! Awesome! :) Thanks for your involvement there! Do you happen to know someone who is involved with the billing API? I think there is a logic flaw in API v3 and I can't for the life of me get a hold of anyone to ask about this: http://stackoverflow.com/questions/30921789/working-around-api-purchase-logic-flaws-for-consumables-in-google-plays-billing. I tried posting to the respective Google Group, but my post somehow just never shows up... – Markus A. Nov 24 '15 at 04:34
  • @ianhanniballake Do you know what percentage of devices this 1.4 billion corresponds to? – Markus A. Nov 24 '15 at 04:38
  • I don't know if anyone tracks the total number of Android devices worldwide. It would be 100% of the number tracked at developer.android.com/about/dashboards/ – ianhanniballake Nov 24 '15 at 04:39
  • I wanted to link this in case anyone gets stuck and needs to know about scope of static variables (and also read through all of our comments). I was going to point you here, @MarkusA, but it looks like you already found your answer :) http://stackoverflow.com/a/33885578/1738090 – w3bshark Nov 24 '15 at 05:25