setOverrideDeadline() doesn't seem to have an effect - android

I've got an app widget where I want to maintain up-to-date information in a line of text that says "x minutes ago", referring to the age of the bitmap below it. I wrote the code as follows:
private fun scheduleUpdateAge(context: Context) {
val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val resultCode = jobScheduler.schedule(
JobInfo.Builder(UPDATE_AGE_JOB_ID, ComponentName(context, UpdateAgeService::class.java))
.setMinimumLatency(MINUTE_IN_MILLIS)
.setOverrideDeadline(2 * MINUTE_IN_MILLIS)
.build())
reportScheduleResult("update age", MINUTE_IN_MILLIS, resultCode)
}
In the last line i check the result of scheduling, it's always successful.
In the job service I update the remote views and then reschedule the job:
class UpdateAgeService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
MyLog.i("UpdateAgeService start job")
updateRemoteViews(applicationContext)
jobFinished(params, false)
scheduleUpdateAge(applicationContext)
return false
}
override fun onStopJob(params: JobParameters): Boolean {
MyLog.i("UpdateAgeService stop job")
return true
}
}
However, I keep observing that Android has not invoked my job for much longer periods, above ten minutes. At that time the other scheduled job, the one that refreshes the bitmap, kicks in and resets the age text as well.
The documentation on setOverrideDeadline states
Set deadline which is the maximum scheduling latency. The job will be run by this deadline even if other requirements are not met.
but Android seems to violate it.
Am I missing something here?
I'm aware that the "natural" way to schedule periodic jobs is setPeriodic(), but setPeriodic() wasn't invoking my job either and its contracts clearly states
You have no control over when within this interval this job will be executed, only the guarantee that it will be executed at most once within this interval.
So missing the schedule is "by the book" for that case.

Related

How to make a guaranteed real-time stopwatch Android

