I am using the workmanager to run a service in the background, minimum interval from documentation is 15 minutes, but i would like to have an interval of 2 minutes.
I tried this code, it is working while debugging from android studio and runs properly every two minutes, but once i unplug the cable, or deploy it directly via apk, it runs only one time and stops.
In main activity onCreate/onResume :
OneTimeWorkRequest oneTimeRequest = new OneTimeWorkRequest.Builder(CallAndSmsWork.class).addTag(TAG).setInitialDelay(15, TimeUnit.SECONDS).build();
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork("CallnSms", ExistingWorkPolicy.REPLACE, oneTimeRequest);
In the end of DoWork method of CallAndSmsWork service :
OneTimeWorkRequest oneTimeRequest = new OneTimeWorkRequest.Builder(CallAndSmsWork.class).setInitialDelay(120, TimeUnit.SECONDS).addTag(TAG).build();
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork("CallnSms", ExistingWorkPolicy.REPLACE, oneTimeRequest);
Is there anything i did wrong, or any other option to have the service running properly every two minutes ?
P.S.: i am on android 10 , one plus 6t
For a 2 min interval you could use a periodic runnable, something like:
private val runnable = object : Runnable {
val periodInMillis = 2 * 60 * 1000L
override fun run() {
Log.d(DEBUG_TAG,"Runnable scheduled")
// Do stuff
mHandler?.postDelayed(this,periodInMillis)
}
}
And send it to a handler thread:
//Threading madness
private var mThread: HandlerThread? = null
private var mHandler: Handler? = null
Than you can initialize:
//Prepare Thread
if (mThread == null) {
mThread = HandlerThread("HeartBeatThread", Process.THREAD_PRIORITY_BACKGROUND)
mThread!!.start()
}
if (mHandler == null) mHandler =
Handler(mThread!!.looper) //Blocks until looper is prepared, which is fairly quick
mHandler!!.post(runnable)
In this way you have a task that enqueues itself and runs every 2 minutes. The issue with that could be Doze mode and something to do with the way that Android handles battery saver mode. For that you can pick up a lock I guess.
Related
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.
what is the best practice to call method each 1 minute even the app in the background(android),
I am using Handler(Looper.getMainLooper()) to do it in forground but I need to call method even the app in the background
private fun runScheduleMethods() {
mainHandler.post(object : Runnable {
override fun run() {
viewModel.loadCurrentLocation(locationRequest)
mainHandler.postDelayed(this, 1000 * 60)
}
})
}
use foreground service with notification(because if notification is active then android OS will not kill it)
here is blog
good luck..!
https://medium.com/#ramkumarv3/foreground-service-with-notification-channel-ac6697c8a6d1
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);
I have periodic worker, which executes for every 15 min. The worker uploads the data files into server. But, when the current worker taking time to upload and still running, after 10 min, the work manager invokes the worker after 10 min which causing an issues as both of them trying to access the db and overriding each other. Is there any way to stop the worker being invoked after 10 min when current worker is still running.
I am using the 2.3.0-alpha03 version.
Can anyone please help?
I would try putting the workers on the same background thread, so the work request gets queued one after the other.
WorkManager.initialize(
context,
new Configuration.Builder()
.setExecutor(Executors.newFixedThreadPool(1))
.build());
You can read more about threading and WorkManager here
If constraints prevent the work request from running you should be able to use an equal constraint on the periodic worker, set the equal state to false before the oneTimeWorker runs and true after the work finishes.
private boolean RUN_WORKER = true;
Constraints constraints = new Constrains.Builder()
.equals(RUN_WORKER)
.build
PeriodicWorkRequest request =
new PeriodicWorkRequest.Builder(myPeriodicWorker.class, 10, TimeUnit.MINUTES)
.setConstraints(constraints)
.build();
WorkManager.getInstance(myContext)
.enqueue(request);
The method that runs OneTimeWorker
RUN_WORKER = false;
OneTimeWorkRequest request = new OneTimeWorkResquest =
new OneTimeWorkRequest.Builder(OneTimeWorker.class)
.build();
WorkManager.getInstance(mContext).getWorkInfoByIdLiveData(request.getId())
.observe(lifecycleOwner, new Observer<WorkInfo>() {
#Override
public void onChanged(#Nullable WorkInfo workInfo) {
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
displayMessage("Work finished!")
}
RUN_WORKER = true;
}
});
The system stops the work and restart it if the execution time is bigger than 10 minutes
https://developer.android.com/topic/libraries/architecture/workmanager/how-to/managing-work
To avoid the stop and restart, the worker class has a support for ForegroundAsync services, which have worked for me:
https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running
I want to schedule 2 different periodic task, for this I am using work manager.
1. Upload file to server - after every 20 min
2. Call API in - after every 15 min
For API call (Daily operation) following is my code:
PeriodicWorkRequest.Builder dailyWorkBuilder =
new PeriodicWorkRequest.Builder(CheckAccount.class, 15,
TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build());
PeriodicWorkRequest DailyJob = dailyWorkBuilder.build();
WorkManager.getInstance().enqueueUniquePeriodicWork("DailyJob", ExistingPeriodicWorkPolicy.REPLACE,DailyJob);
To upload file I am using following code:
PeriodicWorkRequest.Builder wifiWorkBuilder =
new PeriodicWorkRequest.Builder(FileUpload.class, 20,
TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build());
PeriodicWorkRequest wifiWork = wifiWorkBuilder.build();
WorkManager.getInstance().enqueueUniquePeriodicWork("wifiJob", ExistingPeriodicWorkPolicy.REPLACE,wifiWork);
Now here I am facing 2 difficulties:
1. If I open app - Lets say after open app my Activity is HomeActivity in this activity I had written dailyWorkBuilder code (which executes after every 20 min) will get called every time I open app. If I did not open app it will get called after 20 min but before 20 min I open app it gets called. So here I want to check if task is not running then only it should execute not every time when I open app
It also call wifiWorkBuilder (task which execute after every 15 min)it also get called every time when I open app. These 2 task are totally different and not depend on each other, but still if one task get called other will also get called before there specified time.
Whats wrong in above code. Any suggestion will be appreciated. Thanks
I had same problem some days ago. I managed that by-
Schedule work if Work-Manager is not scheduled already.
In your MainActivity where you set work.
if(isWorkScheduled("DailyJob")){
// now schedule DailyJob
}
I also asked a question and answered after getting solution.
private boolean isWorkScheduled(String tag) {
WorkManager instance = WorkManager.getInstance();
if (instance == null) return false;
LiveData<List<WorkStatus>> statuses = instance.getStatusesByTag(tag);
return statuses.getValue() != null && statuses.getValue().size() > 0;
}
This is up to you if you consider below method. It will return true
when some of its task is RUNNING or ENQUEUED.
private boolean isWorkScheduled(String tag) {
WorkManager instance = WorkManager.getInstance();
if (instance == null) return false;
LiveData<List<WorkStatus>> statuses = instance.getStatusesByTag(tag);
if (statuses.getValue() == null) return false;
boolean running = false;
for (WorkStatus workStatus : statuses.getValue()) {
running = workStatus.getState() == State.RUNNING | workStatus.getState() == State.ENQUEUED;
}
return running;
}
Suggestion
Always null check WorkManager object, because it can be null in some cases. You can see doc.
* #return The singleton instance of {#link WorkManager}; this may be {#code null} in unusual
* circumstances where you have disabled automatic initialization and have failed to
* manually call {#link #initialize(Context, Configuration)}.
Use ExistingPeriodicWorkPolicy.KEEP instead of ExistingPeriodicWorkPolicy.REPLACE, if your every task is important, work manager will auto start next work after its completion.