How should i migrate my app to use JobScheduler? - android

My app uses this classic pattern to schedule periodic tasks:
Set an exact alarm via AlarmManager (via setExactAndAllowWhileIdle(), since it has to go off even in Doze)
Start an IntentService from onReceive() via WakefulBroadcastReceiver.startWakefulService()
Do the work in onHandleIntent() and call WakefulBroadcastReceiver.completeWakefulIntent() when finished.
Today i updated the targetSdkVersion to 26 and faced the terrible fact that WakefulBroadcastReceiver is deprecated.
I went to read API docs immediately and found the following:
As of Android O, background check restrictions make this class no
longer generally useful. (It is generally not safe to start a service
from the receipt of a broadcast, because you don't have any guarantees
that your app is in the foreground at this point and thus allowed to
do so.) Instead, developers should use android.app.job.JobScheduler to
schedule a job, and this does not require that the app hold a wake
lock while doing so (the system will take care of holding a wake lock
for the job).
This is kinda confusing to me, i don't really understand what is the purpose of AlarmManager.setExactAndAllowWhileIdle() without being able to keep the device awake anymore.
As i see i cannot set an exact time to run a job with JobScheduler, only conditions (such as network type or charging state) so i have no idea what to do here.
I thought of either
Using AlarmManager and JobScheduler together
Set an alarm (with setExactAndAllowWhileIdle()) and start a job (via JobScheduler) immediately from onReceive(). Since JobScheduler provides a WakeLock, WakefulBroadcastReceiver is not needed.
(does this make sense?)
or
Keep using WakefulBroadcastReceiver despite being deprecated.
I'd really appreciate any advice on this matter.

