How to schedule a job using Jobscheduler - android

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();

Related

Best way to use TriggerEventListener in the background?

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

Job Scheduler doesn't work if app is killed

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

Android-Job run every time device is plugged in

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.

How to use FirebaseJobDispatcher to replace CONNECTIVITY_CHANGE reliably

My app is targeting Android 7, with minimum SDK Android 4.
Hence, listening to CONNECTIVITY_CHANGE (Even when the app is killed) no longer work anymore. What I wish to do is
Even when my main app is killed, when the internet connectivity change from "not available" to "available", I would like to start an alarm broadcast receiver.
I try to achieve with the following code
MainActivity.java
#Override
public void onPause() {
super.onPause();
installJobService();
}
private void installJobService() {
// Create a new dispatcher using the Google Play driver.
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(this));
Job myJob = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(MyJobService.class)
// uniquely identifies the job
.setTag("my-unique-tag")
// one-off job
.setRecurring(true)
// persist forever
.setLifetime(Lifetime.FOREVER)
// start between 0 and 60 seconds from now
.setTrigger(Trigger.executionWindow(0, 60))
// 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 any network
Constraint.ON_ANY_NETWORK
)
.build();
dispatcher.mustSchedule(myJob);
}
However,
MyJobService.java
import android.content.Context;
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;
import org.yccheok.jstock.gui.JStockApplication;
/**
* Created by yccheok on 21/5/2017.
*/
public class MyJobService extends JobService {
#Override
public boolean onStartJob(JobParameters jobParameters) {
Context context = this.getApplicationContext();
android.util.Log.i("CHEOK", "Internet -> " + Utils.isInternetAvailable(context));
// Answers the question: "Is there still work going on?"
return false;
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
// Answers the question: "Should this job be retried?"
return true;
}
}
However, the above code isn't reliable. How I test is
Quit my app.
Kill my app explicitly via Settings using "Force stop".
Turn off internet.
Turn on internet.
Wait for few minutes. MyJobService is never executed.
Is there any reliable way, to use FirebaseJobDispatcher to replace CONNECTIVITY_CHANGE reliably?
I had gone through Firebase JobDispatcher - how does it work compared to previous APIs (JobScheduler and GcmTaskService)? , but I still can't find a way to make it work reliably.
Not sure how you are detecting CONNECTIVITY_CHANGE through FirebaseJobDispatcher but for same situation I had used broadcast
public class ConnectivityStateReceiver extends BroadcastReceiver {
String TAG = "MyApp";
#Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context, NetworkService.class);
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
return;
} else if (cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected()) {
Log.e(TAG, "Connected!");
context.startService(serviceIntent);
} else {
Log.e(TAG, "Not Connected!");
context.stopService(serviceIntent);
}
}
}

Intent Service not working in doze mode

