Periodic job in Android Job Scheduler not working as expected - android

I have a requirement to sync data from server to my local device in specified time interval, for that I am using Android JobScheduler. But it is not working as expected. For example I scheduled periodic job in every 30 minute, at the first time it worked properly. But next times the job is executed before 15 minutes, I debugged the interval time but it is always 30 minutes.
Only 1st time it working correctly.
This is the pending job information log:
Service Name ->ComponentInfo{my pakage name.services.SyncServiceFare}Time Interval->1800000}
Service Name ->ComponentInfo{my pakage name.services.SyncService}Time Interval->1800000}
This is my code:
private fun schedulePeriodicJob(JOB_ID: Int, fetchInterval: Long) {
jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler?
var serviceName : ComponentName? = null
try {
if (jobScheduler == null) {
return
}
jobScheduler?.cancel(JOB_ID)
serviceName = if(JOB_ID == 1){
ComponentName(this, SyncService::class.java)
}else{
ComponentName(this, SyncServiceFare::class.java)
}
val builder = JobInfo.Builder(JOB_ID, serviceName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setBackoffCriteria(60000, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
.setPersisted(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setPeriodic(
TimeUnit.MINUTES.toMillis(fetchInterval),
JobInfo.getMinFlexMillis()
)
} else {
builder.setPeriodic(TimeUnit.MINUTES.toMillis(fetchInterval))
}
val extras = PersistableBundle()
val scheduleTime = System.currentTimeMillis()
extras.putLong("SCHEDULE_TIME", scheduleTime)
builder.setExtras(extras)
jobScheduler?.schedule(builder.build())
} catch (e: Exception) {
e.printStackTrace()
}
}
How can I solve this issue?

The period you are setting for the JobScheduler only means that the process will be executed inside a window of time as large as that period. You do not know when it will run: the system will calculate the best time for the execution the task (probably grouping it together with other scheduled tasks) in order to reduce battery usage and keep the phone in idle state as long as possible.
The next scheduled execution will be inside another window o time that starts as soon as the task has finished. That is why you are seeing inconsistent times and this is by design.
If you need to execute jobs at exact times then you should look at AlarmManager's setExact method, although this is strongly discouraged since it will negatively impact the battery consumption of the device.

Related

WorkManager keeps finished Work in list by Tag

I am enqueueing an OneTimeWorkRequest with AlarmManager every 90 seconds:
// Alarm manager receiver setup (simplified)
getAlarmManager(context).setInexactRepeating(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(),
90 * 1000,
PendingIntent.getBroadcast(..., Intent(SyncAlarmReceiver::class.java), ...)
)
// SyncAlarmReceiver's onReceive
override fun onReceive(context: Context, intent: Intent) {
val workRequest = OneTimeWorkRequest.Builder(SyncWorker::class.java).addTag(TAG).build()
WorkManager.getInstance(context).enqueue(workRequest)
}
Then I am watching the state in Activity by:
WorkManager.getInstance(this).getWorkInfosByTagLiveData(DataSyncWorker.TAG).asFlow().collect {
workInfos ->
workInfos.forEach { workInfo ->
Timber.e("WORK INFO: ${workInfo.id} ${workInfo.state}" )
}
}
}
Each time the work is started, I am getting RUNNING and than SUCCESS states. Thats fine.
My problem is that each time, next work is started, the old work still pop up in workInfos so after 5 launches I have list of 5 SUCCESS workInfos, and so on.
Why old works, that are done, are still in list? Can I somehow remove them?
I need to watch the periodic work in Activity to react on it, but this complicates it.

Android AlarmManager does not trigger when app is killed

I have a requirement to perform a task at exactly every 5 minutes. I have considered multiple options and have attempted to implement this using the AlarmManager class to trigger the task. However I cannot get the alarm to trigger when the application has been killed.
The alarm seems to work flawlessly when the app is open, or running in the background, but as soon as I exit the app, it seems to completely stop.
My implementation is to use the setExactAndAllowWhileIdle() function and to handle the repeating of this myself. The initial alarm is triggered after 5 seconds, then every 5 minutes after this.
I have tested this with 5 and 10 minute increments, but again this never runs when the app is closed.
Please take a look at my implementation:
My Activity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_landing)
initAlarm()
}
private fun initAlarm() {
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 5
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
AlarmReceiver.kt:
class AlarmReceiver : BroadcastReceiver() {
companion object {
private val TAG = AlarmReceiver::class.java.simpleName
}
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "onReceive()")
if (intent?.action == "MY_ALARM") {
Log.d(TAG, "onReceive() - starting service")
context?.startService(Intent(context, MyService::class.java))
initAlarm(context)
}
}
private fun initAlarm(context: Context?) {
val alarm = context?.applicationContext?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 600
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
}
MyService.kt:
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate()")
doMyTask()
}
private fun doMyTask() {
job = CoroutineScope(Dispatchers.IO).launch {
// Perform task here and once complete stop service
stopSelf()
}
}
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy()")
job?.cancel()
job = null
}
The problem is that the code is calling startService() while the app is in the background.
This was allowed prior to Android 8, but now is subject to the following restrictions:
While an app is in the foreground, it can create and run both
foreground and background services freely. When an app goes into the
background, it has a window of several minutes in which it is still
allowed to create and use services. At the end of that window, the app
is considered to be idle. At this time, the system stops the app's
background services, just as if the app had called the services'
Service.stopSelf() methods.
Background Execution Restrictions
The service created in the sample above is a "Background Service" in the language of the Android docs because it does not call startForeground to post a Notification to make the user aware that it is running.
To start a "Foreground service" from an alarm while the app is in the background, ContextCompat.startForegroundSerivce must be used. It will call startForegroundService on Android 8 and above and startService on older devices.
In the service, you will need to call startForeground to post an ongoing service Notification to make the user aware that the service is running. If the Notification is not posted, the service will be killed after 5 seconds.
It would also worth considering whether the task can be accomplished via WorkManager or with Firebase Cloud Messaging.
Lastly, you probably need to inform your client that running a task "exactly every 5 minutes" is not possible on modern Android devices. I have not looked at the Doze implementation recently, but in the past have observed routine delays of up 10 minutes during maintenance windows when using setExactAndAllowWhileIdle. But the delays can be longer under certain circumstances.
Regarding onReceive not being called in the BroadcastReceiver:
Disable Battery Optimisations
Do not kill my app
Lastly, you could try passing in a unique requestCode in getBroadcast instead of passing 0 each time.