In android-o and later you can define a repeating task without alarmmanager like this:
// schedule the start of the service "TestJobService" every 10 - 30 minutes
public static void scheduleJob(Context context) {
ComponentName serviceComponent = new ComponentName(context, TestJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
builder.setMinimumLatency(10 * 60 * 1000); // wait at least
builder.setOverrideDeadline(30 * 60 * 1000); // maximum delay
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
jobScheduler.schedule(builder.build());
}

You can use a BroadcastReciever that is triggered by your Alarm and in that receiver call JobIntentService.enqueueWork with your service, that you'll need to convert to a JobIntentService and implement the onHandleWork method.
The solution is described here:
Issue Moving from IntentService to JobIntentService for Android O

Related

Service is killed in sleep mode.Why?

I've read just about every Stackoverflow answer that exists on this topic, but none of them worked.
Goal: Keep my service running 24/7, all the time
Problem: Whenever my device is on sleep mode for an hour or more, the service is killed
What I've tried to fix it:
Returning START_STICKY from onStartCommand() and using startForeground()
public int onStartCommand(Intent intent, int flags, int startId) {
notification = makeStickyNotification(); //I've simplified the irrelevant code, obviously this would be a real notification I build
startForeground(1234, notification);
return START_STICKY;
}
This works fine, and it even restarts my service whenever the device is low on memory, but it is not enough to fix the problem that occurs when my device goes to sleep for a while.
Using Alarm Manager in onCreate() of my Activity and in onStartCommand() of my Service to call a Broadcast Receiver that calls my service
Intent ll24 = new Intent(this, AlarmReceiver.class);
PendingIntent recurringLl24 = PendingIntent.getBroadcast(this, 0, ll24, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000*60, recurringLl24); // Every minute
This helps keep my service active, but again, doesn't solve my problem
Using Schedule Task Executor to keep it alive
if (scheduleTaskExecutor == null) {
scheduleTaskExecutor = Executors.newScheduledThreadPool(1);
scheduleTaskExecutor.scheduleAtFixedRate(new mainTask(), 0, 1, TimeUnit.SECONDS);
}
...
class mainTask implements Runnable {
public void run() {
// 1 Second Timer
}
}
This also just keeps the service active but doesn't keep it alive after a long sleep.
Separate task Manifest
android:launchMode="singleTop"
This did nothing
How can I (1) test this issue without having to put my phone to sleep and check every hour and (2) keep my service running despite the device going to sleep?
Your service was killed by Doze or Standby mode of Android. That was introduced in Android 6.0 (API level 23).
Doze restrictions
The following restrictions apply to your apps while in Doze:
Network access is suspended.
The system ignores wake locks.
Standard AlarmManager alarms (including setExact() and setWindow()) are deferred to the next maintenance window.
If you need to set alarms that fire while in Doze, use setAndAllowWhileIdle() or setExactAndAllowWhileIdle().
Alarms set with setAlarmClock() continue to fire normally — the system exits Doze shortly before those alarms fire.
The system does not perform Wi-Fi scans.
The system does not allow sync adapters to run. The system does not allow JobScheduler to run.
So system ignored your Alarm Clocks, Scheduler, etc.
In Android Oreo release Android defined limits to background services.
To improve the user experience, Android 8.0 (API level 26) imposes
limitations on what apps can do while running in the background.
Still if app need to run its service always, then we can create foreground service.
Background Service Limitations: While an app is idle, there are limits
to its use of background services. This does not apply to foreground
services, which are more noticeable to the user.
So create a foreground service. In which you will put a notification for user while your service is running. See this answer (There are many others)
Now what if you don't want a notification for your service. A solution is for that.
You can create some periodic task that will start your service, service will do its work and stops itself. By this your app will not be considered battery draining.
You can create periodic task with Alarm Manager, Job Scheduler, Evernote-Jobs or Work Manager.
Instead of telling pros & cons of each one. I just tell you best. Work manager is best solution for periodic tasks. Which was introduced with Android Architecture Component.
Unlike Job-Scheduler(only >21 API) it will work for all versions.
Also it starts work after a Doze-Standby mode.
Make a Android Boot Receiver for scheduling service after device boot.
I created forever running service with Work-Manager, that is working perfectly.
The murder mystery has been solved, and I know what killed my service. Here's what I did:
After I realized that startsticky, startforeground, alarmmanager, scheduleTaskExecutor, and even wakelock were unable to save my service, I realized the murderer couldn't be the Android system, because I had taken every measure possible to prevent the system from killing my service and it still would get killed.
I realized I needed to look for another suspect, since the service wasn't dying because of the system. For that, I had to run an investigation. I ran the following command:
adb shell dumpsys activity processes > tmp.txt
This would give me a detailed log of all the processes running and their system priorities. Essentially, tmp.txt would be the detective in this murder mystery.
I looked through the file with lots of detail. It looked like my service was prioritized properly by the system:
Proc #31: adj=prcp /FS trm= 0 2205:servicename.service/uID (fg-service)
The above line indicates the exact priority of a process running on the Android device. adj=prcp means the service is a visible foreground service.
At this point, I realized that my service must be encountering some error a couple hours after running, so I let it run and die. After it died, I produced a dumpsys again to examine the error:
At this point, my service wasn't listed as a task in the tmp.txt file. Excited, I scrolled to the bottom of the dumpsys and solved the mystery!
com.curlybrace.ruchir.appName.MyService$2.onForeground(MyService.java:199)
at com.rvalerio.fgchecker.AppChecker$2.run(AppChecker.java:118)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
The stack trace that caused the killing of my service was displayed right there! Essentially, a variable that would check for the foreground app being used would become null after a few hours of inactivity, which would cause an exception, and kill the service!
Key Takeaways:
If your service is getting killed, and you've done everything you can to make sure that it shouldn't be killed, perform a dumpsys and examine the nitty gritty of your device's activity process. I guarantee you will find the issue that way.
I still would like to have the bounty awarded to #Khemraj since his answer could be a great solution for someone who hasn't started their service properly. However, I am accepting this answer since it is the solution that actually fixed the issue.
onDestroy() is really unreliable and won't be called often that you want. Same for onLowMemory() callbacks. There is no way to take a guaranteed callback if android decides to kill your process or if user decides to Force Stop your app.
That's normal that than user device go to sleep mode, your service dies. Read about wakelocks. Try something like that in your service:
In manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
In service:
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"tag");
wakeLock.acquire();
But it's rly tricky for user and totally anti-pattern in android world, cuz of battery consumption.
Another option is to trigger service something like every 10 mins. Make pending intent on WakefulBroadcastReceiver(where you can start your service) and schedule it with alarm manager with flag RTC_WAKE_UP
Starting from SDK 26 a Service should have its relative "MainActivity" in foreground OR this Service should be started as in foreground using "startForegroundService()". The "startForeground()" doesn't work as expected if the target SDK is 26+ but need the other way I just explained.
After this you can use following code to Kill and restart the App from scratch (yes, even the Service is killed in this way):
Intent mStartActivity = new Intent(context, StartActivity.class);
int mPendingIntentId = 123456;
PendingIntent mPendingIntent = PendingIntent.getActivity(context, mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
System.exit(0);
Doze mode kills services to save battery. Only valid solution for you is to create a foreground service in Oreo and above.
https://developer.android.com/about/versions/oreo/background

Android Frequent Background Polling - CountDownTimer or JobScheduler

We have a "chat" SDK for lack of a better term, designed to drop into a host application and allow live chat sessions with an agent. Part of that process is polling the server every few seconds while a conversation is taking place for new messages or information. Since this is intended at least partially as a diagnostic aid, it is very desirable that this polling and conversation continue in the background. Note that we have a defined start and end of a conversation, so this is not a boundless process.
Up until recently, this was implemented as a BoundService that was directly started (the conversation has to survive past being bound) With the changes to Android 8.0, this no longer works. The background service is terminated "shortly" after the application is put in the background.
My first attempt around it was to use a periodic JobScheduler, but that can't poll any more frequently than 15 min.
We're investigating options for supporting Android 8.0, and so far the easiest option seems to be to eliminate the background service and implement the polling using a CountDownTimer (more or less obviously on the main thread, but irrelevant because the work is actually handled in an AsyncTask) This seems to be working fine, the polling continues until the conversation ends and everything is copacetic.
Should I change this to use a JobScheduler with a short timeout that just reschedules itself upon completion?
Another option might be a foreground Service containing the conversation itself, but that would entail quite a bit more work.
If you need such short poll times, I would advise either you use the following
ScheduledThreadpoolExecutor
As per reading tour case, this seems the most suitable option as you need regular intervalic polling on a seperate thread. The ScheduledThreadpoolExecutor has a very easy implementation for this. Note the following code:
Executors.newScheduledThreadPool(10) //thread count
.scheduleAtFixedRate(() -> {
//Perform action; //Implement a runnable
},1000, 10, TimeUnit.MINUTES);
//1000 - The initial delay in milliseconds.
//10 - The number of minutes to keep polling.
All this happens on a separate thread, as you requested. Executors are a basic Java Framework Threading Primitive. The only downside is once the process is killed. The constant polling will die too.
For a more Android-Centric approach try an AlarmManager which can optionally survive process deaths even phone-reboots (only if you want, you must declare this in the manifest)
AlarmManager
This class provides access to the system alarm services. These allow you to schedule your application to be run at some point in the future. When an alarm goes off, the Intent that had been registered for it is broadcast by the system, automatically starting the target application if it is not already running.
Example Implementation
Calendar repeatTime = Calendar.getInstance();
repeatTime.set(Calendar.HOUR_OF_DAY,14);
repeatTime.set(Calendar.MINUTE,05);
AlarmManager mAlarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(mContext, <YOURBROADCASTRECEIVER>.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, YOUR_REQUEST_CODE,intent, PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.setInexactRepeating(AlarmManager.RTC,repeatTime.getTimeInMillis(),AlarmManager.INTERVAL_FIFTEEN_MINUTES, pendingIntent);

Time-based notification in Oreo

I am working on a task scheduling app that reminds me of a task at a certain time. I used to implement this with getSystemService(AlarmManager.class).set(RTC_WAKEUP, millis, pendingIntent);. Then a IntentService gets started later and shows a notification.
But in Android Oreo my IntentService simply doesn't get called at all. I noticed the massive discussion about the Background Execution Limits and how you should migrate from AlarmManager to JobScheduler, but nobody mentions how JobScheduler can start a job based on RTC.
What my IntentService does besides showing a notification is nothing more than setting the task as "notified" or "done", which I think is incomparable to the heavy tasks like synchronizing big amount of data over network that the JobScheduler was introduced for.
What is the best practice in Android Oreo to implement time-based notifications?
I am taking #balu-sangem 's solution:
Replace PendingIntent.getService() with
PendingIntent.getBroadcast()
Replace IntentService with BroadcastReceiver

Network call on alarm times out

I have an app that uses AlarmManager to schedule a repeating alarm every X amount of time. When my receiver receives the Intent, it has to make a http request.
The alarm itself works fine and triggers when it should. The network call, however, starts timing out when the phone is not in use. To be more specific:
When I schedule it to fire every minute (bad practise, I know, but just to illustrate), the first 5-8 minutes the request succeeds. After that, I get a java.net.SocketTimeoutException: connect timed out. Sometimes it does succeed, but mostly this happens.
I tried setting the connect/read/write timeouts to a minute, but then I get this exception instead of the one above: java.net.ConnectException: Failed to connect to myapp.example.com/123.45.67.89:80.
My code:
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Consider mApi and myBody to be initialised and valid
mApi.myPostRequest(myBody).enqueue(new Callback<Void> {
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
//Does not get here
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
t.printStackTrace();
}
}
}
}
Things I've tried:
as stated before, increasing timeouts
acquiring a WakeLock in
onReceive and releasing it when the call is done (added the
permission)
Other information:
The alarm is set using
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(), interval, pendingIntent); from my
Activity.
I'm using Retrofit (2.1.0) for the network communication, but you could probably have guessed that from my code ;)
Any ideas on how to get the network call working when the phone is sleeping?
You Should use JobService here, it has many constraint to handle different scenarios and also your Job is guaranteed to be executed by the system.
The problem here is of doze mode, and using JobService this can be resolved easily.
Implementation is also easy all you need to do is create a JobService and inside it's onStartJob() start your network thread and then just dispatch your job.
For more detail
https://developer.android.com/reference/android/app/job/JobService.html
From https://developer.android.com/reference/android/content/BroadcastReceiver.html
As a general rule, broadcast receivers are allowed to run for up to 10
seconds before they system will consider them non-responsive and ANR
the app. Since these usually execute on the app's main thread, they
are already bound by the ~5 second time limit of various operations
that can happen there (not to mention just avoiding UI jank), so the
receive limit is generally not of concern. However, once you use
{#goAsync }, though able to be off the main thread, the broadcast
execution limit still applies, and that includes the time spent
between calling this method and ultimately PendingResult.finish().
Further reading says
If you are taking advantage of this method to have more time to
execute, it is useful to know that the available time can be longer in
certain situations. In particular, if the broadcast you are receiving
is not a foreground broadcast (that is, the sender has not used
FLAG_RECEIVER_FOREGROUND), then more time is allowed for the receivers
to run, allowing them to execute for 30 seconds or even a bit more.
(long work should be punted to another system facility such as
JobScheduler, Service, or see especially JobIntentService),
You can try using #goAsync. Or you can switch your logic to JobIntentService
I haven't tested any of these.
You have a basic mistake in your code - you cannot make requests (or any long running operations) in your broadcast receiver - it dies after ~10 sec so that might be the reason for some of your failures.
You should move the request logic to a service (IntentService) which you'll start from your broadcast receiver and make the request there.
That should work just fine.
From developer docs : https://developer.android.com/reference/android/app/AlarmManager.html
The Alarm Manager holds a CPU wake lock as long as the alarm
receiver's onReceive() method is executing. This guarantees that the
phone will not sleep until you have finished handling the broadcast.
Once onReceive() returns, the Alarm Manager releases this wake lock.
This means that the phone will in some cases sleep as soon as your
onReceive() method completes
In your code onReceive will return before mApi.myPostRequest(myBody).enqueue ...task will be executed, then this task probably will be never executed due the CPU will stop as soon onReceive returns.
You said you tested acquiring a WakeLock, but new Android 6.0 Doze mode ignores wakelocks
It seems that OnReceive will have to wait for the task to end
Some ideas:
Checking for some terminate flag in a loop with thread.sleep ?
If the task uses a Thread object then using thread.join() ?
You should use AlarmManager.RTC_WAKEUP, not AlarmManager.ELAPSED_REALTIME_WAKEUP to wake up device and you should use a service to do your job started in Received in onReceive with startWakefulService(context, service.class). This will ensure that device will fully wake up and do your network call without timeout.
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime(), interval, pendingIntent);
If the problem is caused by Doze and Wakelocks being ignored, you should try the tips offered in https://developer.android.com/training/monitoring-device-state/doze-standby.html:
Standard AlarmManager alarms (including setExact() and setWindow()) are >deferred to the next maintenance window.
If you need to set alarms that fire while in Doze, use setAndAllowWhileIdle() >or setExactAndAllowWhileIdle().
Alarms set with setAlarmClock() continue to fire normally — the system exits Doze shortly before those alarms fire.
there are 2 problems in your implementation:
1) if broadcast receiver doesn't get finished executing within 10 seconds then ANR will happen.
2) All network calls are optimized in the background, so when device is wake it may work else will not fire HTTP request to conserve battery and other resources.
what you should do is inside service create a loop (with sleep time of few seconds) that checks time on each iteration and when that time is reached then perform task, I have also faced such issues when tried to upload files to server at an interval of 1 hour, so I decided to work on my own rather than using AlarmManager class...
I hope this will help you...
Use Foreground service to get your job done.
Refer the official link for dive into Foreground service
By using Foreground service, we can set the interval to start doing an API call in the background without getting affected by the doze mode. follow this simple example that includes complete code and walkthrough.
happy coding. Thanks ;)

