Android helps us to schedule jobs in an efficient way. Android has a built in Job scheduler for this.
Lets see how this is done.
Before that lets see what google has to say about Job Scheduling.
These are the normal tasks an application does :
- Updating network resources.
- Downloading information.
- Updating background tasks.
- Scheduling system service calls.
Scheduling this work intelligently can improve your app’s performance, along with aspects of system health such as battery life. JobScheduler does this scheduling work for you.
Below are the other ways for doing periodic tasks
- AlarmManager
- Firebase JobDispatcher
- SyncAdapter
You can read more about job scheduling from here…
https://developer.android.com/topic/performance/scheduling.html
Code
Main Activity that starts the Job.
package com.coderzheaven.android.jobscheduler; import android.app.Activity; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.PersistableBundle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.CheckBox; import android.widget.EditText; import android.widget.RadioButton; import android.widget.Toast; import com.example.android.jobscheduler.service.MyJobService; import java.lang.ref.WeakReference; import java.util.List; public class MainActivity extends Activity { private static final String TAG = MainActivity.class.getSimpleName(); public static final int JOB_STARTED = 1; public static final int JOB_STOPPED = 2; public static final String MESSENGER_INTENT_KEY = BuildConfig.APPLICATION_ID + ".MESSENGER_INTENT_KEY"; public static final String WORK_DURATION_KEY = BuildConfig.APPLICATION_ID + ".WORK_DURATION_KEY"; private EditText mDelayEditText; private EditText mDeadlineEditText; private EditText mDurationTimeEditText; private RadioButton mWiFiConnectivityRadioButton; private RadioButton mAnyConnectivityRadioButton; private CheckBox mRequiresChargingCheckBox; private CheckBox mRequiresIdleCheckbox; private ComponentName mServiceComponent; private int mJobId = 0; // Handler for incoming messages from the service. private IncomingMessageHandler mHandler; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sample_main); // Set up UI. mDelayEditText = (EditText) findViewById(R.id.delay_time); mDurationTimeEditText = (EditText) findViewById(R.id.duration_time); mDeadlineEditText = (EditText) findViewById(R.id.deadline_time); mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered); mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any); mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging); mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle); mServiceComponent = new ComponentName(this, MyJobService.class); mHandler = new IncomingMessageHandler(this); } @Override protected void onStop() { // A service can be "started" and/or "bound". In this case, it's "started" by this Activity // and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call // to stopService() won't prevent scheduled jobs to be processed. However, failing // to call stopService() would keep it alive indefinitely. stopService(new Intent(this, MyJobService.class)); super.onStop(); } @Override protected void onStart() { super.onStart(); // Start service and provide it a way to communicate with this class. Intent startServiceIntent = new Intent(this, MyJobService.class); Messenger messengerIncoming = new Messenger(mHandler); startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming); startService(startServiceIntent); } /** * Executed when user clicks on SCHEDULE JOB. */ public void scheduleJob(View v) { JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent); String delay = mDelayEditText.getText().toString(); if (!TextUtils.isEmpty(delay)) { builder.setMinimumLatency(Long.valueOf(delay) * 1000); } String deadline = mDeadlineEditText.getText().toString(); if (!TextUtils.isEmpty(deadline)) { builder.setOverrideDeadline(Long.valueOf(deadline) * 1000); } boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked(); boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked(); if (requiresUnmetered) { builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); } else if (requiresAnyConnectivity) { builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); } builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked()); builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked()); // Extras, work duration. PersistableBundle extras = new PersistableBundle(); String workDuration = mDurationTimeEditText.getText().toString(); if (TextUtils.isEmpty(workDuration)) { workDuration = "1"; } extras.putLong(WORK_DURATION_KEY, Long.valueOf(workDuration) * 1000); builder.setExtras(extras); // Schedule job Log.d(TAG, "Scheduling job"); JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); tm.schedule(builder.build()); } /** * Executed when user clicks on CANCEL ALL. */ public void cancelAllJobs(View v) { JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); tm.cancelAll(); Toast.makeText(MainActivity.this, R.string.all_jobs_cancelled, Toast.LENGTH_SHORT).show(); } /** * Executed when user clicks on FINISH LAST TASK. */ public void finishJob(View v) { JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs(); if (allPendingJobs.size() > 0) { // Finish the last one int jobId = allPendingJobs.get(0).getId(); jobScheduler.cancel(jobId); Toast.makeText( MainActivity.this, String.format(getString(R.string.cancelled_job), jobId), Toast.LENGTH_SHORT).show(); } else { Toast.makeText( MainActivity.this, getString(R.string.no_jobs_to_cancel), Toast.LENGTH_SHORT).show(); } } /** * A {@link Handler} allows you to send messages associated with a thread. A {@link Messenger} * uses this handler to communicate from {@link MyJobService}. It's also used to make * the start and stop views blink for a short period of time. */ private static class IncomingMessageHandler extends Handler { // Prevent possible leaks with a weak reference. private WeakReference<MainActivity> mActivity; IncomingMessageHandler(MainActivity activity) { super(/* default looper */); this.mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity mainActivity = mActivity.get(); if (mainActivity == null) { // Activity is no longer available, exit. return; } Message m; switch (msg.what) { /* * Receives callback from the service when a job has landed * on the app. Turns on indicator and sends a message to turn it off after * a second. */ case JOB_STARTED: Toast.makeText(mainActivity, "Job Started", Toast.LENGTH_SHORT).show(); break; case JOB_STOPPED: Toast.makeText(mainActivity, "Job Stopped", Toast.LENGTH_SHORT).show(); break; } } } }
Layout
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TableLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp"> <TableRow android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="12dp" android:gravity="right|center_vertical" android:text="@string/work_duration" android:textSize="14sp" /> <EditText android:id="@+id/duration_time" android:layout_width="48dp" android:layout_height="wrap_content" android:inputType="number" android:text="5" /> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="12dp" android:gravity="right|center_vertical" android:text="@string/connectivity" android:textSize="14sp" /> <RadioGroup android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <RadioButton android:id="@+id/checkbox_any" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="@string/any" /> <RadioButton android:id="@+id/checkbox_unmetered" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/unmetered" /> </RadioGroup> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="12dp" android:gravity="right|center_vertical" android:text="@string/delay" android:textSize="14sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/timing" /> <EditText android:id="@+id/delay_time" android:layout_width="48dp" android:layout_height="wrap_content" android:inputType="number" android:text="0" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/deadline" android:textSize="14sp" /> <EditText android:id="@+id/deadline_time" android:layout_width="48dp" android:layout_height="wrap_content" android:inputType="number" android:text="15" /> </LinearLayout> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="12dp" android:gravity="right|center_vertical" android:text="@string/charging_caption" android:textSize="14sp" /> <CheckBox android:id="@+id/checkbox_charging" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/charging_text" /> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="12dp" android:gravity="right|center_vertical" android:text="@string/idle_caption" /> <CheckBox android:id="@+id/checkbox_idle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/idle_mode_text" /> </TableRow> </TableLayout> <Button android:id="@+id/schedule_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="40dp" android:layout_marginRight="40dp" android:layout_marginTop="20dp" android:onClick="scheduleJob" android:text="@string/schedule_job_button_text" /> <Button android:id="@+id/finished_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_marginLeft="40dp" android:layout_marginRight="40dp" android:onClick="finishJob" android:padding="20dp" android:text="@string/finish_job_button_text" /> <Button android:id="@+id/cancel_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="40dp" android:layout_marginRight="40dp" android:onClick="cancelAllJobs" android:text="@string/cancel_all_jobs_button_text" /> </LinearLayout> </ScrollView>
Service
The Below service is used to do the work and update the result back in MainActivity’s UI.
package com.coderzheaven.android.jobscheduler.service; import android.app.job.JobParameters; import android.app.job.JobService; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.annotation.Nullable; import android.util.Log; import static com.example.android.jobscheduler.MainActivity.JOB_STARTED; import static com.example.android.jobscheduler.MainActivity.JOB_STOPPED; import static com.example.android.jobscheduler.MainActivity.MESSENGER_INTENT_KEY; import static com.example.android.jobscheduler.MainActivity.WORK_DURATION_KEY; /** * Service to handle callbacks from the JobScheduler. Requests scheduled with the JobScheduler * ultimately land on this service's "onStartJob" method. It runs jobs for a specific amount of time * and finishes them. It keeps the activity updated with changes via a Messenger. */ public class MyJobService extends JobService { private static final String TAG = MyJobService.class.getSimpleName(); private Messenger mActivityMessenger; @Override public void onCreate() { super.onCreate(); Log.i(TAG, "Service created"); } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "Service destroyed"); } /** * When the app's MainActivity is created, it starts this service. This is so that the * activity and this service can communicate back and forth. See "setUiCallback()" */ @Override public int onStartCommand(Intent intent, int flags, int startId) { mActivityMessenger = intent.getParcelableExtra(MESSENGER_INTENT_KEY); return START_NOT_STICKY; } @Override public boolean onStartJob(final JobParameters params) { // The work that this service "does" is simply wait for a certain duration and finish // the job (on another thread). sendMessage(JOB_STARTED, params.getJobId()); long duration = params.getExtras().getLong(WORK_DURATION_KEY); // Uses a handler to delay the execution of jobFinished(). Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { sendMessage(JOB_STOPPED, params.getJobId()); jobFinished(params, false); } }, duration); Log.i(TAG, "on start job: " + params.getJobId()); // Return true as there's more work to be done with this job. return true; } @Override public boolean onStopJob(JobParameters params) { // Stop tracking these job parameters, as we've 'finished' executing. sendMessage(JOB_STOPPED, params.getJobId()); Log.i(TAG, "on stop job: " + params.getJobId()); // Return false to drop the job. return false; } private void sendMessage(int messageID, @Nullable Object params) { // If this service is launched by the JobScheduler, there's no callback Messenger. It // only exists when the MainActivity calls startService() with the callback in the Intent. if (mActivityMessenger == null) { Log.d(TAG, "Service is bound, not started. There's no callback to send a message to."); return; } Message m = Message.obtain(); m.what = messageID; m.obj = params; try { mActivityMessenger.send(m); } catch (RemoteException e) { Log.e(TAG, "Error passing service object back to activity."); } } }
All Done. Now run the app and see the result.
You can set the scheduling time in the UI.