Android Open Source - JobSchedulerCompat Job Scheduler Service






From Project

Back to project page JobSchedulerCompat.

License

The source code is released under:

Apache License

If you think the Android project JobSchedulerCompat listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package me.tatarka.support.internal.job;
/*  w w w. j  a va  2 s  .  c o  m*/
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;

import me.tatarka.support.internal.receivers.IdleReceiver;
import me.tatarka.support.job.IJobCallback;
import me.tatarka.support.job.IJobService;
import me.tatarka.support.job.JobInfo;
import me.tatarka.support.job.JobParameters;
import me.tatarka.support.internal.receivers.JobStatus;
import me.tatarka.support.internal.receivers.TimeReceiver;

/**
 * @hide
 */
public class JobSchedulerService extends Service {
    static final String TAG = "JobServiceSchedulerService";

    private static final String EXTRA_MSG = "EXTRA_MSG";
    private static final String EXTRA_JOB_ID = "EXTRA_JOB_ID";

    private static final int MSG_START_JOB = 0;
    private static final int MSG_STOP_JOB = 1;
    private static final int MSG_STOP_ALL = 2;
    private static final int MSG_RECHECK_CONSTRAINTS = 3;

    private SparseArray<JobServiceConnection> runningJobs = new SparseArray<JobServiceConnection>();

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int what = intent.getIntExtra(EXTRA_MSG, -1);
        Message message = Message.obtain(handler, what, intent);
        handler.handleMessage(message);
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void handleStartJob(int jobId) {
        if (runningJobs.get(jobId) != null) {
            // Job already running!
            return;
        }

        JobStore jobStore = JobStore.initAndGet(this);
        JobStatus job;
        synchronized (jobStore) {
            job = jobStore.getJobByJobId(jobId);
        }

        if (job == null) {
            // Attempting to start a non-existent job, it may have already been canceled.
            return;
        }

        Intent intent = new Intent();
        intent.setComponent(job.getJob().getService());

        int flags = BIND_AUTO_CREATE;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            flags |= BIND_WAIVE_PRIORITY;
        }