One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Below is the code:
public class GpsTrackingService extends IntentService {
....
#Override
protected void onHandleIntent(Intent intent) {
do{
try{
//make API call here
//then go to sleep for 2 mins
TimeUnit.SECONDS.sleep(120);
} catch(InterruptedException ex){
ex.printStackTrace();
}
} while (preferences.shouldSendGps()); //till the user can send gps.
}
....
}
Manifest
<service android:name=".commons.GpsTrackingService" />
This is working fine when the phone is active. However, whenever the phone goes into doze mode it fails to wake.
Will using alarm manager with WAKE permission solve this?
I have just got the code base and need to fix this within today. It'll be great if someone can help.
As the documentation says:
In Doze mode, the system attempts to conserve battery by restricting
apps' access to network and CPU-intensive services. It also prevents
apps from accessing the network and defers their jobs, syncs, and
standard alarms.
Periodically, the system exits Doze for a brief time to let apps
complete their deferred activities. During this maintenance window,
the system runs all pending syncs, jobs, and alarms, and lets apps
access the network.
In few words, while in Doze mode the system suspends network accesses, ignores Wake Locks, stops acquiring data from sensors, defers AlarmManager jobs to the next Doze maintenance window (which are progressively less frequently called), also WiFi scans, JobScheduler jobs and Sync adapters do not run.
Neither setAndAllowWhileIdle() nor setExactAndAllowWhileIdle() can fire alarms more than once per 9 (?) minutes, per app.
And it seems that the Foreground Services are also involved into this "Doze Drama", at least in MarshMellow (M).
To survive in this situation, tons of applications need to be at least rewiewed. Can you imagine a simple mp3 player which stops playing music when the device enters in Doze Mode?
Doze mode starts automatically, when the device is unplugged from the power supply and left on the table for about 1 hour or so, or even earlier when the user clicks the power button to power down the screen, but I think this could depend by the device manufacturer too.
I tried a lot of countermeasures, some of them really hilarious.
At the end of my tests I reached a possible solution:
One possible (and maybe the only) way to have your app running even when the host device is in Doze mode, is basically to have a ForegroundService (even a fake one, doing no jobs at all) running in another process with an acquired partial WakeLock.
What you need to do is basically the following (you could create a simple project to test it):
1 - In your new project, create a new class which extends Application (myApp), or use the
main activity of the new project.
2 - In myApp onCreate() start a Service (myAntiDozeService)
3 - In myAntiDozeService onStartCommand(), create the Notification
needed to start the service as a foreground service, start the
service with startForeground(id, notification) and acquire the
partial WakeLock.
REMEMBER! This will work, but it is just a starting point, because you have to be careful with the "Side Effects" this approach will generate:
1 - Battery drain: The CPU will work for your app forever if you
don't use some strategy and leave the WakeLock always active.
2 - One notification will be always shown, even in the lockscreen,
and this notification cannot be removed by simply swiping it out, it
will be always there until you'll stop the foreground service.
OK, let's do it.
myApp.java
public class myApp extends Application {
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
// start foreground service
startForeService();
}
private void stopForeService() {
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STOPFOREGROUND_ACTION);
stopService(service);
}
private void startForeService(){
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STARTFOREGROUND_ACTION);
startService(service);
}
#Override
public void onTerminate() {
stopForeService();
super.onTerminate();
}
}
myAntiDozeService.java
public class myAntiDozeService extends Service {
private static final String TAG = myAntiDozeService.class.getName();
private static boolean is_service_running = false;
private Context mContext;
private PowerManager.WakeLock mWakeLock;
private static final int NOTIFICATION_ID = 12345678;
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!is_service_running && STARTFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Start Foreground Intent ");
showNotification();
is_service_running = true;
acquireWakeLock();
} else if (is_service_running && STOPFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Stop Foreground Intent");
is_service_running = false;
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
#Override
public void onDestroy() {
releaseWakeLock();
super.onDestroy();
}
private void showNotification(){
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(mContext)
.setContentTitle("myApp")
.setTicker("myApp")
.setContentText("Application is running")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
// starts this service as foreground
startForeground(NOTIFICATION_ID, notification);
}
public void acquireWakeLock() {
final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
releaseWakeLock();
//Acquire new wake lock
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG+"PARTIAL_WAKE_LOCK");
mWakeLock.acquire();
}
public void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
mWakeLock = null;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml changes.
In the AndroidManifest.xml add this permission:
<uses-permission android:name="android.permission.WAKE_LOCK" />
Don't forget to add the name of your app in the <application> tag:
<application
....
android:name=".myApp"
....
And finally add your foreground service running into another process:
<service
android:name=".myAntiDozeService"
android:process=":MyAntiDozeProcessName">
</service>
A couple of notes.
In the previous example, the notification created, when clicked,
opens the ActivityMain activity of your test project.
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
but you can use another kind of intent too.
To test it, you have to add some job to be performed into your
ActivityMain.java, for example some repeating alarm (which was
normally stopped when the device falls in Doze Mode), or a ripetitive
network access, or a timed tone played, or.... whatever you want.
Remember that the job performed by the main activity has to run
forever because to test this AntiDoze you need to wait at least 1
hour to be sure the device enters in Doze Mode.
To enter in Doze mode, the device has to be quiet and unplugged, so
you can't test it while you are debugging. Debug your app first,
check that everything is running then stop it, unplug, restart the
app again and leave the device alone and quiet on your desk.
The adb commands suggested by the documentation to simulate Doze
and StandBy modes could and could not give you the right results
(it depends, I suppose, by the device manufacturer, drivers, bla
bla). Please make your tests in the REAL behaviour.
In my first test, I used an AlarmManager and a tone generator to play a tone every 10 minutes just to understand that my app was still active.
And it is still running from about 18 hours, breaking my ears with a loud tone exactly every 10 minutes. :-)
Happy coding!
One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Only have a service running while it is actively delivering value to the user. Sitting around for two minutes, watching the clock tick, is not actively delivering value to the user.
Will using alarm manager with WAKE permission solve this?
That depends on what you mean by "solve this". You can use AlarmManager to request to get control every two minutes so that you can do work. While the device is in Doze mode, you will not actually get control every two minutes, but once per maintenance window.

Categories

Resources