In my Android app I need to run a background service every time the device is plugged in and idle (it should start after the user connects his device to the charger and end when he disconnects it).
What I want is a similar thing to listening for the ACTION_POWER_CONNECTED broadcast, but targeting Oreo this broadcast doesn't get sent.
I tried Android-Job from Evernote because it doesn't require Google Play Services, as Firebase JobDispatcher does.
new JobRequest.Builder(DemoSyncJob.TAG)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.build()
.schedule();
The problem is that I don't want to have to schedule the job every time. Instead, I want to schedule the job once and have it run every time the user connects his device to the charger.
How can it be done?
Thank you.
Because it's fine for me to run the job only once a day, but only when the device is plugged in, I have solved the problem like this:
public class ProcessPhotosJob extends Job {
public static final String TAG = "process_photos_job";
#NonNull
#Override
protected Result onRunJob(Params params) {
new ProcessPhotos().execute(getContext());
scheduleForTomorrow();
return Result.SUCCESS;
}
public static void scheduleNow() {
schedule(1);
}
public static void scheduleForTomorrow() {
schedule(TimeUnit.DAYS.toMillis(1));
}
private static void schedule(long delayMillis) {
if (!JobManager.instance().getAllJobRequestsForTag(TAG).isEmpty()) {
// Job already scheduled
return;
}
new JobRequest.Builder(ProcessPhotosJob.TAG)
.setExecutionWindow(delayMillis, delayMillis + TimeUnit.DAYS.toMillis(1))
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.setRequirementsEnforced(true)
.setUpdateCurrent(true)
.build()
.schedule();
}
}
Hope it helps someone.
Related
Before API 31, we are using the following guideline, to implement a delayed started long running task, when user "quit" the app.
https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running#long-running-java
public class XXXApplication extends MultiDexApplication implements DefaultLifecycleObserver {
#Override
public void onPause(LifecycleOwner owner) {
startCloudWorker();
}
}
public static void startCloudWorker() {
OneTimeWorkRequest oneTimeWorkRequest =
new OneTimeWorkRequest.Builder(CloudWorker.class)
.setInitialDelay(..., TimeUnit.MILLISECONDS)
...
.build();
WorkManager workManager = getWorkManager();
workManager.enqueue(oneTimeWorkRequest);
}
public class CloudWorker extends Worker {
public CloudWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
final ForegroundInfo foregroundInfo = createForegroundInfo(...)
setForegroundAsync(foregroundInfo);
// Some busy job here...
return Result.success();
}
}
Due to restriction imposed by new API 31
Apps that target Android 12 (API level 31) or higher can't start
foreground services while running in the background, except for a few
special cases. If an app tries to start a foreground service while the
app is running in the background, and the foreground service doesn't
satisfy one of the exceptional cases, the system throws a
ForegroundServiceStartNotAllowedException.
https://developer.android.com/guide/components/foreground-services#background-start-restrictions
I thought using setExpedited might be a good workaround to overcome such limitation.
How does setExpedited work and is it as good&reliable as a foreground service?
However, when I start to adopt the new requirement
public static void startCloudWorker() {
OneTimeWorkRequest oneTimeWorkRequest =
new OneTimeWorkRequest.Builder(CloudWorker.class)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setInitialDelay(..., TimeUnit.MILLISECONDS)
...
.build();
WorkManager workManager = getWorkManager();
workManager.enqueue(oneTimeWorkRequest);
}
I am getting
java.lang.IllegalArgumentException: Expedited jobs cannot be delayed
I was wondering, what is the right way, to have a delayed started long running task when app is in background for API 31?
I'm looking to make an application that runs in the background, logging location data without the user actually having to have the application in the foreground but at the same time doesn't use too much battery.
I originally thought of setting a BroadcastReceiver for BOOT_COMPLETED and run a service which uses a Significant Motion sensor to log location data whenever the it fired off, but ever since Oreo, there are alot of limitations on background services.
What is the best way to do this?
You can use JobService it's efficient in terms of battery and modern way to perform the task in the background.
public class YourJobService extends JobService {
#Override
public boolean onStartJob(JobParameters params) {
if (!Utility.isServiceRunning(GetAlertService.class, getApplicationContext())) {
startService(new Intent(getApplicationContext(), GetAlertService.class));
}
jobFinished(params, false);
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
return true;
}
}
and you can configure it the way you want it like this
ComponentName getAlertJobComponent = new ComponentName(context.getPackageName(), YourJobService.class.getName());
JobInfo.Builder getAlertbuilder = new JobInfo.Builder(Constants.getAlertJobid, getAlertJobComponent);
getAlertbuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // require unmetered network
getAlertbuilder.setRequiresDeviceIdle(true); // device should be idle
getAlertbuilder.setPeriodic(10 * 1000);
getAlertbuilder.setRequiresCharging(false); // we don't care if the device is charging or not
JobScheduler getAlertjobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
getAlertjobScheduler.schedule(getAlertbuilder.build());
For more detail refer this Intelligent Job-Scheduling
I have a web service on my server that needs to be pinged every hour. For this, I am using an Android app to ping it every hour. I have tried using Alarm manager but it stops working after few hours and if I swipe exit it. I have tried using service but for some reason, that doesn't seem to work and my app keeps crashing. I have am thinking about using Firebase Job dispatcher. My requirement is that the app needs to ping the web service on my server every hour. This should go on for at least next 3-4 months. Is there a way to accomplish this ? Thanks in advance!
EDIT: I have tried broadcast receiver with Alarm Manager but have not been able to sustain the firing for more then 4 hours.
I second Anantha's answer but seems like job parameters are little off for your needs.
You can go over this article to learn about the subtle differences between various Job schedulers.
As a matter of fact, even Google recommends using Firebase Job Schedular if the app needs to do a network communication due to various reasons. Please watch the attached video on the Github page for more info on the same. This also gives you basic code to kickstart your application. You can just change the job parameters to suit your needs
Hopefully, this below code should suit your requirement of triggering every one hour with a tolerance of 15 minutes
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(getContext()));
final int periodicity = (int)TimeUnit.HOURS.toSeconds(1); // Every 1 hour periodicity expressed as seconds
final int toleranceInterval = (int)TimeUnit.MINUTES.toSeconds(15); // a small(ish) window of time when triggering is OK
Job myJob = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(yourJobService.class)
// uniquely identifies the job
.setTag("my-unique-tag")
// recurring job
.setRecurring(true)
// persist past a device reboot
.setLifetime(Lifetime.FOREVER)
// start between 0 and 60 seconds from now
.setTrigger(Trigger.executionWindow(periodicity, toleranceInterval))
// overwrite an existing job with the same tag
.setReplaceCurrent(true)
// retry with exponential backoff
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
// constraints that need to be satisfied for the job to run
.setConstraints(
// only run on an unmetered network
Constraint.ON_ANY_NETWORK
)
.setExtras(schedulerextras)
.build();
dispatcher.mustSchedule(myJob);
Jhon you can use firebase jobdispatcher. because it will support from api level 9. you can see below how to create job dispatcher and how to call it.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
scheduleJob(this);
}
public static void scheduleJob(Context context) {
//creating new firebase job dispatcher
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context));
//creating new job and adding it with dispatcher
Job job = createJob(dispatcher);
dispatcher.mustSchedule(job);
}
public static Job createJob(FirebaseJobDispatcher dispatcher){
Job job = dispatcher.newJobBuilder()
//persist the task across boots
.setLifetime(Lifetime.FOREVER)
//.setLifetime(Lifetime.UNTIL_NEXT_BOOT)
//call this service when the criteria are met.
.setService(ScheduledJobService.class)
//unique id of the task
.setTag("UniqueTagForYourJob")
//don't overwrite an existing job with the same tag
.setReplaceCurrent(false)
// We are mentioning that the job is periodic.
.setRecurring(true)
// Run between 30 - 60 seconds from now.
.setTrigger(Trigger.executionWindow(30, 60))
// retry with exponential backoff
.setRetryStrategy(RetryStrategy.DEFAULT_LINEAR)
//.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
//Run this job only when the network is available.
.setConstraints(Constraint.ON_ANY_NETWORK, Constraint.DEVICE_CHARGING)
.build();
return job;
}
public static Job updateJob(FirebaseJobDispatcher dispatcher) {
Job newJob = dispatcher.newJobBuilder()
//update if any task with the given tag exists.
.setReplaceCurrent(true)
//Integrate the job you want to start.
.setService(ScheduledJobService.class)
.setTag("UniqueTagForYourJob")
// Run between 30 - 60 seconds from now.
.setTrigger(Trigger.executionWindow(30, 60))
.build();
return newJob;
}
public void cancelJob(Context context){
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context));
//Cancel all the jobs for this package
dispatcher.cancelAll();
// Cancel the job for this tag
dispatcher.cancel("UniqueTagForYourJob");
}}
ScheduledJobService.java
public class ScheduledJobService extends JobService {
private static final String TAG = ScheduledJobService.class.getSimpleName();
#Override
public boolean onStartJob(final JobParameters params) {
//Offloading work to a new thread.
new Thread(new Runnable() {
#Override
public void run() {
codeYouWantToRun(params);
}
}).start();
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
return false;
}
public void codeYouWantToRun(final JobParameters parameters) {
try {
Log.d(TAG, "completeJob: " + "jobStarted");
//This task takes 2 seconds to complete.
Thread.sleep(2000);
Log.d(TAG, "completeJob: " + "jobFinished");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//Tell the framework that the job has completed and doesnot needs to be reschedule
jobFinished(parameters, true);
}
}}
You will need to use JobScheduler(api >21 ) and GcmNetworkManager (api<21) depending on the api level of android. Check out this library from evernote which takes care of it.
Do you try broadcast receiver? I use Broadcast Receiver with Alarm Manager to vibrate every minute and it work fine. The only problem is that when device turn off or restarted, it not vibrate till I enter my application.
My test code.
public void setAlarm() {
alarmMgr =(AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(AlarmManagerActivity.this, AlarmManagerBroadcastReceiver.class);
intent.setAction("a.b.c.d");
PendingIntent pi = PendingIntent.getBroadcast( getApplicationContext(), 0, intent, 0);
//After after 5 seconds
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 13);
calendar.set(Calendar.MINUTE, 40);
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis()
, (1000) * (60)
, pi);
}
My receiver
#Override
public void onReceive(Context context, Intent intent) {
Log.d(getClass().getSimpleName(), Intent.ACTION_BOOT_COMPLETED);
if ( intent.getAction().equals("a.b.c.d")) {
Log.d(getClass().getSimpleName(), "Custom Broadcast01");
Vibrator vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(10000);
}
else
Log.d(getClass().getSimpleName(), "no this action for intent!");
}
Broadcast receiver to start Alarm when device restart
<receiver
android:name=".OnBootBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class OnBootBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
setAlarm();
}
}
I have looked at many other threads on this. However, none of this seems to help.
I need to set up job scheduler that runs everyday at 3PM.
Here is my service class:
public class AttendanceCheckScheduler extends JobService {
private AttendanceCheckTask mAttendanceCheckTask;
private static final String TAG = "AttendanceCheckScheduler";
#Override
public boolean onStartJob(JobParameters params) {
mAttendanceCheckTask = new AttendanceCheckTask();
mAttendanceCheckTask.execute();
jobFinished(params, false);
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
mAttendanceCheckTask.cancel(true);
return false;
}
private class AttendanceCheckTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
// some long running task...
Toast.makeText(getApplicationContext(), "Task run", Toast.LENGTH_SHORT).show();
return null;
}
#Override
protected void onPostExecute(Void s) {
super.onPostExecute(s);
}
}
}
And this is how I am scheduling the job in ActivityHome.
public class ActivityHome extends AppCompatActivity {
private static final int JOB_ID = 100;
private JobScheduler jobScheduler;
protected JobInfo jobInfo;
private static final String TAG = "ActivityHome";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// other irrelevant codes here...
ComponentName componentName = new ComponentName(this, AttendanceCheckScheduler.class);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, componentName);
builder.setPeriodic(5000); // every 5 seconds for testing purpose...
builder.setPersisted(true);
jobInfo = builder.build();
jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);
Toast.makeText(this, "job scheduled", Toast.LENGTH_LONG).show();
}
}
Everythings seems to work fine and I am receiving the toast message Task run every 5 seconds as set above.
But when the app is killed (by clearing all task in multitask window), I stop receiving the toast messages.
How do I keep it running even if the app is killed ?
P.S: I want some task to perform once everyday at 3PM.
For background task for long time when your app is killed, you need to implement AlarmManager or WorkManager. WorkManager was in beta but now its ready only for Android X, just implement the dependency and copy paste WorkManager class code from google and put your task.
WorkManager latest dependency:
implementation "androidx.work:work-runtime:2.2.0"
I found this article. Here it says you have to call jobFinished() in your onPostExecute method of the asyncTask to let the system know the job is done.
jobFinished() requires two parameters: the current job, so that it
knows which wakelock can be released, and a boolean indicating whether
you’d like to reschedule the job. If you pass in true, this will kick
off the JobScheduler’s exponential backoff logic for you
I hope it helps.
Scheduling jobs like a pro with JobScheduler
There is an option not to kill the application, but it is very rude and it's the hack:
In your Manifest -> inside activity tag -> Add following line
android:excludeFromRecents="true"
Your app not show in recent apps history. So user can't kill the app.
Information is obtained by reference: https://code-examples.net/en/q/26c6cf8
I am learning how to use JobScheduler. as shown in onresume method, I set the criteria to be met in order to execute the job, the
job will be scheduled when the device is not charging, no matter the device is idle or not and the job will be executed every
7 seconds.
at run time, the usb cable is connected to the device in order to install the App which means the device is charging, so after
installing the App the job have not started because the device is charging, but after i unplug the usb cable i exected the job
to be executed but what happened is that the job never started, and i could not understand why
please let me know why such behavior is happeneing and please let me know the answer of the following question it will help me to
better understand the jobScheduler:
Q: is setRequiresCharging(false) means, that the task will be executed only if the device is NOT charging or it means that
the task will be executed no matter if the device is charging or not?
main activity
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static int jobId = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w(TAG, "onCreate");
setContentView(R.layout.activity_main);
}
#Override
protected void onResume() {
super.onResume();
Log.w(TAG, "onResume");
ComponentName serviceComponent = new ComponentName(this, MyJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(jobId, serviceComponent);
builder.setRequiresCharging(false);
builder.setRequiresDeviceIdle(false);
builder.setPeriodic(7 * 1000);
JobScheduler jobScheduler = (JobScheduler) getApplication().getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
}
}
jobService:
package example.com.jobscheduler_00;
public class MyJobService extends JobService {
private static final String TAG = MyJobService.class.getSimpleName();
#Override
public boolean onStartJob(JobParameters params) {
Log.w(TAG, "onStartJob JobId: " + params.getJobId());
Toast.makeText(this, "onStartJob JobId:" + params.getJobId(), Toast.LENGTH_SHORT).show();
jobFinished(params, false);
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
Log.w(TAG, "onStopJob");
Toast.makeText(this, "onStopJob JobId:" + params.getJobId(), Toast.LENGTH_SHORT).show();
return true;
}
}
Is setRequiresCharging(false) means, that the task will be executed only if the device is NOT charging or it means that the task will be executed no matter if the device is charging or not?
From the documentation:
Specify that to run this job, the device needs to be plugged in. This defaults to false.
In other words, if you want your job to be run only in condition, when the device is charging - you should pass true. By default it is false, which means charging criteria is disregarded, i.e. your job will be executed regardless the device is charging or no (assuming other criterias are fulfilled).
You may check whether your job has started successfully by the int value that JobScheduler.schedule(JobInfo job) returns. It will return either RESULT_SUCCESS or RESULT_FAILURE.
The new WorkManager API will help you set necessary constraints for the task you want to schedule.
Have a look at this video for a brief intro - https://www.youtube.com/watch?v=pErTyQpA390 (WorkManager at 21:44).
EDIT: Adding an example to showcase the capabilities of the new API
For eg:
You can set constraints related to charging state of the device like this (along with other constraints like if the device is supposed to be idle for the task to run etc..)
// Create a Constraints that defines when the task should run
Constraints yourConstraints = new Constraints.Builder()
.setRequiresDeviceIdle(true/false)
.setRequiresCharging(true/false)
// Many other constraints are available
.build();
// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest yourWork =
new OneTimeWorkRequest.Builder(YourWorkerClass.class)
.setConstraints(yourConstraints)
.build();