How To schedule and a send Notification even after user removes app from recent apps?

My app is simple
Take Time from the user(I know how to do it)
schedule A notification(I know how to send notification)
Now I just want to know that How can I send this notification even user removes it from recent apps.
Tried Solutions-
AlarmManager, BroudcastReceiver, Service, Intent Service,
But all are work only when the app is present in RAM (recent apps list Of Mobile).
How can I do that just tell me the topic. I don't need code.
I will study that.
you can use WorkManger to schedule tasks.
The WorkManager API makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or device restarts.
check Google documentation here.
for notifications, you can send a notification in your work manager class. learn more here.
these hints should be enough. let me know if you need more clarifications.
As you have mentioned that AlarmManager and others did not work for you, I tested what you are trying to achieve with JobScheduler.
It worked for me, and it worked even after removing the app from the recent apps list.
I tried it on Android 10 emulator.
You have asked for topics / references to study as the answer.
So first I'll mention the references I used.
Here is the code lab of JobScheduler and it's really helpful : https://codelabs.developers.google.com/codelabs/android-training-job-scheduler/
Here is a good reference about creating multiple scheduled jobs : https://android-developers.googleblog.com/2017/10/working-with-multiple-jobservices.html
Here is what I tried.
NotificationJobService.kt
private const val NOTIF_CHANNEL_ID = "primary_notification_channel"
private const val NOTIF_CHANNEL_NAME = "Job Service notification"
class NotificationJobService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
// Get Notification Manager
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Create Notification Channel if device OS >= Android O
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel(NOTIF_CHANNEL_ID, NOTIF_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT).let {
notificationManager.createNotificationChannel(it)
}
}
// Create PendingIntent with empty Intent
// So this pending intent does nothing
val pendingIntent = PendingIntent.getActivity(this, 0, Intent(), PendingIntent.FLAG_ONE_SHOT)
// Configure NotificationBuilder
val builder = NotificationCompat.Builder(this, NOTIF_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Title")
.setContentText("Message")
.setAutoCancel(true)
.setContentIntent(pendingIntent)
// Make the Notification
notificationManager.notify(0, builder.build())
// False to let system know that the job is completed by the end of onStartJob(),
// and the system calls jobFinished() to end the job.
return false
}
override fun onStopJob(params: JobParameters?): Boolean {
// True because if the job fails, you want the job to be rescheduled instead of dropped.
return true
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Job ID must be unique if you have multiple jobs scheduled
var jobID = 0
// Get fake user set time (a future time 1 min from current time)
val ( userSetHourOfDay, userSetMinute ) = getMockUserSetTime()
val timeToWaitBeforeExecuteJob = calculateTimeDifferenceMs(userSetHourOfDay, userSetMinute)
(getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler).run {
schedule(
JobInfo.Builder(
jobID,
ComponentName(baseContext, NotificationJobService::class.java)
)
// job execution will be delayed by this amount of time
.setMinimumLatency(timeToWaitBeforeExecuteJob)
// job will be run by this deadline
.setOverrideDeadline(timeToWaitBeforeExecuteJob)
.build()
)
}
}
// Returns a pair ( hourOfDay, minute ) that represents a future time,
// 1 minute after the current time
private fun getMockUserSetTime() : Pair<Int, Int> {
val calendar = Calendar.getInstance().apply {
// add just 1 min from current time
add(Calendar.MINUTE, 1)
}
return Pair(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE))
}
// Calculate time difference relative to current time in ms
private fun calculateTimeDifferenceMs(hourOfDay: Int, minute: Int) : Long {
val now = Calendar.getInstance()
val then = (now.clone() as Calendar).apply {
set(Calendar.HOUR_OF_DAY, hourOfDay)
set(Calendar.MINUTE, minute)
}
return then.timeInMillis - now.timeInMillis
}
}
I used setMinimumLatency(timeToWaitBeforeExecuteJob) and setOverrideDeadline(timeToWaitBeforeExecuteJob) constraints when scheduling the job so that, the job will be executed at exact time we want it to run.
I ran the app once, go back and removed the app from recent apps list.
Then I suspended the device, and after 1 minute, I heard the notification sound.
When I resumed the device and checked, the expected notification was there.
You should consider Remon Shehatta's answer as well. Because it seems
WorkManager sits on top of JobScheduler and AlarmManager, and picks
the right one based on device's API level. So it may be better to use
WorkManager if you are targeting API levels older than 21.
But as you mentioned that AlarmManager did not work for you, you should
experiment and choose the right one.
Please update us with your findings.

