I want to run some task (i.e. get my web site news page) periodically (once a week/ a day), even if my application is closed. Is it possible?
Yes it is, you need to look at the AlarmManager to setup a reoccurring "Alarm". This is better for battery life on the device, as unlike a service it does not run constantly in the background. The Alarm triggers a broadcast receiver which will execute your custom code.
As a final note - there are enum values for the timing of the Alarm including daily, half daily and many more although you can just set an actual value.
A good example can be found in the follow SO post:
Alarm Manager Example
Update
Newer features have been added to Android. If you are reading this then I would advise you now look into GcmNetworkManager. This optimises battery life and works pre-lollipop. For Lollipop onwards you can use JobScheduler. I would advise using these classes over the AlarmManager.
I think the best fit is GcmNetworkManager. Basically it has everything you need from AlarmManager plus persistence, so job can proceed executing after reboot.
Example:
PeriodicTask task = new PeriodicTask.Builder()
.setService(MyTaskService.class)
.setTag(TASK_TAG_PERIODIC)
.setPeriod(5L)
.build();
mGcmNetworkManager.schedule(task);
As an alternative I'm comparing the current week:
Calendar cal = Calendar.getInstance();
int currentWeekOfYear = cal.get(Calendar.WEEK_OF_YEAR);
SharedPreferences sharedPreferences= this.getSharedPreferences("appInfo", 0);
int weekOfYear = sharedPreferences.getInt("weekOfYear", 0);
if(weekOfYear != currentWeekOfYear){
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt("weekOfYear", currentWeekOfYear);
editor.commit();
// Your once a week code here
}
I'm not advocating this is better than the Alarm solution. I'm just showing a different approach.
Related
I have an app that should show a notification every 2 hours and should stop if user has already acted upon the notif. Since background services are history now, I thought of using WorkManager ("android.arch.work:work-runtime:1.0.0-beta01") for the same.
My problem is that although the work manager is successfully showing the notifications when app is running, but it won't show notification consistently in the following cases(I reduced the time span from 2 hours to 2 minutes to check the consistency):
when app is killed from the background.
device is in screen off.
state device is in unplugged state(i.e not charging).
By consistency , i mean that the notifications show at least once in the given time span. for 2 minutes time span, the freq of notifications went from once every 4 minutes to completely not show any notification at all. for 2 hours timespan( the timespan that i actually want), its been 4 hours and i haven't got a single notification. Here is the Code i am using for calling WorkManger:
public class CurrentStreakActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
setDailyNotifier();
...
}
private void setDailyNotifier() {
Constraints.Builder constraintsBuilder = new Constraints.Builder();
constraintsBuilder.setRequiresBatteryNotLow(false);
constraintsBuilder.setRequiredNetworkType(NetworkType.NOT_REQUIRED);
constraintsBuilder.setRequiresCharging(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
constraintsBuilder.setRequiresDeviceIdle(false);
}
Constraints constraints =constraintsBuilder.build();
PeriodicWorkRequest.Builder builder = new PeriodicWorkRequest
.Builder(PeriodicNotifyWorker.class, 2, TimeUnit.HOURS);
builder.setConstraints(constraints);
WorkRequest request = builder.build();
WorkManager.getInstance().enqueue(request);
}
....
}
Here is the worker class(i can post showNotif(..) and setNotificationChannel(...) too if they might be erroronous):
public class PeriodicNotifyWorker extends Worker {
private static final String TAG = "PeriodicNotifyWorker";
public PeriodicNotifyWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
Log.e(TAG, "PeriodicNotifyWorker: constructor called" );
}
#NonNull
#Override
public Result doWork() {
// Log.e(TAG, "doWork: called" );
SharedPreferences sp =
getApplicationContext().getSharedPreferences(Statics.SP_FILENAME, Context.MODE_PRIVATE);
String lastcheckin = sp.getString(Statics.LAST_CHECKIN_DATE_str, Statics.getToday());
// Log.e(TAG, "doWork: checking shared preferences for last checkin:"+lastcheckin );
if (Statics.compareDateStrings(lastcheckin, Statics.getToday()) == -1) {
Log.e(TAG, "doWork: last checkin is smaller than today's date, so calling creating notification" );
return createNotificationWithButtons(sp);
}
else {
Log.e(TAG, "doWork: last checkin is bigger than today's date, so no need for notif" );
return Result.success();
}
}
private Result createNotificationWithButtons(SharedPreferences sp) {
NotificationManager manager =
(NotificationManager) getApplicationContext().getSystemService((NOTIFICATION_SERVICE));
String channel_ID = "100DaysOfCode_ID";
if (manager != null) {
setNotificationChannel(manager,channel_ID);
showNotif(manager, channel_ID, sp);
return Result.success();
}
else {
return Result.failure();
}
I am using a xiaomi miA2 androidOne device with Android Pie(SDK 28). There are a few other things that are troubling me:
What can i possibly do to know if my WorkManager is running? Other that just wait for 2 hours and hope for a notification. I actually tried something like that, keeping my phone connected to pc and checking android studio's logcat every now and then. It DOES run all the logs when the worker is actually called, but i don't think that's a correct way to test it, or is it?
In the above Code, the setDailyNotifier() is called from the onCreate() every time the app is opened. Isn't it Wrong? shouldn't there be some unique id for every WorkRequest and a check function like WorkManger.isRequestRunning(request.getID) which could let us check if a worker is already on the given task??If this was a case of AsyncTask, then boy we would have a mess.
I have also checked #commonsware's answer here about wakelock when screen is off, but i remember that work manager does use alarm manager in the inside when available. So what am I missing here?
Few comments:
WorkManager has a minimum periodic interval of 15minutes and does not guarantee to execute your task at a precise time. You can read more about this on this blog.
All the usual background limitation you've on newer Android releases are still relevant when you use WorkManager to schedule your tasks. WorkManager guarantees that the task are executed even if the app is killed or the device is restated, but it cannot guarantee the exact execution.
There's one note about the tasks being rescheduled when your app is killed. Some OEM have done modification to the OS and the Launcher app that prevents WorkManager to be able to accomplish these functionality.
Here's the issuetracker discussion:
Yes, it's true even when the phone is a Chinese phone.
The only issue that we have come across is the case where some Chinese OEMs treat swipe to dismiss from Recents as a force stop. When that happens, WorkManager will reschedule all pending jobs, next time the app starts up. Given that this is a CDD violation, there is not much more that WorkManager can do given its a client library.
To add to this, if a device manufacturer has decided to modify stock Android to force-stop the app, WorkManager will stop working (as will JobScheduler, alarms, broadcast receivers, etc.). There is no way to work around this. Some device manufacturers do this, unfortunately, so in those cases WorkManager will stop working until the next time the app is launched.
As of now , i have this app installed for last 8 days and i can confirm that the code is correct and app is working fine. as said by pfmaggi , the minimum time interval for work manager to schedule the work is 15 minutes, so there is a less chance that the WorkManager would have worked as expected in my testing conditions( of 2 minutes ) . Here are some of my other observations:
Like I said in the question that i was unable to recieve a notification for 4 hours even though i have passed the repeat interval as 2 hours. This was because of Flex Time. I passed in the flex time of 15 minutes and now it shows notifications between correct time interval. so i will be marking pfmaggi's answer as correct.
The problem of repeated work request can be solved by replacing WorkManager.getInstance().enqueue(request) with WorkManager.getInstance().enqueueUniqueWork(request,..)
I was still unable to find a way to test the work manager in the way i have described.
I have an Android application which needs to send backup data every midnight to my cloud server. For this I need to schedule the job to occur every 24 hours without fail. I have read about AlarmManager and JobScheduler class which can schedule your jobs effectively.
In my case I am using JobScheduler class and here is my scheduleJob() method which sets a job to schedule after every 86400000 milliseconds (ie. one day).
#RequiresApi(api =Build.VERSION_CODES.LOLLIPOP)
public void scheduleJob(View v){
JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(this,JobSchedule.class));
builder.setPersisted(true);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
builder.setPeriodic(86400000);
builder.setRequiresCharging(false);
builder.setRequiresDeviceIdle(false);
builder.setBackoffCriteria(10000,JobInfo.BACKOFF_POLICY_EXPONENTIAL);
int test = jobScheduler.schedule(builder.build());
if(test <= 0){
//sth went wrong
}
}
My question stands is, from where and how should I implement jobSchedule() so that I can get the script to run exactly at 12 midnight.
Also, if I call scheduleJob method from, say, Android's Activity XYZ's onCreate() method, then everytime the Activity XYZ is called, it's override method onCreate() will be called and an additional job will be scheduled. So what should I do and from where should I schedule the job such that only one job is scheduled for the entire lifetime of the application?
To summarize using an example, Whatsapp backs up its data every day at 2 am. Even if I have turned off my net connection, it backs up my data as soon as I come online. I want to implement similar functionality in my application.
You can use SharedPreference to implement one-time execution. Something like this
SharedPreferences wmbPreference = PreferenceManager.getDefaultSharedPreferences(this);
boolean isFirstRun = wmbPreference.getBoolean("FIRSTRUN", true);
if (isFirstRun)
{
// Code to run once
SharedPreferences.Editor editor = wmbPreference.edit();
editor.putBoolean("FIRSTRUN", false);
editor.commit();
}
According to the documentation JobInfo Builder, setRequieresCharging and setRequieresDeviceIdle are set false by default, so you dont need to set them up false.
I'm using Evernote's JobScheduler. The library provides a way to set a periodic job, however that has a minimum interval of 15 minutes between repetitions.
I want to be able to send a user's location to the server every few minutes (maximum 3, minimum 2). Is it technically OK to schedule your old job again and again after the job is done? Something like this:
protected Result onRunJob(Params params)
{
// my actual job runs here
schedulePeriodicJob();
return Result.SUCCESS;
}
private void schedulePeriodicJob()
{
jobId = new JobRequest.Builder(ScheduleJob.TAG)
.setExact(TimeUnit.MINUTES.toMillis(2))
.setBackoffCriteria(TimeUnit.MINUTES.toMillis(1), JobRequest.BackoffPolicy.LINEAR)
.build()
.schedule();
}
Or should I simply use a foreground service to achieve this?
Evernote job scheduler is based upon JobScheduler, GcmNetworkManager or AlarmManager depending on the context/device/playservices version. Whatever the implementation, APIs exposes the following method:
public JobRequest.Builder setPeriodic(long intervalMs, long flexMs)
I would use this one instead of "manually" rescheduling job. I also don't think your setBackoffCriteria call would not work without setting recurring job at APIs level.
I was using GCM network manager, but then I heard that Firebase JobDispatcher includes GCM plus other features so I'm trying to use that.
I have successfully programmed a periodic task and it works fine, but the problem is that I need the period to change and not be fixed from the beginning.
The reason for that is, I'm using an activity recognition service and I want the next time the JobDispatcher executes the periodic task to be based on the detected current activity. For example if you're walking, the next time the task is triggered is after 30 minutes, while if you're in a car then the period is 5 minutes (mainly because if you're in a car it's more likely that your phone will provide different location values in a short while compared to when you're on foot).
This is the way I program the periodic task, as you can see I'm setting a fixed value, I want to know if the service that is triggered by this task can provide a feedback that'll change the period of the task.
final Builder builder = jobDispatcher.newJobBuilder()
.setTag(form.tag.get())
.setRecurring(form.recurring.get())
.setLifetime(form.persistent.get() ? Lifetime.FOREVER : Lifetime.UNTIL_NEXT_BOOT)
.setService(DemoJobService.class)
.setTrigger(Trigger.executionWindow(
form.getWinStartSeconds(), form.getWinEndSeconds()))
.setReplaceCurrent(form.replaceCurrent.get())
.setRetryStrategy(jobDispatcher.newRetryStrategy(
form.retryStrategy.get(),
form.getInitialBackoffSeconds(),
form.getMaximumBackoffSeconds()));
if (form.constrainDeviceCharging.get()) {
builder.addConstraint(Constraint.DEVICE_CHARGING);
}
if (form.constrainOnAnyNetwork.get()) {
builder.addConstraint(Constraint.ON_ANY_NETWORK);
}
if (form.constrainOnUnmeteredNetwork.get()) {
builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);
}
To do this you'd need to have your onStartJob in DemoJobService.class cancel itself using your tag that you've saved in SharedPrefs / elsewhere (form.tag.get() in your example):
FirebaseJobDispatcher(GooglePlayDriver(context)).cancel(yourTag)
It can then reschedule the job with the updated parameters. Unfortunately there's currently no way to edit jobs, they must be cancelled and recreated!
Part of my question, how I can set up a job with less then 15 minutes interval in "Nougat", was answerd by "blizzard" in his answer here:
Job Scheduler not running on Android N
He explained the problem and suggested to use the following workaround:
JobInfo jobInfo;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
.setMinimumLatency(REFRESH_INTERVAL)
.setExtras(bundle).build();
} else {
jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
.setPeriodic(REFRESH_INTERVAL)
.setExtras(bundle).build();
}
However, using the suggested
.setMinimumLatency(REFRESH_INTERVAL)
just starts the job once;
but how do I get it periodic with a period of around 30 seconds on an android nougat device (not using handler or alarm manager)?
If someone is still trying to overcome the situation,
Here is a workaround for >= Android N (If you want to set the periodic job lower than 15 minutes)
Check that only setMinimumLatency is used. Also, If you are running a task that takes a long time, the next job will be scheduled at, Current JOB Finish time + PROVIDED_TIME_INTERVAL
.SetPeriodic(long millis) works well for API Level below Android N
#Override
public boolean onStartJob(final JobParameters jobParameters) {
Log.d(TAG,"Running service now..");
//Small or Long Running task with callback
//Reschedule the Service before calling job finished
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
scheduleRefresh();
//Call Job Finished
jobFinished(jobParameters, false );
return true;
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
private void scheduleRefresh() {
JobScheduler mJobScheduler = (JobScheduler)getApplicationContext()
.getSystemService(JOB_SCHEDULER_SERVICE);
JobInfo.Builder mJobBuilder =
new JobInfo.Builder(YOUR_JOB_ID,
new ComponentName(getPackageName(),
GetSessionService.class.getName()));
/* For Android N and Upper Versions */
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mJobBuilder
.setMinimumLatency(60*1000) //YOUR_TIME_INTERVAL
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
UPDATE:
If you are considering your repeating job to run while in Doze Mode and thinking about JobScheduler, FYI: JobSchedulers are not allowed to run in Doze mode.
I have not discussed about the Dozing because we were talking about JobScheduler. Thanks, #Elletlar, for pointing out that some may think that it will run even when the app is in doze mode which is not the case.
For doze mode, AlarmManager still gives the best solution. You can use setExactAndAllowWhileIdle() if you want to run your periodic job at exact time period or use setAndAllowWhileIdle() if you're flexible.
You can also user setAlarmClock() as device always comes out from doze mode for alarm clock and returns to doze mode again. Another way is to use FCM.
Reference: Doze Restrictions
https://developer.android.com/training/monitoring-device-state/doze-standby
I struggled with same thing when I wanted to setup Job for refresh small part of data. I found out that solution for this problem may be setting up Job one more time with the same ID after i calledjobFinished(JobParameters, boolean). I think it should work every time on main thread.
My function to setup Job looks like this:
JobInfo generateRefreshTokenJobInfo(long periodTime){
JobInfo.Builder jobBuilder = new JobInfo.Builder(1L, new ComponentName(mContext, JobService.class));
jobBuilder.setMinimumLatency(periodTime);
jobBuilder.setOverrideDeadline((long)(periodTime * 1.05));
jobBuilder.setRequiresDeviceIdle(false);
return jobBuilder.build();
}
When I finish my work after first call of Job i call in main thread
jobFinished(mJobParameters, true);
registerRefreshJob(5*60*1000L);
This will reschedule my Job one more time for the same amount of time on the same id. When device is in idle state you still have to take under consideration lack of wake locks in doze so your job may not be started so often as you wish. It is mentioned in https://developer.android.com/about/versions/nougat/android-7.0-changes.html
If the device is stationary for a certain time after entering Doze, the system applies the rest of the Doze restrictions to PowerManager.WakeLock, AlarmManager alarms, GPS, and Wi-Fi scans. Regardless of whether some or all Doze restrictions are being applied, the system wakes the device for brief maintenance windows, during which applications are allowed network access and can execute any deferred jobs/syncs.