In Android development, there are scenarios where you need to perform a task at a later point in time or when certain conditions are met, such as when the device is connected to a power adapter or WIFI. Fortunately, in API 21 (Android 5.0, or Lollipop), Google provides a new component called the JobScheduler API to handle such scenarios.

When a set of preset conditions are met, the JobScheduler API performs an operation for your application. Unlike AlarmManager, this execution time is indeterminate. In addition, the JobScheduler API allows multiple tasks to be executed simultaneously. This allows your application to perform certain tasks without considering battery drain due to timing control.

JobScheduler is simple to use

1. Create Job Service
public class MyJobService extends JobService { private static final String LOG_TAG ="ms" ; @override public Boolean onStartJob(JobParameters params) {if (isNetWorkConnected()){// Perform the download task new SimpleDownloadTask().execute(params); return true; } return false; } @Override public boolean onStopJob(JobParameters params) { return false; }Copy the code

The onStartJob(JobParameters Params) method is executed when the task starts, because this is used by the system to trigger the task that has already been executed. As you can see, this method returns a Boolean value. If the return value is false, the system assumes that the task has finished when the method returns. If the return value is true, the system assumes that the task is about to be executed, and the burden of executing the task falls on you. When the task is complete, call jobFinished(JobParameters Params, Boolean needsRescheduled) to notify the system

When the system receives a cancellation request, the system calls the onStopJob(JobParameters Params) method to cancel the task waiting to be executed. It is important to note that if onStartJob(JobParameters Params) returns false, the system assumes that no task is running when a cancel request is received. In other words, onStopJob(JobParameters Params) will not be called in this case.

Note that the Job service runs on your main thread, which means you need to use child threads, handlers, or an asynchronous task to run time-consuming operations to prevent blocking the main thread

2. Create a JobScheduler object and associate it
MJobScheduler = (JobScheduler) getSystemService(context.job_scheduler_service);Copy the code

When you want to create a scheduled task, you can use jobinfo. Builder to build a JobInfo object and pass it to your Service. Jobinfo. Builder takes two parameters, the first is the identifier of the task you want to run and the second is the class name of the Service component.

ComponentName componentName = new ComponentName(this,MyJobService.class); JobInfo job=new Jobinfo.builder (I,componentName).setMinimumLatency(5000) .setrequiredNetworkType (JobInfo.NETWORK_TYPE_ANY)// Any network.build (); // call schedule mjobscheduler.schedule (job);Copy the code

JobInfo Other Settings:

.setMinimumLatency(5000)//5 seconds minimum delay,.setoverrideDeadline (60000)// Maximum execution time .setrequiredNetworkType (JobInfo.NETWORK_TYPE_UNMETERED)// Free network --wifi Bluetooth USB . SetRequiredNetworkType (JobInfo.NETWORK_TYPE_ANY)// Any network -- /** Sets the retry/retreat policy and the measurement to be performed when a task fails to be scheduled. InitialBackoffMillis: wait interval for the first retry ms *backoffPolicy: corresponding retreat policy. Wait times, for example, grow exponentially. */ .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) .setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, Jobinfo. BACKOFF_POLICY_LINEAR) //.setperiodic (long intervalMillis)// Sets the execution period. The task can be executed at most once at a specified interval. SetPeriodic (long intervalMillis,long flexMillis)// At the end of the periodic execution there is a flexMiliis length window period in which the task can be executed. // Set whether to keep this task after the device restarts. RECEIVE_BOOT_COMPLETED // CTRL +shift+y/u x //.setpersisted (Boolean isPersisted); //. SetRequiresDeviceIdle (Boolean)// When the device is in the idle state // .addtriggerContentUri (uri)// If the data corresponding to the listening URI changes, the task is triggered. / / setTriggerContentMaxDelay (long duration) / / set the Content change until the task is carried out in the middle of the maximum delay time / / set the Content change until the task is performed in the middle of the delay. If the content changes during this delay time, the delay time rewrites the calculation. // .setTriggerContentUpdateDelay(long durationMilimms)Copy the code

JobScheduler source code analysis

JobScheduler is the system service JobSchedulerService. JobScheduler is an abstract class. Here is the source for JobScheduler:

public abstract class JobScheduler { public static final int RESULT_FAILURE = 0; public static final int RESULT_SUCCESS = 1; JobInfo public abstract int schedule(JobInfo job); public abstract int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag); Public abstract void cancel(int jobId); Public void cancelAll(); public void cancelAll(); Public abstract @nonnull List<JobInfo> getAllPendingJobs(); Public abstract @nullable JobInfo getPendingJob(int Jobid);Copy the code

} The implementation class of JobScheduler is JobSchedulerImpl;

public class JobSchedulerImpl extends JobScheduler { IJobScheduler mBinder; /* package */ JobSchedulerImpl(IJobScheduler binder) { mBinder = binder; } @override public int schedule(JobInfo job) {// This mBinder is JobSchedulerService iJobscheduler.stub,  return mBinder.schedule(job); } catch (RemoteException e) { return JobScheduler.RESULT_FAILURE; } } @Override public void cancel(int jobId) { try { mBinder.cancel(jobId); } catch (RemoteException e) {} } @Override public void cancelAll() { try { mBinder.cancelAll(); } catch (RemoteException e) {} } @Override public List<JobInfo> getAllPendingJobs() { try { return mBinder.getAllPendingJobs(); } catch (RemoteException e) { return null; }}}Copy the code

Call mjobScheduler.schedule (job) in the code; The schedule method of the JobScheduler implementation class JobSchedulerImpl is called. MBinder. Schedule (job); The JobSchedulerService mBinder calls the schedule method of the iJobScheduler. Stub inner class of JobSchedulerService. Where is the JobSchedulerService started? JobSchedulerService is a system service.

public class JobSchedulerService extends com.android.server.SystemService implements StateChangedListener, JobCompletedListener { static final boolean DEBUG = false; /** The number of concurrent jobs we run at one time. */ private static final int MAX_JOB_CONTEXTS_COUNT = ActivityManager.isLowRamDeviceStatic() ? 1:3; static final String TAG = "JobSchedulerService"; /** Master list of jobs. */ final JobStore mJobs; static final int MSG_JOB_EXPIRED = 0; static final int MSG_CHECK_JOB = 1; / /... Omitted n more code}Copy the code

Is the system service that should be started in SystemServer, first look at SystemServer source;

public final class SystemServer { private static final String TAG = "SystemServer"; Public static void main(String[] args) {new SystemServer().run(); }Copy the code

The main method calls the run method. Here is the code in the run method:

private void run() { //.... // Start services. // Try {startBootstrapServices(); startCoreServices(); // Will go this startOtherServices(); } catch (Throwable ex) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting system services", ex); throw ex; } / /... Omitted n more code}Copy the code

As you can see from the run method, a series of system services are started, which calls the startOtherServices() method.

private void startOtherServices() { //.... Omitted n more code mSystemServiceManager. StartService (TwilightService. Class); / / here is to start JobSchedulerService mSystemServiceManager. StartService (JobSchedulerService. Class); if (! disableNonCoreServices) { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) { mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS); } if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) { mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS); } if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) { mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); }} / /... Omitted n more code}Copy the code

In the method startOtherServices method calls the mSystemServiceManager. StartService (JobSchedulerService. Class); Here the JobSchedulerService service is started; Next we analyze the source of JobSchedulerService; Let’s look at the constructor of the JobSchedulerService:

public JobSchedulerService(Context context) { super(context); // Create the controllers. mControllers = new ArrayList<StateController>(); / / the following control class are inherited StateController class/class/Internet connection control mControllers. Add (ConnectivityController. Get (this)); // Add (timecontroller.get (this)); // Add (idlecontroller.get (this)); // BatteryController.add (batteryController.get (this)); mControllers.add(AppIdleController.get(this)); mHandler = new JobHandler(context.getMainLooper()); // New iJobScheduler. Stub (mBinder); // New iJobScheduler. Stub (mBinder); mJobSchedulerStub = new JobSchedulerStub(); mJobs = JobStore.initAndGet(this); }Copy the code

Mbinder.schedule (job); JobSchedulerService (IJobScheduler.Stub); Take a look at this method:

final class JobSchedulerStub extends IJobScheduler.Stub { //.... @override public int schedule(JobInfo job) throws RemoteException {if (DEBUG) {log.d(TAG, "Scheduling job: " + job.toString()); Final int pid = binder.getCallingPid (); final int pid = binder.getCallingpid (); final int uid = Binder.getCallingUid(); / /... Omitted n code more long ident = Binder. The clearCallingIdentity (); Try {/ / this is the key code, call the schedule of JobSchedulerService return JobSchedulerService. Enclosing the schedule (job, uid); } finally { Binder.restoreCallingIdentity(ident); }}}Copy the code

The schedule method of JobSchedulerService is called. The schedule method of JobSchedulerService is called.

public int schedule(JobInfo job, int uId) { JobStatus jobStatus = new JobStatus(job, uId); cancelJob(uId, job.getId()); startTrackingJob(jobStatus); Mhandler.obtainmessage (MSG_CHECK_JOB).sendtotarget (); return JobScheduler.RESULT_SUCCESS; }Copy the code

MSG_CHECK_JOB is the target source of the message. Take a look at the methods in JobHandler

private class JobHandler extends Handler { public JobHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message message) { synchronized (mJobs) { if (! mReadyToRock) { return; } } switch (message.what) { case MSG_JOB_EXPIRED: synchronized (mJobs) { JobStatus runNow = (JobStatus) message.obj; if (runNow ! = null && ! mPendingJobs.contains(runNow) && mJobs.containsJob(runNow)) { mPendingJobs.add(runNow); } queueReadyJobsForExecutionLockedH(); } break; case MSG_CHECK_JOB: synchronized (mJobs) { maybeQueueReadyJobsForExecutionLockedH(); } break; } maybeRunPendingJobsH(); removeMessages(MSG_CHECK_JOB); }Copy the code

Through send message Handler, and then call maybeQueueReadyJobsForExecutionLockedH () method,

private void queueReadyJobsForExecutionLockedH() { ArraySet<JobStatus> jobs = mJobs.getJobs(); if (DEBUG) { Slog.d(TAG, "queuing all ready jobs for execution:"); PendingJobs PendingJobs for (int I =0; i<jobs.size(); i++) { JobStatus job = jobs.valueAt(i); if (isReadyToBeExecutedLocked(job)) { if (DEBUG) { Slog.d(TAG, " queued " + job.toShortString()); } // add the target task to the set of target tasks to be processed. } else if (isReadyToBeCancelledLocked(job)) { stopJobOnServiceContextLocked(job); } } if (DEBUG) { final int queuedJobs = mPendingJobs.size(); if (queuedJobs == 0) { Slog.d(TAG, "No jobs pending."); } else { Slog.d(TAG, queuedJobs + " jobs queued."); }}}Copy the code

This method is mainly to traverse the work tasks to be dealt with in the future and then add them to the set of work tasks to be dealt with one by one; This method completes and executes the maybeRunPendingJobsH() method in JobHandler;

private void maybeRunPendingJobsH() { synchronized (mJobs) { if (mDeviceIdleMode) { // If device is idle, we will not schedule jobs to run. return; } Iterator<JobStatus> it = mPendingJobs.iterator(); if (DEBUG) { Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); While (it.hasNext()) {JobStatus nextPending = it.next(); JobServiceContext availableContext = null; for (int i=0; i<mActiveServices.size(); i++) { JobServiceContext jsc = mActiveServices.get(i); final JobStatus running = jsc.getRunningJob(); if (running ! = null && running.matches(nextPending.getUid(), nextPending.getJobId())) { // Already running this job for this uId, skip. availableContext = null; break; } if (jsc.isAvailable()) { availableContext = jsc; } } if (availableContext ! = null) { if (DEBUG) { Slog.d(TAG, "About to run job " + nextPending.getJob().getService().toString()); } // This method is the method of processing the pending task if (! availableContext.executeRunnableJob(nextPending)) { if (DEBUG) { Slog.d(TAG, "Error executing " + nextPending); } mJobs.remove(nextPending); } it.remove(); } } } } }Copy the code

Source code from the above analysis can be concluded that this method by traversing the to-do tasks collection, processing tasks, here called the availableContext. ExecuteRunnableJob (nextPending) method, this is the method of processing tasks to be processed, Let’s take a look at the source code of this method and analyze it:

public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (! name.equals(mRunningJob.getServiceComponent())) { mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();  return; } this.service = IJobService.Stub.asInterface(service); final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag()); Mwakelock.setworksource (new WorkSource(mrunningjob.getuid ()))); mWakeLock.setReferenceCounted(false); mWakeLock.acquire(); mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget(); }}Copy the code

JobServiceContext ServiceConnection, this is the interprocess communication ServiceConnection, by calling the availableContext. ExecuteRunnableJob (nextPending) method, It’s called onServiceConnected ected service, and it’s called a WakeLock lock to prevent sleep. The handleServiceBoundH() method is then called by sending a message to the Handler,

/** Start the job on the service. */ private void handleServiceBoundH() { if (DEBUG) { Slog.d(TAG, "MSG_SERVICE_BOUND for " + mRunningJob.toShortString()); } if (mVerb ! = VERB_BINDING) { Slog.e(TAG, "Sending onStartJob for a job that isn't pending. " + VERB_STRINGS[mVerb]); closeAndCleanupJobH(false /* reschedule */); return; } if (mCancelled.get()) { if (DEBUG) { Slog.d(TAG, "Job cancelled while waiting for bind to complete. " + mRunningJob); } closeAndCleanupJobH(true /* reschedule */); return; } try { mVerb = VERB_STARTING; scheduleOpTimeOut(); Startjob service.startJob(mParams); startJob(mParams); } catch (RemoteException e) { Slog.e(TAG, "Error sending onStart message to '" + mRunningJob.getServiceComponent().getShortClassName() + "' ", e); }}Copy the code

Startjob (jobService); startJob (jobService); startJob (jobService)

The control class StateController was mentioned in JobSchedulerService. This is an abstract class with many implementation classes. I’m only going to analyze one ConnectivityController implementation class. ConnectivityController source code:

public class ConnectivityController extends StateController implements
    ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "JobScheduler.Conn";
//工作任务状态集合
private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
  //这个是手机网络连接改变广播,网络发生改变,会触发这个广播
private final BroadcastReceiver mConnectivityChangedReceiver =
        new ConnectivityChangedReceiver();
/** Singleton. */
private static ConnectivityController mSingleton;
private static Object sCreationLock = new Object();
/** Track whether the latest active network is metered. */
private boolean mNetworkUnmetered;
/** Track whether the latest active network is connected. */
private boolean mNetworkConnected;

public static ConnectivityController get(JobSchedulerService jms) {
    synchronized (sCreationLock) {
        if (mSingleton == null) {
            mSingleton = new ConnectivityController(jms, jms.getContext());
        }
        return mSingleton;
    }
}

private ConnectivityController(StateChangedListener stateChangedListener, Context context) {
    super(stateChangedListener, context);
    // Register connectivity changed BR.
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    mContext.registerReceiverAsUser(
            mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
    ConnectivityService cs =
            (ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
    if (cs != null) {
        if (cs.getActiveNetworkInfo() != null) {
            mNetworkConnected = cs.getActiveNetworkInfo().isConnected();
        }
        mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered();
    }
}

@Override
public void maybeStartTrackingJob(JobStatus jobStatus) {
    if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
        synchronized (mTrackedJobs) {
            jobStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
            jobStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
            mTrackedJobs.add(jobStatus);
        }
    }
}

@Override
public void maybeStopTrackingJob(JobStatus jobStatus) {
    if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
        synchronized (mTrackedJobs) {
            mTrackedJobs.remove(jobStatus);
        }
    }
}

/**
 * @param userId Id of the user for whom we are updating the connectivity state.
 */
private void updateTrackedJobs(int userId) {
    synchronized (mTrackedJobs) {
        boolean changed = false;
        for (JobStatus js : mTrackedJobs) {
            if (js.getUserId() != userId) {
                continue;
            }
            boolean prevIsConnected =
                    js.connectivityConstraintSatisfied.getAndSet(mNetworkConnected);
            boolean prevIsMetered = js.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered);
            if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) {
                changed = true;
            }
        }
        if (changed) {
            mStateChangedListener.onControllerStateChanged();
        }
    }
}

/**
 * We know the network has just come up. We want to run any jobs that are ready.
 */
public synchronized void onNetworkActive() {
    synchronized (mTrackedJobs) {
        for (JobStatus js : mTrackedJobs) {
            if (js.isReady()) {
                if (DEBUG) {
                    Slog.d(TAG, "Running " + js + " due to network activity.");
                }
                mStateChangedListener.onRunJobNow(js);
            }
        }
    }
}

class ConnectivityChangedReceiver extends BroadcastReceiver {
    /**
     * We'll receive connectivity changes for each user here, which we process independently.
     * We are only interested in the active network here. We're only interested in the active
     * network, b/c the end result of this will be for apps to try to hit the network.
     * @param context The Context in which the receiver is running.
     * @param intent The Intent being received.
     */
    // TODO: Test whether this will be called twice for each user.
    @Override
    public void onReceive(Context context, Intent intent) {
        if (DEBUG) {
            Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
                    + context.getUserId());
        }
        final String action = intent.getAction();
        if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
            final int networkType =
                    intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
                            ConnectivityManager.TYPE_NONE);
            // Connectivity manager for THIS context - important!
            final ConnectivityManager connManager = (ConnectivityManager)
                    context.getSystemService(Context.CONNECTIVITY_SERVICE);
            final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
            final int userid = context.getUserId();
            // This broadcast gets sent a lot, only update if the active network has changed.
            if (activeNetwork == null) {
            //网络未联接
                mNetworkUnmetered = false;
                mNetworkConnected = false;
                updateTrackedJobs(userid);
            } else if (activeNetwork.getType() == networkType) {
                mNetworkUnmetered = false;
                mNetworkConnected = !intent.getBooleanExtra(
                        ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
                if (mNetworkConnected) {  // No point making the call if we know there's no conn.
                    mNetworkUnmetered = !connManager.isActiveNetworkMetered();
                }
            //更新工作任务
                updateTrackedJobs(userid);
            }
        } else {
            if (DEBUG) {
                Slog.d(TAG, "Unrecognised action in intent: " + action);
            }
        }
    }
};

@Override
public void dumpControllerState(PrintWriter pw) {
    pw.println("Conn.");
    pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered);
    for (JobStatus js: mTrackedJobs) {
        pw.println(String.valueOf(js.hashCode()).substring(0, 3) + ".."
                + ": C=" + js.hasConnectivityConstraint()
                + ", UM=" + js.hasUnmeteredConstraint());
    }
}
 }
Copy the code

ConnectivityController (updateTrackedJobs(userID)) is used to check whether the network has changed. A change will adjust mStateChangedListener. OnControllerStateChanged () method; This in turn calls the onControllerStateChanged method in the JobSchedulerService class:

  @Override
public void onControllerStateChanged() {
    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
Copy the code

In onControllerStateChanged approach through send message handler, and then call the maybeQueueReadyJobsForExecutionLockedH ();

private void maybeQueueReadyJobsForExecutionLockedH() { int chargingCount = 0; int idleCount = 0; int backoffCount = 0; int connectivityCount = 0; List<JobStatus> runnableJobs = new ArrayList<JobStatus>(); ArraySet<JobStatus> jobs = mJobs.getJobs(); for (int i=0; i<jobs.size(); i++) { JobStatus job = jobs.valueAt(i); if (isReadyToBeExecutedLocked(job)) { if (job.getNumFailures() > 0) { backoffCount++; } if (job.hasIdleConstraint()) { idleCount++; } if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) { connectivityCount++; } if (job.hasChargingConstraint()) { chargingCount++; } runnableJobs.add(job); } else if (isReadyToBeCancelledLocked(job)) { stopJobOnServiceContextLocked(job); } } if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT || chargingCount >= MIN_CHARGING_COUNT || runnableJobs.size() >= MIN_READY_JOBS_COUNT) { if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); } for (int i=0; i<runnableJobs.size(); Mpendingjobs.add (runnableJobs.get(I)); } } else { if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything."); } } if (DEBUG) { Slog.d(TAG, "idle=" + idleCount + " connectivity=" + connectivityCount + " charging=" + chargingCount + " tot=" + runnableJobs.size()); }}Copy the code

Add the pending work task to the collection using the above method, and then call maybeRunPendingJobsH(); I mentioned this before, I won’t say it again, same thing;

JobScheduler: Scheduler: Scheduler: Scheduler: Scheduler: Scheduler: Scheduler: Scheduler

I do Android development for many years, later will be updated on android advanced UI,NDK development, performance optimization and other articles, more please pay attention to my wechat public number: thank you!


image