Android WorkManager usage scope - android

Android 12 brings restrictions on foreground service usage and dev team offers a WorkManager as an alternative.
Google team wrote a good article on what should be used and when. My use case is close to long-running operations which is covered here. However, it requires to define a category of service type which seems doesn't cover my use case.
My use case is to run minting a token from mobile wallet on ethereum chain and wait for callback. I use privately hosted eth chain which mines a block each 15 seconds. Keeping in mind this time schedule might increase in future depends on changes in business requirements I want the app to support a longer request even at this stage of the development. That's why I need to run it as a long running operation.
FOREGROUND_SERVICE_TYPE_SPECIAL_USE looks like a right fit, but it is supported only on the latest SDK. Compilation under the my current compile SDK (33) supports only this options
[camera=64, connectedDevice=16, dataSync=1, location=8, mediaPlayback=2, mediaProjection=32, microphone=128, phoneCall=4]
That's from an error shown by compiler.
Android compileSdk = 34 is not supported, but anyway it is still under preview. This limited options from the compiler error messages tells me I WorkManager long running operations are not a good fit for my use case. Altogether it brings me to the questions:
Which type of foreground service type should I choose?
Shall I reject WorkManager for this use case at all?
What could be an alternative for WorkManager on post Android 12 devices for foreground-service operations?

Related

Workmanager with multiple-processes

Is there a way to enqueue work in work manager from multiple processes? Right now I’m getting this message when I’m trying to do it from the "non-ui" process
I/WM-GreedyScheduler: Ignoring schedule request in non-main process.
Work manager version I'm using is 2.4.0. I know there is 2.5.0-alpha2 right now, but I cannot use that in production
WorkManager 2.5.0-alpha02 is the first version of WorkManager that has multi-process support.
There is no mechanism for using WorkManager across multiple processes in any previous version of WorkManager (hence why these new APIs were added in the first place) so you'll need to either upgrade to that version of WorkManager or wait for it to become beta/RC/stable.

No way to make Work persistent across reboots?

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.

Logic behind Android O Background Service Limitations

The new limitations on running background services is explained here and here.
This new behaviour is apparently to stop apps doing lots of stuff in the background without the user even being aware, which is fair enough.
However, one of the suggested workarounds is to use a scheduled job instead. But doesn't that also potentially result in the app "doing lots of stuff in the background without the user even being aware"? The only difference being that Android decides exactly when this stuff is done, rather than the app.
So, what exactly is the point of the new limitations? Or maybe I've missed something fundamental.
EDIT: This is not a duplicate of this question... that one is about using startServiceInForeground() as an alternative (or about documentation relating to that) whereas this question is about using a scheduled job as an alternative (and whether that defeat the purpose of the new limitations completely). These are completely different alternatives.
I see your point.
Seems to me (after reading through the docs) this is the only pro that we get using the new JobScheduler.
However if you look through Job Scheduler Improvements in O
Scheduled jobs now support several new constraints:
JobInfo.isRequireStorageNotLow()
Job does not run if the device's available storage is low.
JobInfo.isRequireBatteryNotLow()
Job does not run if the battery level is at or below the criticial threshold; this is the level at which the device shows the Low battery warning system dialog.
NETWORK_TYPE_METERED
Job requires a metered network connection, like most cellular data plans.
You would notice that for each JobInfo you get to specify which constraints need to hold for the job to start.
Once the job has been started, it is then free to use as many resources as it wants for however long it wants.
That seems to be true, since there is no indication from the docs on what would happen if/when the resources are required again. However you can break down your big task into smaller tasks and then use the [JobScheduler.enque()](https://developer.android.com/reference/android/app/job/JobScheduler.html#enqueue(android.app.job.JobInfo, android.app.job.JobWorkItem)) method to ensure that the JobScheduler stops before starting next job in queue if the System otherwise requires resources. However this may not be necessary if JobScheduler provides a method or a callback to the Job to pause/stop (doubtful since the docs don't get into it) I've never tried it myself.
Conclusion :
So in conclusion while the new API does not completely restrict the Background Services to run while required resources are available, it does however provide a platform for the coder to restrict them to start only when above mentioned conditions are met while also restricting min lines of code required. Which should in most cases minimize battery usage and provide a smoother experience to user.

Saving/Serializing OkHttp/Retrofit Requests

I want to be able to recover from crash/closing the app or just device being disconnected.
Currently when I detect that the network is out for my Android device I save the Call created with RetroFit2 in a stack (to process later). If the user were to close the app or restart the device I lose the possibility to save these calls anywhere...
My question is the following, how can I save a RetroFit Call or an OkHttp3 Request?
None of them is serializable or nor can I convert them to strings from what I could see looking at the code.
Use android priority jobqueue by Yigit Boyar (one of the google android guys). It'll serailize your jobs, detect network changes (and respond accordingly) and persist even through device reboots (let alone app crashes). Plus a ton of other features. Just take a look. It is not exactly what you requested but it's a better solution. It's Magic.
Starting with v2, Job Queue can be integrated with JobScheduler or GCMNetworkManager. This integration allows Job Queue to wake up the aplication based on the criterias of the Jobs it has. You can see the deatails on the related wiki page. The Scheduler API is flexible such that you can implement a custom version of it if your target market does not have Google Play Services.
Try it and you'll be glad you did, as I've been. It filled the huge gap in my code that I spent weeks hacking together with spit, ducktape and faith.

Some questions about GcmTaskService

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.

Categories

Resources