Upload data to server on Internet in Android

I am trying to upload to data to a server in background when there are internet and app is not running in the front end.
So I read somewhere this can be achieved by JobService.
I created a simple job service that toasts onStartJob and in the splash screen activity. I called the below code:
mJobScheduler = (JobScheduler)
getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(1,
new ComponentName(getPackageName(),
Unigen_Upload_JobScheduler.class.getName()));
builder.setPeriodic(60000);
builder.setPersisted(true);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
if (mJobScheduler.schedule(builder.build()) <= 0) {
Log.e("Value", "onCreate: Some error while scheduling the job");
}
This runs the first time properly but doesn't run again after 1 minute. I am not sure why this isn't happening?
Also, I had another question will this trigger whenever the WIFI or Mobile is switched ON / Changed?
Do I have to use a broadcast receiver to do the above?
Thanks!
Update:
public class Unigen_Upload_JobScheduler extends JobService {
public Unigen_Upload_JobScheduler() {
}
#Override
public boolean onStartJob(JobParameters params) {
Toast.makeText(this,"Executed",Toast.LENGTH_LONG).show();
Log.e("Value","+_ what should I do");
/*
* True - if your service needs to process
* the work (on a separate thread).
* False - if there's no more work to be done for this job.
*/
return false;
}
#Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
The minimum interval for JobScheduler Periodic Job is 15 minutes. Check the reason behind this:
Why can't an interval be smaller than 15 minutes for periodic jobs?
JobScheduler's minimum periodic interval is 15 minutes or (15 * 60 * 1000) 900000ms. You can look into WorkManager which is a part of android jetpack for more convenient usage. As for your second question workmanager has convenient methods for scheduling tasks on various scenarios.

AlarmManager scheduling fires for alarms an hour or less in advance, but fails to fire after longer than that

I am trying to schedule SMS messages in my app, using alarm manager with the RTC_WAKEUP type so that it will fire even when the device is in doze mode (as it needs to be exact). I have had reports of it not working and in my own testing it seems that if I for example schedule a message for an hour ahead it will fire, but then scheduling for the next day does not fire. (I have verified that the times being scheduled are correct).
Here is the code for the scheduling:
fun scheduleTimeMessage(activity: Context, time: Long) {
val manager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(activity, TimeSendReceiver::class.java)
intent.putExtra("TIME", time)
Log.e("ERROR?", "Time is " + time + SimpleDateFormat(" dd/MM/yy hh:mm", Locale.US).format(Date(time)))
if (Build.VERSION.SDK_INT >= 23)
manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(activity, time.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT))
else
manager.setExact(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(activity, time.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT))
}
Here is the BroadcastReceiver:
class TimeSendReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val epoch = intent.getLongExtra("TIME", 0)
val database = SchedulerDatabase(context)
val cursor = database.getTimeCursor(epoch)
if (cursor.moveToFirst()) {
do {
if (cursor.getString(cursor.getColumnIndex("Address")).contains(","))
sendGroupMessage(context, cursor)
else {
if (cursor.getString(cursor.getColumnIndex("Image")) == "")
sendSMSMessage(context, cursor)
else
sendMMSMessage(context, cursor)
}
} while (cursor.moveToNext())
database.deleteMessageData(epoch)
cursor.close()
}
}
}
Ok this one is hard to test (I would have to wait for a day for one test), but here a some things you might try:
Check out the WakefulBroadcastReceiver, explained in this post
Check whether your device has some battery optimization settings. This can result in your app being killed after some time and all it's alarms being canceled. You should be able to turn off optimization for certain apps.
I ended up using the JobScheduler API and I set the JobInfo.Builder to:
val jobInfo = JobInfo.Builder(Constants.JOB_SCEDULE_ID, ComponentName())
.setOverrideDeadLine(millis + 100)
.setMinimumLatency(millis - 100)
.build()
This makes it so that the scheduler wakes up the device on the necessary time as needed by the user, but uses smart scheduling.

Categories

Resources