Alarm Manager vs Job Scheduler vs ScheduledThreadPoolExecutor

this question is just to get some ideas on these concepts to see what people in the industry use these for.
If you can just give a few example uses for each of these, I would greatly appreciate it. I know how they work and did read the google documentation on them but I still seem to be having a hard time picking one over the other. I don't need you to explain to me how they work. I am simply asking for a few of their example uses. How you ended up incorporating them into your apps and why you picked them over the others.
Thanks
AlarmManager
Using the AlarmManager to Schedule Tasks at the System Level
The AlarmManager provides access to system-level alarm services. Using the AlarmManager allows an application to schedule tasks that may need to run or repeat beyond the scope of its lifecycle. This allows the application to perform some function even after the application process or all of its Android components have been cleaned up by the system.
Typically, the AlarmManager is used to fire off a PendingIntent that will start up a Service in the future. The AlarmManager triggers Services based on an elapsed interval or at a specific clock time. Both of these options also have the ability to wake up the device when it is asleep if the alarm is urgent.
The benefits of the AlarmManager come into play when using inexact intervals or times to fire off Services. The Android system tries to batch alarms with similar intervals or times together in order to preserve battery life. By batching alarms from multiple applications, the system can avoid frequent device wake and networking.
One concern to consider while using the AlarmManager is that alarms are wiped out during device reboots. Applications need to register the RECEIVE_BOOT_COMPLETE permission in their Android Manifest and reschedule their alarms in a BroadcastReceiver.
Another concern is that a poorly designed alarm could cause battery drain. While the AlarmManager does have the ability to wake devices and set an exact time for an alarm, the documentation mentions that developers should be wary of these features when performing networking. Aside from draining a device’s battery by avoiding batch alarms, setting an exact time for an application to sync with a server could put high strain on a server if every application installation tries to sync with the server around the same time! This can be avoided by adding some randomness to alarm intervals or times.
AlarmManager is a great candidate for scheduling if an application needs to perform a local event at an exact time or inexact interval. Alarm clock or reminder applications are great examples for AlarmManager usage. However, the documentation discourages using AlarmManager for scheduling network-related tasks. Let’s take a look at some better options for networking.
Job Scheduler
JobScheduler helps perform background work in an efficient way, especially networking. JobServices are scheduled to run based on criteria declared in JobInfo.Builder(). These criteria include performing the JobService only when the device is charging, idle, connected to a network or connected to an unmetered network. JobInfo can also include minimum delays and certain deadlines for performing the JobService. Jobs will queue up in the system to be performed at a later time if none of these criteria are met. The system will also try to batch these jobs together in the same manner that alarms are scheduled in order to save battery life when making a network connection.
Developers might be concerned about a scheduler that frequently delays firing off its JobServices. If jobs are frequently delayed and data stale as a result, it would be nice to know about such things. JobScheduler will return information about the JobService such as if it was rescheduled or failed. JobScheduler has back-off and retry logic for handling these scenarios, or developers could handle those scenarios themselves.
Subclassing JobService requires an override of its onStartJob(JobParams params) and onStopJob(JobParams params) methods. onStartJob() is where callback logic for jobs should be placed, and it runs on the main thread. Developers are responsible for threading when dealing with long running jobs. Return true to onStartJob() if separate thread processing needs to occur, or false if processing can occur on the main thread and there is no more work to be done for this job. Developers must also call jobFinished(JobParameters params, boolean needsReschedule) when the job is complete and determine whether or not to reschedule more jobs. onStopJob() will get called to stop or cleanup tasks when initial JobInfo parameters are no longer met, such as a user unplugging their device if that parameter is a requirement.
There might be a lot to think about when implementing a JobService, but it comes with a lot more flexibility than AlarmManager. Another handy feature is that scheduled jobs persist through system reboots.
There is at least one drawback to using JobScheduler. As of the writing of this post, it’s compatable only with API level 21 and higher. Here you can find the distribution of Android devices running various API levels. While there is technically no backport of JobScheduler, a similar tool is GCM Network Manager.
REFERENCE LINK

Categories

Resources