        JobServiceConnection connection = new JobServiceConnection(job);
        runningJobs.put(job.getJobId(), connection);
        bindService(intent, connection, flags);
    }

    private void handleStopJob(int jobId) {
        JobServiceConnection connection = runningJobs.get(jobId);
        if (connection != null) {
            connection.stop(false);
        }
    }

    private void handleStopAll() {
        int size = runningJobs.size();
        for (int i = 0; i < size; i++) {
            int jobId = runningJobs.keyAt(i);
            JobServiceConnection connection = runningJobs.get(jobId);
            connection.stop(false);
        }
    }

    private void handleRecheckConstraints() {
        int size = runningJobs.size();
        for (int i = 0; i < size; i++) {
            int jobId = runningJobs.keyAt(i);
            JobServiceConnection connection = runningJobs.get(jobId);
            if (!connection.job.isConstraintsSatisfied()) {
                connection.stop(true);
            }
        }
    }

    private class JobServiceConnection implements ServiceConnection {
        JobStatus job;
        JobParameters jobParams;
        IJobService jobService;
        boolean allowReschedule = true;

        JobServiceConnection(final JobStatus job) {
            this.job = job;
            this.jobParams = new JobParameters(new IJobCallback.Stub() {
                @Override
                public void jobFinished(int jobId, boolean needsReschedule) throws RemoteException {
                    finishJob(jobId, JobServiceConnection.this);

                    if (allowReschedule) {
                        if (needsReschedule || job.getJob().isPeriodic()) {
                            rescheduleJob(job, needsReschedule);
                        }
                    }
                    stopIfFinished();
                }

                @Override
                public void acknowledgeStartMessage(int jobId, boolean workOngoing) throws RemoteException {
                    if (!workOngoing) {
                        finishJob(jobId, JobServiceConnection.this);
                        stopIfFinished();
                    }
                }

                @Override
                public void acknowledgeStopMessage(int jobId, boolean reschedule) throws RemoteException {
                    finishJob(jobId, JobServiceConnection.this);

                    if (allowReschedule) {
                        if (reschedule || job.getJob().isPeriodic()) {
                            rescheduleJob(job, reschedule);
                        }
                    }
                    stopIfFinished();
                }
            }, job.getJobId(), job.getExtras(), !job.isConstraintsSatisfied());
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            jobService = (IJobService) service;

            try {
                jobService.startJob(jobParams);
            } catch (Exception e) {
                Log.e(TAG, "Error while starting job: " + job.getJob());
                throw new RuntimeException(e);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            runningJobs.remove(job.getJobId());
            jobService = null;
            jobParams = null;
            stopIfFinished();
        }

        void stop(boolean allowReschedule) {
            if (jobService != null) {
                this.allowReschedule = allowReschedule;
                try {
                    jobService.stopJob(jobParams);
                } catch (Exception e) {
                    Log.e(TAG, "Error while stopping job: " + job.getJobId());
                    throw new RuntimeException(e);
                }
            }
        }
    }

    static void startJob(Context context, int jobId) {
        context.startService(new Intent(context, JobSchedulerService.class)
                .putExtra(EXTRA_MSG, MSG_START_JOB)
                .putExtra(EXTRA_JOB_ID, jobId));
    }

    static void stopJob(Context context, int jobId) {
        context.startService(new Intent(context, JobSchedulerService.class)
                .putExtra(EXTRA_MSG, MSG_STOP_JOB)
                .putExtra(EXTRA_JOB_ID, jobId));
    }

    static void stopAll(Context context) {
        context.startService(new Intent(context, JobSchedulerService.class)
                .putExtra(EXTRA_MSG, MSG_STOP_ALL));
    }

    static void recheckConstraints(Context context) {
        context.startService(new Intent(context, JobSchedulerService.class)
                .putExtra(EXTRA_MSG, MSG_RECHECK_CONSTRAINTS));
    }

    private void finishJob(int jobId, JobServiceConnection connection) {
        if (runningJobs.get(jobId) != null) {
            unbindService(connection);
        }
        runningJobs.remove(jobId);
        JobStore jobStore = JobStore.initAndGet(this);
        synchronized (jobStore) {
            jobStore.remove(connection.job);
        }
    }

    private void rescheduleJob(JobStatus job, boolean wasFailure) {
        JobStatus newJob;
        if (wasFailure) {
            newJob = rescheduleFailedJob(job);
        } else {
            newJob = reschedulePeriodicJob(job);
        }

        JobStore jobStore = JobStore.initAndGet(this);
        synchronized (jobStore) {
            jobStore.remove(job);
            jobStore.add(newJob);
        }

        TimeReceiver.setAlarmsForJob(this, newJob);
        if (job.hasIdleConstraint()) {
            IdleReceiver.setIdleForJob(this, newJob);
        }
    }

    /**
     * A job is rescheduled with exponential back-off if the client requests this from their
     * execution logic.
     * A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the
     * timeliness of the reschedule. For an idle-mode job, no deadline is given.
     * @return A newly instantiated JobStatus with the same constraints as the last job except
     * with adjusted timing constraints.
     */
    private JobStatus rescheduleFailedJob(JobStatus job) {
        if (job.hasIdleConstraint()) {
            // Don't need to modify time on idle job, it will run whenever the next idle period is.
            return job;
        }
        
        final long elapsedNowMillis = SystemClock.elapsedRealtime();
        final JobInfo jobInfo = job.getJob();

        final long initialBackoffMillis = jobInfo.getInitialBackoffMillis();
        final int backoffAttemps = job.getNumFailures() + 1;

        long delayMillis;
        switch (job.getJob().getBackoffPolicy()) {
            case JobInfo.BACKOFF_POLICY_LINEAR:
                delayMillis = initialBackoffMillis * backoffAttemps;
                break;
            default:
            case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
                delayMillis = (long) Math.scalb(initialBackoffMillis, backoffAttemps - 1);
                break;
        }
        delayMillis = Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
        return new JobStatus(job, elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, backoffAttemps);
    }

    /**
     * Called after a periodic has executed so we can to re-add it. We take the last execution time
     * of the job to be the time of completion (i.e. the time at which this function is called).
     * This could be inaccurate b/c the job can run for as long as
     * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
     * to underscheduling at least, rather than if we had taken the last execution time to be the
     * start of the execution.
     * @return A new job representing the execution criteria for this instantiation of the
     * recurring job.
     */
    private JobStatus reschedulePeriodicJob(JobStatus job) {
        final long elapsedNow = SystemClock.elapsedRealtime();
        // Compute how much of the period is remaining.
        long runEarly = Math.max(job.getLatestRunTimeElapsed() - elapsedNow, 0);
        long newEarliestRunTimeElapsed = elapsedNow + runEarly;
        long period = job.getJob().getIntervalMillis();
        long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
        return new JobStatus(job, newEarliestRunTimeElapsed,
                newLatestRuntimeElapsed, 0 /* backoffAttempt */);
    }

    private void stopIfFinished() {
        if (runningJobs.size() == 0) {
            JobServiceCompat.jobsFinished(this);
            stopSelf();
        }
    }

    private final Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            Intent intent = (Intent) msg.obj;
            switch (msg.what) {
                case MSG_START_JOB: {
                    int jobId = intent.getIntExtra(EXTRA_JOB_ID, 0);
                    handleStartJob(jobId);
                    break;
                }
                case MSG_STOP_JOB: {
                    int jobId = intent.getIntExtra(EXTRA_JOB_ID, 0);
                    handleStopJob(jobId);
                    break;
                }
                case MSG_STOP_ALL: {
                    handleStopAll();
                    break;
                }
                case MSG_RECHECK_CONSTRAINTS: {
                    handleRecheckConstraints();
                    break;
                }
            }
        }
    };
}




