So, I'll start by saying I have already done a lot of research on this, but I'm still stuck.
So here's the goal: Preform a database action (insert a reminder message into local sqlitedb) and send a notification at pre-set times given by some user settings.
And here's the problem: It doesn't work when the app is closed.
I have tried using AlarmManager with a broadcast pending intent and with a service pending intent. Neither seemed to work in the problem scenario. After looking into it some more, I found that on newer versions of Android, they are paring back some of the functionality of AlarmManager in favor of JobScheduler, a new API that 1) seems to be overkill for what I'm doing and 2) won't work on older devices. Sending alarms while the app is closed seems to be one of those "pared-back" features. What should I do? What do I use? What does everyone else usually do? Have I misunderstood something? Please help; I can't find anything on Google!
There are a few methods of running "jobs" pre-lollipop.
You can use the JobSchedulerCompat library, but this library has a huge difference compared to the default Lollipop API:
On Lollipop jobs are batched and scheduled for all apps at once. The library however does not have access to other apps data and therefore can't combine jobs from two different apps using the library.
Another option is to use JobScheduler for lollipop and greater devices and then use AlarmManager for pre-lollipop devices by checking
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){}
Finally, you can use the Sync-Adapter API. The Sync adapter encapsulates the code for the tasks that transfer data between the device and a server. Based on the scheduling and triggers you provide in your app, the sync adapter framework runs the code in the sync adapter component. To add a sync adapter component to your app, you need to add the following pieces:
Sync adapter class.
Bound Service.
Sync adapter XML metadata file.
Declarations in the app manifest.
Related
I'm trying to get a feature in my app that opens an ftp connection every night, compares the content of the ftp server to the contents of the sharedPreferences JSON and if there is a difference, upload the missing data to the ftp server.
So far I think I need to use an AlarmManager for this, so that the code runs even when the app is closed at night.
This is a completely new thing to me and I'm looking up some documentation and StackOverflow questions about it, but I can't really figure out if this is what I need to use for what I'm trying to do.
I found the following question which contains a nice example of AlarmManager. But can it also retrieve data from sharedPreferences? I was also wondering if I can run an async task (using coroutines).
EDIT: I read something about a workmanager. Would this be a better solution for what I'm trying to achieve?
Workmanager is introduced from Android 8. It is better then AlarmManager because it can manage the doze mode and more when you phone is in sleep mode. You can configure the workmanager to do the work each x mins with conditions (if the device connected, if the phone has % of battery ..)
The great issus with AlarmManager is that you don't garatute that it will be launched at the specific time because the system will manage all alarm with priority...
For more details : link
Yes, you can fetch data from shared preferences from your BroadcastReceiver. And you can also run asynchronous tasks from your BroadcastReceiver, such as coroutines or threads.
I have some critical data to be synced with the server every half an hour or so. I tried using WorkManager but turns out that it cannot run reliably even if I have REQUEST_IGNORE_BATTERY_OPTIMIZATIONS. So then I thought I should use AlarmManager, but they wrote in the docs:
This is a case where you might be tempted to use a repeating alarm. But if you own the server that is hosting your app's data, using Google Cloud Messaging (GCM) in conjunction with sync adapter is a better solution than AlarmManager.
So then I went to SyncAdapter docs and they mentioned with a star:
We recommended WorkManager as the recommended solution for most background processing use cases. Please reference the background processing guide to learn which solution works best for you.
I also read in quite a few blog posts that Sync Manager will be phased out or replaced in the near future.
So I am back again with WorkManager. Is it a good idea to use SyncAdapter in 2020?
If you need to have a sync reliably every 30 minutes, use Firebase Cloud Messaging (the evolution of GCM) and use WorkManager to enqueue a Worker that does the actual backup upload when the application on the device receives the message.
If your application is going to be always in the background you can expect that it will drift into having less and less execution time while in the background. Ignoring battery optimization helps in that case.
Here you have the information regarding the different power buckets.
Note This is for stock Android, some OEMs add additional battery optimization that further reduce background tasks. You can see a collection on issues on https://dontkillmyapp.com/
So I know WorkManager utilizes JobScheduler on supported APIs but there doesnt seem to be any way to make WorkManager work persistent across reboots? Is the only option to request the BOOT_COMPLETED permission and reschedule jobs?
To answer your question: You don't need to do anything if the device is rebooted. WorkManager keeps scheduling your Work without any additional requirement from your side.
WorkManager persists Work requests in its internal Room database. This allows it to guarantee Work execution across device restart.
The documentation covers this as this blog "Introducing WorkManager" that the team wrote last year.
WorkManager is actually used to persist deferrable tasks even after your app exits and the device restarts, please refer to the docs. It uses JobScheduler for api 23 and above and broadcastReceiver and AlarmManger on api 14 to 22. You can use constraints to check battery status, network coverage ...etc depending your particular usecase. You just have to be careful not to remove or rename your existing classes after they are added in the queue because WorkManager uses it's internal database to store those classes and your app will crash if you remove them or rename them.
Background
Ever since Android O came out I had trouble with what classes and methods I should use when I want to schedule background sync tasks and notifications, more specifically, I want to do these two separate things:
Background sync task to download data from the server, and notify the user about important information (that was just fetched from the server). These syncs should occur at an almost precise time of the day (e.g. 21:30 ±10min).
Notify the user about an upcoming event, for example, one week before an exam.
Because Android O has placed some restrictions on AlarmManager, I cannot set a background service that runs at a specific time of the day, unless I use getForegroundService(), which, as the docs say, should only be used for services that are noticeable to the user.
What I tried / considered
I have been using JobService that runs periodically every so and so hours, but I would prefer for it to run at a more specific time of the day.
I have looked into CalendarProvider, and also considered a push notification service, but it seems to me like an overkill for simple tasks like these.
Question
My final question is what methods I could, or I should use to implement the above features?
Use WorkManager this is the best way to Run Task in Background in Android Oreo and Pie versions and it also works in older versions of Android.
See the Documentation
https://developer.android.com/topic/libraries/architecture/workmanager
https://developer.android.com/topic/libraries/architecture/workmanager/basics
https://developer.android.com/topic/libraries/architecture/workmanager/advanced
GitHub Example
https://github.com/krunalpatel3/WorkManager-Example-Andorid
Reference
http://thetechnocafe.com/how-to-use-workmanager-in-android/
https://www.youtube.com/watch?v=fAQKvBHeg_w
https://www.youtube.com/watch?v=1VVir3-4hII
https://www.youtube.com/watch?v=0jgkQYebYvQ
https://www.youtube.com/watch?v=ooP8kkhvRQI
Background
I wanted to use the new JobScheduler API that was presented on Lollipop, but sadly it doesn't have an official port for pre-Lollipop.
However, there is the GcmTaskService, which provides very similar functionalities.
The problem
This API is quite new, so there are very few places to look for information of how to use it (here and here, for example).
The questions
I have a few questions about this new API :
It seems that it requires Google Play Services (here) to be used (except for when using Lollipop version of Android, which will use the normal JobScheduler). What should I do in case the Google play services aren't available?
It seems that even though I've used "setPersisted(true)" for a repeated task, when I restart the device the task won't be called again. How come?
EDIT: that's because I missed a permission of RECEIVE_BOOT_COMPLETED .
What is the default behavior of a task, in case I don't use "setRequiredNetwork" ? Is it "NETWORK_STATE_ANY" ?
The docs say about what's returned from onRunTask , I can return any of the values "RESULT_FAILURE", "RESULT_RESCHEDULE", "RESULT_SUCCESS" (info here). It seems both the FAILURE and SUCCESS options will do the same thing - remove the task from the queue. Is it true? If so, what exactly is the difference between them ? Do they function differently?
Are "TaskParams" used only for the tag of the task? Can I somehow pass a bundle to the task using the API? Otherwise, I would need to set a DB for storing what should be passed to the tasks, right?
Is it possible for the app to get the queue of the tasks? I know it's possible using adb, but is it possible using the API too?
They say (here) that each task has a wakelock of up to 3 minutes. What should be done if the task needs more than that? Should it acquire another wakelock for itself? Will the API warn that the wakelock was released? Here's what the docs say:
The scheduler will hold a PowerManager.WakeLock for your service,
however after three minutes of execution if your task has not returned
it will be considered to have timed out, and the wakelock will be
released. Rescheduling your task at this point will have no effect. If
you suspect your task will run longer than this you should start your
own service explicitly or use some other mechanism; this API is
intended for relatively quick network operations.
They say (here) that all networks-tasks are removed each time the app gets upgraded/replaced, and there is a call for "onInitializeTasks" when this happens, and that you can re-schedule them again. How can I re-schedule the tasks? I don't think I can even get the list of tasks...
Is it possible to tell the task to prefer specific times during the day ? For example, between the 14:00-15:00 ?
I've noticed that if you schedule a task, and then you force-stop and/or clear data of the app, the task will still run. How can I avoid this behavior?
jacktech24 did a really good job, but i will try as well in case there are any lingering questions.
It seems that it requires Google Play Services (here) to be used (except for when using Lollipop version of Android, which will use the normal JobScheduler). What should I do in case the Google play services aren't available?*
You can't use this API if Google Play Services isn't available. Rather, the Google Play Services client library is designed to request that the user download and install Google Play Services if it detects that it's missing, but I don't believe that the GcmNetworkManager does this.
What is the default behavior of a task, in case I don't use "setRequiredNetwork" ? Is it "NETWORK_STATE_ANY" ?*
The javadoc describes which is the default.
The docs say about what's returned from onRunTask , I can return any of the values "RESULT_FAILURE", "RESULT_RESCHEDULE", "RESULT_SUCCESS" (info here). It seems both the FAILURE and SUCCESS options will do the same thing - remove the task from the queue. Is it true? If so, what exactly is the difference between them ? Do they function differently?*
The only difference between these 2 is that in the adb shell dumpsys it will display what you returned, so you can use this to troubleshoot issues.
The other reason is that if the task fails, it is strange to require the client return a "success."
Are "TaskParams" used only for the tag of the task? Can I somehow pass a bundle to the task using the API? Otherwise, I would need to set a DB for storing what should be passed to the tasks, right?*
In the next version of GmsCore the ability to add a bundle to the task should be supported.
Is it possible for the app to get the queue of the tasks? I know it's possible using adb, but is it possible using the API too?
No it's not possible. Instead you should perform the cancel when you want it and if the task is not there it will be a no-op. Similarly you should schedule the task at the point in your code where you would have queried for the list of tasks. use setUpdateCurrent=false to ensure that it doesn't update the pre-existing task. The AlarmManager works in a similar way in that you would set the alarm regardless of whether the alarm was already set - the api was designed to follow this.
They say (here) that each task has a wakelock of up to 3 minutes. What should be done if the task needs more than that? Should it acquire another wakelock for itself? Will the API warn that the wakelock was released? Here's what the docs say:*
Yes, the app should acquire its own wakelock and everything will be fine. The reason the scheduler releases the wakelock after 3 mins is because in practice having an unlimited wakelock timeout only leads to really hard to track down battery drain bugs. If you need longer than 3 mins you have a sophisticated enough use-case that you can dig into how the PowerManager APIs work and call the acquire()/release() yourself (it's really quite simple, the fact that the network manager does it for you is more of a politeness than anything else).
They say (here) that all networks-tasks are removed each time the app gets upgraded/replaced, and there is a call for "onInitializeTasks" when this happens, and that you can re-schedule them again. How can I re-schedule the tasks? I don't think I can even get the list of tasks...*
You reschedule the tasks the same way you scheduled them in the first place. Whatever function you used to schedule them, call that function from GcmTaskService#onInitializeTasks. This was done to avoid lingering tasks across app logic changes. Consider the situation where a developer changes their task timetable, and starts using a different tag. They would be required to call cancel(old_tag) after they'd detected the upgrade (which they'd have to add more code to do), which would mean they'd need a reference to the old (unused) tag even in their new code. This would imply that a tag is a stable identifier that shouldn't change across app upgrades - which shouldn't be a requirement for this api.
Is it possible to tell the task to prefer specific times during the day ? For example, between the 14:00-15:00 ?*
No, this type of background scheduling causes all sorts of problems with herding across large populations of devices. I.e. if 1 device runs a job at 15:00 that is probably fine. But if 1x10e6 do suddenly your server is in serious problems.
I've noticed that if you schedule a task, and then you force-stop and/or clear data of the app, the task will still run. How can I avoid this behavior?*
Unfortunately you can't, but this is not intentional and should be changed - there should be no way for an app to run after the user has explicitly stopped it.
you can find answers to most of your questions it here.
https://github.com/jacktech24/gcmnetworkmanager-android-example/blob/master/README.md
To answers that are not answered there
7: You won't get notified when wakelock is removed, and as they say in documentation, this API is only for short tasks, if you have longer, write your own implementation
9: No you can't as of now, the API doesn't allow it
10: That's because Google Play services are taking care of it, and there is no way around it. You have to detect in the service whether the app is setup (I expect that's your problem) eg. configuration is created etc. and eventually cancel all tasks when one of them is called.