I'm trying to use a foreground service to implement a count up timer feature in my app, essentially a stopwatch. Below is the general idea of what I'm doing, which works well when the app is in the foreground, but not when the screen is off.
This is because handler.postDelayed() is not guaranteed to be real time, and when the screen is off, it's far from it.
class TimerService: Service(), CoroutineScope {
private var currentTime: Int = 0
private val handler = Handler(Looper.getMainLooper())
private var runnable: Runnable = object : Runnable {
override fun run() {
if (timerState == TimerState.START) {
currentTime++
}
broadcastUpdate()
// Repeat every 1 second
handler.postDelayed(this, 1000)
}
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
// Call this to start the timer
private fun startCoroutineTimer() {
launch(coroutineContext) {
handler.post(runnable)
}
}
// Rest of the service class
...
}
My question is, what is the cannonical way to do something like this? I need to be able to guarantee, using a foreground service, that I can make an accurate stopwatch that is able to start, pause, resume, and stop.
I have the state broadcasting and everything else worked out already, but the actual timer part is where I'm stuck, and I can't seem to find a simple and efficient solution that is guaranteed to be accurate.
First start with a different OS. There's an entire class of OSes called RTOS (real time OS). Linux (and thus Android) are not one. If you actually need realtime, linux is not an acceptable solution.
But let's say you don't actually need realtime, you just want higher accuracy. There's a few ways to easily improve your code.
The biggest thing is that your current code assumes the timer will go off once per second. Don't do that. Instead, keep track of the time when the timer starts. Each time the timer goes off, get the current time. The time elapsed is the delta. That way, any accumulated inaccuracies get wiped away each tick. That will also fix a lot of your screen off case, as the first update after screen on will update to the correct time elapsed.
private var timeStarted : Long = 0
private var timeElapsed : Long = 0
private var runnable: Runnable = object : Runnable {
override fun run() {
if (timerState == TimerState.START) {
timeElapsed = System.getCurrentTimeMillis() - timeStarted
}
broadcastUpdate()
// Repeat every 1 second
handler.postDelayed(this, 1000)
}
}
private fun startCoroutineTimer() {
timeStarted = System.getCurrentTimeMillis()
handler.post(runnable)
}
Also notice you don't need a coroutine to post to a handler. The handler takes care of multithreading, launching a coroutine there provides no value.

Android: Periodic work manager that repeats on certain days or dates

I want to build a work manager that sends users a notification at certain times. The repeating criterias are the following:
a notification every week, on TUESDAY at 14:30
a notification every month, o the 15th of that month, having a repeat count of only 6 times
How can I achieve this?
There is no way to set an exact time so no periodic work is possible. You have to recalculate every time:
Do something like this by using "setInitialDelay":
fun setupWork(){
if (isCountLimitReached()) {
return
}
val workRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(getNextInterval(),TimeUnit.MILLISECONDS)
.build()
updateCount()
WorkManager
.getInstance(context)
// Make sure you have only one work like that.
.enqueueUniqueWork(
"MY_WORK_UNIQUE_NAME",
// If there is existing work like this - replace it.
ExistingWorkPolicy.REPLACE,
workRequest
)
}
fun getNextInterval(): Long {
TODO("Get milliseconds from 'execution date' and subtract 'now'")
}
private fun updateCount(){
TODO("Use SharedPreferences or something like this")
}
private fun isCountLimitReached(): Boolean {
TODO("Use SharedPreferences or something like this")
}

Execute function every full minute android/kotlin

Is this possible to execute function in android every minute starting from full minute, so eg. when I run my app at 8:30:12 the first function call will be at 8:31:00 and next at 8:32:00 and so on.
You have to first calculate the time required to become exact minute that is starting the task at 8:30:12, first call should happen at 8:31:00.
So for that you can pull current time using System.currentTimeMillis() and then ceil it's minute value to the nearest integer,
// import kotlin.math.ceil
val firstCallTime = ceil(System.currentTimeMillis() / 60_000.0).toLong() * 60_000
Then you can use coroutines to handle your task that will use a CommonPool to get reusable threads from and won't require to you to create threads.
// scope can be anything, for instance you want to call the function
// only when client is using your application then use viewModelScope provided,
// otherwise your own scope like `val scope = CoroutineScope(Dispatchers.Main)`
// Or use Dispatchers.Default for CPU intensive tasks
val parentJob = scope.launch {
// suspend till first minute comes after some seconds
delay(firstCallTime - System.currentTimeMillis())
while (true) {
launch {
yourFunctionToBeCalled()
}
delay(60_000) // 1 minute delay (suspending)
}
}
// If you ever want your job to cancel just do a cancel,
// the function delay suspends and so cancellable without interrupting the threads
// parentJob.cancel()
Use PeriodicWorkRequest along with WorkManager class in android. It goes like this:
val work = PeriodicWorkRequestBuilder<MyWorker>(60, TimeUnit.SECONDS)
.build();
val workManager = WorkManager.getInstance(context);
workManager.enqueuePeriodicWork(work);

Android LiveData: How to avoid data transfer from the database when an app is re-opened from the background?

My Android app is listening on a Firebase Database. Whenever my activity becomes inactive, I stop the listener, and when the activity becomes active again, I restart listening. This is done using LiveData and the 'onActive' and 'onInactive' methods as below:
#Override
protected void onActive() {
Log.d(LOG_TAG, "onActive");
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
Log.d(LOG_TAG, "onInactive");
query.removeEventListener(listener);
}
Using the debugger, I have noticed that when I press the back button and close the app, the onInactive method gets called, and the app goes in the background. When I reopen the app, by picking it among the apps that are in the background, the onActive method gets called. However, in this case, all my data is reread from the database which will consume data bandwidth.
My question is:
What is the best way to avoid the re-reading of the data every time the app is coming back from the background?
Thanks
What you will need to do is set a "timeout" of sorts on your LiveData so that it defers becoming inactive for as much delay as you deem appropriate.
I implemented a "LingeringLiveData" superclass for exactly this situation. You can see it in a project of mine on GitHub. It's written in Kotlin, but you should be able to port it to Java without much trouble.
Subclasses need to provide implementations for startLingering and stopLingering that mirror what you would normally do in onActive and onInactive.
Basically, it sets up a timer to delay a call to endLingering after onInactive has been invoked, but only if onActive isn't invoked before that time expires. This lets you app stop and start without losing the listener.
abstract class LingeringLiveData<T> : LiveData<T>() {
companion object {
private const val STOP_LISTENING_DELAY = 2000L
}
// To be fully unit-testable, this code should use an abstraction for
// future work scheduling rather than Handler itself.
private val handler = Handler()
private var stopLingeringPending = false
private val stopLingeringRunnable = StopLingeringRunnable()
/**
* Called during onActive, but only if it was not previously in a
* "lingering" state.
*/
abstract fun beginLingering()
/**
* Called two seconds after onInactive, but only if onActive is not
* called during that time.
*/
abstract fun endLingering()
#CallSuper
override fun onActive() {
if (stopLingeringPending) {
handler.removeCallbacks(stopLingeringRunnable)
}
else {
beginLingering()
}
stopLingeringPending = false
}
#CallSuper
override fun onInactive() {
handler.postDelayed(stopLingeringRunnable, STOP_LISTENING_DELAY)
stopLingeringPending = true
}
private inner class StopLingeringRunnable : Runnable {
override fun run() {
if (stopLingeringPending) {
stopLingeringPending = false
endLingering()
}
}
}
}
Jetpack LiveData KTX now also offers a liveData convenience constructor that accepts a similar timeout parameter and runs code in a coroutine. You won't be able to use at all from Java, but it's nice to know about.
The easiest way to decrease data downloads in this case is to enable disk persistence in the Firebase client.
With disk persistence enabled, the Firebase client will write any data it gets from the server to a local disk cache, cleaning up older data if the cache gets too big.
When the client is restarted, the client will read the data from disk first, and then only request updates from the server using a so-called delta sync. While this delta sync still transfers data, it should typically be significantly less than the total data at the location you listen on.

Android JobScheduler onStartJob called multiple times

The JobScheduler calls onStartJob() multiple times, although the job finished. Everything works fine, if I schedule one single job and wait until it has finished. However, if I schedule two or more jobs with different IDs at the same time, then onStartJob() is called again after invoking jobFinished().
For example I schedule job 1 and job 2 with exactly the same parameters except the ID, then the order is:
onStartJob() for job 1 and job 2
Both jobs finish, so jobFinished() is invoked for both of them
After that onStartJob() is called again for both jobs with the same ID
My job is very basic and not complicated.
public class MyJobService extends JobService {
#Override
public boolean onStartJob(final JobParameters params) {
new Thread(new Runnable() {
#Override
public void run() {
try {
// do something
} finally {
// do not reschedule
jobFinished(params, false);
}
}
}).start();
// yes, job running in the background
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
// mark my background task as stopped
// do not reschedule
return false;
}
}
I schedule the jobs like this
JobInfo jobInfo = createBaseBuilder(request)
.setMinimumLatency(2_000L)
.setOverrideDeadline(4_000L)
.setRequiresCharging(false)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
int scheduleResult = mJobScheduler.schedule(jobInfo);
// is always success
I don't know what's wrong.
I guess it's caused by the pending Job, so I call mJobScheduler.cancelAll() after the service started, problem resolved.
I think this relates to the Android bug reported here, which has apparently been fixed for Android N but will be present in earlier versions.
The OP is using a setOverrideDeadline(). My understanding of the issue reported in the linked post above is that if the job is running when the override deadline fires, it causes the job to be scheduled to run again.
So the advice is to ensure that the override fires either before the job is scheduled (not sure how that is achieved) or after it has finished. Neither seems particularly satisfactory, but at least it seems to have been fixed in Android N.
this is the problem in android lollypop and Marshmallow. It is fixed in Nougat as explained by Matthew Williams here

Categories

Resources