Java Source Code List

me.tatarka.support.internal.IJobServiceCompat.java
me.tatarka.support.internal.IoThread.java
me.tatarka.support.internal.JobSchedulerCompat.java
me.tatarka.support.internal.JobSchedulerLollipopDelegate.java
me.tatarka.support.internal.job.JobSchedulerService.java
me.tatarka.support.internal.job.JobServiceCompat.java
me.tatarka.support.internal.job.JobStore.java
me.tatarka.support.internal.receivers.BootReceiver.java
me.tatarka.support.internal.receivers.ControllerPrefs.java
me.tatarka.support.internal.receivers.IdleReceiver.java
me.tatarka.support.internal.receivers.JobStatus.java
me.tatarka.support.internal.receivers.NetworkReceiver.java
me.tatarka.support.internal.receivers.PowerReceiver.java
me.tatarka.support.internal.receivers.ReceiverUtils.java
me.tatarka.support.internal.receivers.TimeReceiver.java
me.tatarka.support.internal.util.ArraySet.java
me.tatarka.support.internal.util.ContainerHelpers.java
me.tatarka.support.internal.util.EmptyArray.java
me.tatarka.support.internal.util.FastXmlSerializer.java
me.tatarka.support.internal.util.MapCollections.java
me.tatarka.support.internal.util.XmlUtils.java
me.tatarka.support.job.ApplicationTest.java
me.tatarka.support.job.JobInfo.java
me.tatarka.support.job.JobParameters.java
me.tatarka.support.job.JobScheduler.java
me.tatarka.support.job.JobService.java
me.tatarka.support.job.sample.MainActivity.java
me.tatarka.support.job.sample.service.TestJobService.java
me.tatarka.support.os.PersistableBundleCompat.java
me.tatarka.support.os.PersistableBundle.java