Advanced Android - detailed explanation of the use of JobScheduler mechanism for more power-saving background tasks

introduction

After Android 5.0 System, in order to improve the use fluency and extend the battery life, Google introduced the mechanism that the system will recycle the application and automatically destroy the Service pulled up by the application in the application background / lock screen. At the same time, in order to meet the needs of triggering the execution of some tasks under specific conditions (such as network, charging state, power, time, cycle, etc.), JobScheduler mechanism came into being. In a word, JobScheduler is the best choice for tasks triggered by certain predetermined conditions.

1, Overview of JobScheduler mechanism

In the jobscheduler mechanism, each business requiring background is abstracted as a Job. Through system management Job, we can improve resource utilization and reduce unnecessary wake-up, so as to improve performance and save power. When the system starts, it will pass the system_ The server process starts the * * JobSchedulerService * * service. When using this mechanism, first construct a specific background task object through JobInfo and pass it to the background task scheduler through jobscheduler. When the configured conditions are met, the system will execute the corresponding Job on the corresponding JobService. In short, the system provides a background task that can be executed periodically without the developer waking up. It will be executed automatically when the configured conditions are met.

2, JobSchedulerService service

The JobSchedulerService knows nothing about constraints, or the state of active jobs. It receives callbacks from the various controllers and completed jobs and operates accordingly.

From the JobScheduler instance obtained by (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE), we can know that JobSchedulerService also runs in the background in the form of system service. JobSchedulerService does not know the status and constraints of jobs, and processes all kinds of jobs through the callback of various controller s.

1. The start of the schedulerservice is triggered

On COM android. server. Systemserver #startotherservices method

 mSystemServiceManager.startService(JobSchedulerService.class);

The way in which SystemServiceManager starts all system core services is similar. Basically, it constructs related objects according to the incoming class bytecode type newInstance reflection, registers them in the system service list, and then triggers its corresponding onStart method to start the corresponding services.

com.android.server.SystemServiceManager#startService(java.lang.Class)

public <T extends SystemService> T startService(Class<T> serviceClass) {
     try {
         final String name = serviceClass.getName(); 
         final T service;
             Constructor<T> constructor = serviceClass.getConstructor(Context.class);
             service = constructor.newInstance(mContext);//The mscontext passed into the SystemServiceManager reflects the construction of the JobSchedulerService object
				...
         // {@ link ArrayList<SystemService> mServices}Register it.
         mServices.add(service);
         // Start it.  Start JobSchedulerService 
         service.onStart();
         return service;
     }
 }

2. Construction of JobSchedulerService object

 public final class JobSchedulerService extends com.android.server.SystemService
        implements StateChangedListener, JobCompletedListener{
        ...
 public JobSchedulerService(Context context) {
        super(context);
        mHandler = new JobHandler(context.getMainLooper());//Using system_ The Looper of the main thread in the server process initializes the JobHandler
        mConstants = new Constants(mHandler);
        mJobSchedulerStub = new JobSchedulerStub();//Create corresponding Binder server
        mJobs = JobStore.initAndGet(this);

        // Create the controllers.
        mControllers = new ArrayList<StateController>();
        mControllers.add(ConnectivityController.get(this));//Register to listen for broadcast of network connection status
        mControllers.add(TimeController.get(this));//Register to listen to the broadcast when the Job time expires
        mControllers.add(IdleController.get(this));//Registration monitoring screen on / off, dream in / out, broadcast of status change
        mBatteryController = BatteryController.get(this);//Register to monitor whether the battery is charged and the broadcast of power status
        mControllers.add(mBatteryController);
        mStorageController = StorageController.get(this);
        mControllers.add(mStorageController);
        mControllers.add(AppIdleController.get(this));//Monitor whether the app is idle
        mControllers.add(ContentObserverController.get(this));//Listen for ContentObserver event broadcast
        mControllers.add(DeviceIdleJobsController.get(this));//Monitoring device idle broadcast
    }
     
         @Override
    public void onControllerStateChanged() {
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    }

    @Override
    public void onRunJobNow(JobStatus jobStatus) {
        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
    }
    @Override
    public void onStart() {
        publishLocalService(JobSchedulerInternal.class, new LocalService());
        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
    }

JobSchedulerService inherits from SystemService class and implements StateChangedListener and JobCompletedListener interfaces. The construction method mainly completes four things during execution.

2.1. Use system_ The JobHandler was initialized by Looper, the main thread of the server process

The process runs on the main thread, so it can't do time-consuming operations.

//com.android.server.job.JobSchedulerService.JobHandler 
final private class JobHandler extends Handler {
        public JobHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message message) {
            synchronized (mLock) {
                if (!mReadyToRock) {//phase == PHASE_ THIRD_ PARTY_ APPS_ CAN_ mReadyToRock is true at start to run the third-party App
                    return;
                }
                switch (message.what) {
                    case MSG_JOB_EXPIRED: {
							...
                    } break;
                    case MSG_CHECK_JOB:
                        if (mReportedActive) {
                            // if jobs are currently being run, queue all ready jobs for execution.
                            queueReadyJobsForExecutionLocked();
                        } else {
                            // Check the list of jobs and run some of them if we feel inclined.
                            maybeQueueReadyJobsForExecutionLocked();
                        }
                        break;
                    case MSG_CHECK_JOB_GREEDY:
                        queueReadyJobsForExecutionLocked();
                        break;
                    case MSG_STOP_JOB:
                        cancelJobImplLocked((JobStatus) message.obj, null,
                                "app no longer allowed to run");
                        break;
                }
                maybeRunPendingJobsLocked();
                // Don't remove JOB_EXPIRED in case one came along while processing the queue.
                removeMessages(MSG_CHECK_JOB);
            }
        }
    }

2.2. Create the corresponding Binder server of JobSchedulerService

    /**
     * Binder stub trampoline implementation
     */
    final class JobSchedulerStub extends IJobScheduler.Stub {
      
        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();

        // IJobScheduler implementation
        @Override
        public int schedule(JobInfo job) throws RemoteException {
			. . . 
            try {
                return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        // IJobScheduler implementation
        @Override
        public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
			...
            long ident = Binder.clearCallingIdentity();
            try {
                return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override
        public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
                throws RemoteException {
			...
            try {
                return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
                        packageName, userId, tag);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override
        public List<JobInfo> getAllPendingJobs() throws RemoteException {

            try {
                return JobSchedulerService.this.getPendingJobs(uid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override
        public JobInfo getPendingJob(int jobId) throws RemoteException {
            final int uid = Binder.getCallingUid();
            long ident = Binder.clearCallingIdentity();
            try {
                return JobSchedulerService.this.getPendingJob(uid, jobId);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override
        public void cancelAll() throws RemoteException {
			...
            try {
                JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app");
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override
        public void cancel(int jobId) throws RemoteException {
			...
            try {
                JobSchedulerService.this.cancelJob(uid, jobId);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    };

2.3. Create a persistence related JobStore

When the JobStore object is constructed, it will create / data / system / job / jobs XML files, which may have been stored before, will also parse the XML files, create JobInfo and, and convert them into corresponding JobStatus. Finally, all jobstatuses are saved to the JobSet set set, which is why the JobScheduler can be persistent.

The JobStatus object records the jobId, ComponentName, uid, tag and failure times of the task.

  private JobStore(Context context, Object lock, File dataDir) {
        File systemDir = new File(dataDir, "system");
        File jobDir = new File(systemDir, "job");
        jobDir.mkdirs();
        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
        mJobSet = new JobSet();
        readJobMapFromDisk(mJobSet);
    }

// frameworks/base/services/core/java/com/android/server/job/JobStore.java
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
static JobStore initAndGet(JobSchedulerService jobManagerService) {
  synchronized (sSingletonLock) {
    if (sSingleton == null) {
      sSingleton = new JobStore(jobManagerService.getContext(),
                                jobManagerService.getLock(), 									Environment.getDataDirectory());
    }
    return sSingleton;
  }
}

2.4. Create and register listener StateController of preset conditions

Create and register listeners with preset conditions. Statecontroller is still implemented internally through broadcasting. It listens to the corresponding broadcasting and notifies the listener. When the conditions are met, it will send corresponding messages through the Handler to trigger task execution.

Statecontroller typeexplain
ConnectivityControllerRegister to listen for broadcast of network connection status
TimeControllerRegister to listen to the broadcast when the job time expires
IdleControllerRegistration monitoring screen on / off, dream in / out, broadcast of status change
BatteryControllerRegister to monitor whether the battery is charged and the broadcast of power status
AppIdleControllerMonitor whether the app is idle
ContentObserverControllerMonitor the change of content URIs through ContentObserver
DeviceIdleJobsControllerSet constraints for app according to doze status.
public class ConnectivityController extends StateController implements
        ConnectivityManager.OnNetworkActiveListener {

    private final ConnectivityManager mConnManager;
    /** Singleton. */
    private static ConnectivityController mSingleton;
    private ConnectivityController(StateChangedListener stateChangedListener, Context context,
            Object lock) {
        super(stateChangedListener, context, lock);

        mConnManager = mContext.getSystemService(ConnectivityManager.class);
        mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);

        final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        mContext.registerReceiverAsUser(
                mConnectivityReceiver, UserHandle.SYSTEM, intentFilter, null, null);

        mNetPolicyManager.registerListener(mNetPolicyListener);//Active monitoring network
    }
	...
    /**
     * Update all jobs tracked by this controller.
     * @param uid only update jobs belonging to this UID, or {@code -1} to update all tracked jobs.
     */
    private void updateTrackedJobs(int uid) {
        synchronized (mLock) {
            boolean changed = false;
            for (int i = 0; i < mTrackedJobs.size(); i++) {
                final JobStatus js = mTrackedJobs.get(i);
                if (uid == -1 || uid == js.getSourceUid()) {
                    changed |= updateConstraintsSatisfied(js);
                }
            }
            if (changed) {
                mStateChangedListener.onControllerStateChanged();
            }
        }
    }

    /**
     * We know the network has just come up. We want to run any jobs that are ready.
     */
    @Override
    public synchronized void onNetworkActive() {
        synchronized (mLock) {
            for (int i = 0; i < mTrackedJobs.size(); i++) {
                final JobStatus js = mTrackedJobs.get(i);
                if (js.isReady()) {
                    mStateChangedListener.onRunJobNow(js);
                }
            }
        }
    }
    private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            updateTrackedJobs(-1);
        }
    };
}

After Android O banned the sending of some broadcasts, these controllers dynamically registered the broadcasts. These controllers triggered the corresponding preset callback interface and handed it over to JobScheduler for processing

/**
 * Is it charging
 */
public static boolean isCharging(Context context){
    //Register a broadcast that contains the state of charge and is a continuous broadcast
    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent intent = context.registerReceiver(null,filter);
    //Get charge status
    int isPlugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    boolean acPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_AC;
    boolean usbPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_USB;
    boolean wifiPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
    return acPlugged || usbPlugged || wifiPlugged;
}

3. Real start of JobSchedulerService

Like other system services, it performs publishing so that other applications can use the service directly through Binder.

    @Override
    public void onStart() {
        publishLocalService(JobSchedulerInternal.class, new LocalService());
        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
    }

Finally, the registration is completed in the static code block of system service registry. You can see that when the client requests to obtain a JOB_SCHEDULER_SERVICE service, which returns the JobSchedulerImpl instance inherited from JobScheduler.

        registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
                new StaticServiceFetcher<JobScheduler>() {
            @Override
            public JobScheduler createService() {
                IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE);
                return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
            }});

At this point, the JobSchedulerService service is started.

3, JobService

Entry point for the callback from the {@link android.app.job.JobScheduler}

The abstract class JobService inherits from the Service class. When the JobScheduler detects that the system state reaches the corresponding start condition, it will start JobService to execute tasks. Therefore, we need to inherit JobService and create a Service inherited from JobService, and we must implement two methods: onStartJob(JobParameters params) and onStopJob(JobParameters params).

public abstract class JobService extends Service {
    final JobHandler mHandler;
    final JobSchedulerStub mJobSchedulerStub;
    
    IJobService mBinder = new IJobService.Stub() {
        public void startJob(JobParameters jobParams) {
            ensureHandler();
            //Send msg to the Handler of the main thread_ EXECUTE_ Job message
            Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams);
            m.sendToTarget();
        }
        public void stopJob(JobParameters jobParams) {
            ensureHandler();
            //Send msg to the Handler of the main thread_ STOP_ Job message
            Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams);
            m.sendToTarget();
        }
    };
    void ensureHandler() {
       synchronized (mHandlerLock) {
           if (mHandler == null) {
               mHandler = new JobHandler(getMainLooper());
           }
       }
   }
   public final IBinder onBind(Intent intent) {
      return mBinder.asBinder();
  }
	...
}

When JobService runs in the App process, mHandler is the Handler associated with the main thread of the App process. When sending a message to the main thread to start and stop the task, the onStartJob and onStopJob methods will be called back respectively.

1,boolean OnStartJob(JobParameters params)

When the job starts to execute, the onStartJob(JobParameters params) method (used by the system to trigger the executed task) will be triggered and a boolean value will be returned. If the value is false, the system will think that the task has been executed when it returns; If true is returned, the system task is about to be executed. Therefore, when the task is completed, you need to call jobFinished(JobParameters params, boolean needsRescheduled) to notify the system.

  • If the return value is false, the system assumes that any task does not take a long time to run and has been completed by the time the method returns.
  • **If the return value is true, the system assumes that the task takes some time and needs to be executed in our own application** When a given task is completed, you need to call the jobFinished(JobParameters params, boolean needsRescheduled) method to stop the task and inform the system that the task has been processed.

In other words, the onStartJob method is called when the system determines that the constraints are met, and we can execute our business logic here.

2,boolean OnStopJob(JobParameters params)

When the system receives a cancellation request, it will trigger the onStopJob(JobParameters params) method to cancel the task waiting to be executed, and also return a boolean value. It is important that if onStartJob(JobParameters params) returns false, the system assumes that there is no running task when it receives a cancellation request. In other words, onStopJob(JobParameters params) will not be called in this case. When receiving a cancellation request, onStopJob(JobParameters params) is used by the system to cancel the suspended task. If onStartJob(JobParameters params) returns false, when the cancellation request is received, the system assumes that there is no currently running work, and it does not call onStopJob(JobParameters params) at all. Therefore, we need to manually call the jobFinished (JobParameters params, boolean needsReschedule) method.

It should be noted that onStartJob and onStopJob methods run in the main thread. We can't do time-consuming operations in them, otherwise it may lead to ANR. We can use another thread Handler or tasks with longer running time to process asynchronous tasks. Therefore, a Handler or AsyncTask is usually created in the above customized JobService class to handle the Job that needs to be performed.

3,void jobFinished (JobParameters params, boolean needsReschedule)

The callback notifies the JobManager that execution has completed, which can be called from any thread because it will eventually run on the main thread of the application. When the system receives this message, it will release the wakeup being saved.

  • params -- the param passed in should be consistent with the param in onStartJob
  • Reschedule the work according to the condition of reschedule. Otherwise, pass in false to let the system know whether the task should be repeated under the initial conditions**

When the task is completed, you need to call jobfinished (jobparameters params, Boolean needs rescheduled) to let the system know which task has been completed, and it can start queuing the next operation. If this is not done, the work will run only once and the application will not be allowed to perform additional work.

4, JobScheduler

This is an API for scheduling various types of jobs against the framework that will be executed in your application's own process.

From the perspective of code, JobScheduler provides us developers with a series of API s for managing and scheduling JobInfo. At the same time, from another perspective, it is also an Android system service - the binder object related to JobScheduler service. From the above, we know that the implementation class of JobScheduler is JobSchedulerImpl, which mainly calls com.com through binder android. server. job. JobSchedulerService. Binder remote interface method in jobschedulerstub.

methodparameterexplain
int schedule(JobInfo job)Corresponding task informationAdd the task to be executed to the scheduling set. If the jobId carried in the passed in parameter already exists, the job with the old ID will be overwritten. If the job with the passed in jobId is currently running, it will be stopped. If the schedule method fails, it will return an error code less than 0. Otherwise it will return to us in jobinfo ID defined in builder.
void cancel(int jobId)jobIdCancel the job corresponding to jobId. If the task is currently executing, it will stop immediately and the return value of its Job Service#onStopjob (Job Parameters) method will be ignored.
void cancelAll()\Cancel all the jobs configured by the current application. Cancelall() is cautious, because the function of this method is to cancel all the jobs under the uid. That is, when multiple apps pass through shareUid, executing cancelall() in any app will cancel all the jobs in the apps under the same uid
List< JobInfo > getAllPendingJobs()\Get all jobs configured by the current application
JobInfo getPendingJob(int jobId)jobIdGet the Job corresponding to jobId
public class JobSchedulerImpl extends JobScheduler {
    IJobScheduler mBinder;

    /* package */ JobSchedulerImpl(IJobScheduler binder) {
        mBinder = binder;
    }

    @Override
    public int schedule(JobInfo job) {
        try {
            //{@link com.android.server.job.JobSchedulerService.JobSchedulerStub#schedule}
            return mBinder.schedule(job);
        } catch (RemoteException e) {
            return JobScheduler.RESULT_FAILURE;
        }
    }

    @Override
    public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag) {
        return mBinder.scheduleAsPackage(job, packageName, userId, tag);
    }

    @Override
    public void cancel(int jobId) {
        mBinder.cancel(jobId);
    }

    @Override
    public void cancelAll() {
       mBinder.cancelAll();
    }

    @Override
    public List<JobInfo> getAllPendingJobs() {
        return mBinder.getAllPendingJobs();
    }

    @Override
    public JobInfo getPendingJob(int jobId) {
        return mBinder.getPendingJob(jobId);
    }
}

1,JobSchedulerService.JobSchedulerStub#schedule

In JobSchedulerImpl, call the schedule method through mBinder, and then pass it to the Binder object JobSchedulerStub of JobSchedulerService server

// IJobScheduler implementation
@Override
public int schedule(JobInfo job) throws RemoteException {
	return JobSchedulerService.this.schedule(job, uid);
}

Call jobschedulerservice in JobSchedulerStub this. Schedule method,

//@link com.android.server.job.JobSchedulerService.JobSchedulerStub#schedule
 public int schedule(JobInfo job, int uId) {
     return scheduleAsPackage(job, uId, null, -1, null);
 }


    public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
            String tag) {
        //Create JobStatus
        JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
        JobStatus toCancel;
        synchronized (mLock) {
            //Cancel the task under the uid first
            toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
            if (toCancel != null) {
                cancelJobImpl(toCancel, jobStatus);
            }
            //Start tracking the task
            startTrackingJob(jobStatus, toCancel);
        }
        //To system_ The main thread of the server process sends a message
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
        return JobScheduler.RESULT_SUCCESS;
    }

Then the Message is processed through JobHandler, and other processes are similar.

5, Jobinfo & jobinfo Builder

JobInfo is the encapsulation of task subject information, such as task execution conditions, bound JobService class name, policy, retry policy, task execution time, persistence, etc. Through the constructor pattern JobInfo When builder constructs JobInfo, it needs to pass in a jobId and the bound JobService class name, where jobId is the unique flag of the Job, and then cancel the Job through this jobId.

Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the parameters required to schedule work against the calling application.

JobInfo.Builder member methodparameterexplainremarks
addTriggerContentUri(JobInfo.TriggerContentUri uri)Add a TriggerContentUri (the principle is to use ContentObserver to monitor a Content Uri), which will trigger the execution of the task when and only when it changes.In order to continuously monitor the change of Content, a new task needs to be scheduled after the latest task is triggered (trigger URI cannot be combined with setPeriodic(long) or setpersistent (Boolean)). To continuously monitor changes in Content, you need to schedule new JobInfo to monitor the same URI before completing JobService's latest changes. Because setting this property is not compatible with periodic or persistent jobs, doing so will throw an IllegalArgumentException exception when calling build()
setBackoffCriteria(long mills, int policy)mills indicates the time interval of the first retry attempt, and policy indicates the retry policySet the fallback / retry strategy, which is similar to the conflict backoff in the network principle. When a task fails to be scheduled, it needs to be retriedThe preset time interval is: DEFAULT_INITIAL_BACKOFF_MILLIS 30000 and MAX_BACKOFF_DELAY_MILLIS 18000000
The default strategies are: backoff_ POLICY_ Explicit binary backoff, the waiting interval increases exponentially and BACKOFF_POLICY_LINEAR
setExtras(PersistableBundle extras)Data attached to JobThe additional data attached to the setting, similar to the function of Bundle, is persistent, so only the original type is allowed.You can use pendingjob Getextras() get
setMinimumLatency(long minLatencyMillis)Set the delayed execution time (MS) of the task, which is equivalent to post delay.
setOverrideDeadline(long maxDeadlineMillis)Set the latest delay time of the task. If other conditions are not met at the specified time, the task will also be started.
setPeriodic(long ms)Set the running cycle of the task, i.e. once every X milliseconds
setPeriodic(long intervalMillis, long flexMillis)Set a flex length window at the end of the Job cycle, and tasks may be executed.Android API 24 and above
setPersisted(boolean isPersisted)Set whether persistence is supported. After the device is restarted, the system determines whether to continue the corresponding task according to the value.
setRequiredNetworkType(int networkType)Configure network conditions for task executionNETWORK_TYPE_NONE - the default choice. This task will be executed regardless of whether there is a network or not
NETWORK_TYPE_ANY -- any kind of network is needed to make the task executable.
NETWORK_TYPE_UNMETERED - the task will be executed only when it is not a cellular network (such as when WIFI is connected)
setRequiresCharging(boolean requiresCharging)This task will only be performed when the device is charged. This is not only inserted into the charger, but also triggered when the battery is in a healthy state,Generally speaking, the power of mobile phone is more than 15%
setRequiresDeviceIdle(boolean isDeviceIdle)Specify that the Job can run only in the idle state, and execute the work after the device is in the screen off or dreaming state (similar to the sleep animation state of window) for 71 minutes.
setTransientExtras(Bundle extras)Set the additional data that the job can carry, similar to the function of Intent carrying Bundle.Android API 26 and above
setTriggerContentMaxDelay(long durationMs)Set the maximum total delay (in milliseconds) allowed from the first detected Content change to the Job, and set the maximum delay between the Content change and the execution of the task.Android API 24 and above
setTriggerContentMaxDelay(long durationMs)Set the maximum total delay (in milliseconds) allowed from the first detected Content change to the Job, and set the maximum delay between the Content change and the execution of the task.Android API 24 and above
setExtras(PersistableBundle extra)Set the additional data that the job can carry, similar to the function of Intent carrying Bundle.

6, Steps for using JobScheduler

1. Inherit JobService and override onStartJob and onStopJob methods to implement customized JobService

After the job is started, the onStartJob method will be called. Because the JobService runs on the main thread, it is necessary to use a sub thread, Handler or an asynchronous task to run time-consuming operations to prevent blocking the main thread. If you want to perform time-consuming operations, you need to create a thread to do it; If onStartJob executes a time-consuming task, it can return false, indicating that the task execution is over. If onStartJob starts a thread to execute a time-consuming task, it must return true, indicating that the task is still executing. After the task is really finished, manually call the JobFinished() method to tell the system that the task has ended.

1.1. Handle time-consuming operations in the form of Handler

public class JobServiceWithHandler  extends JobService {
    
    private Messenger mActivityMessenger;
     @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mActivityMessenger = intent.getParcelableExtra(MESSENGER_INTENT_KEY);
        return START_NOT_STICKY;
    }
    
    private final Handler mJobHandler = new Handler( new Handler.Callback() {
        @Override
        public boolean handleMessage( Message msg ) {
            //TODO business operation
            //...
            jobFinished( (JobParameters) msg.obj, false );//Notify after task execution
            return true;
        }
    } );

    @Override
    public boolean onStartJob(JobParameters params) {
        mJobHandler.sendMessage( Message.obtain( mJobHandler, 1, params ) );
        
        /* GOOGLE The official website demo simulates cross process communication through Messenger
         sendMessage(MSG_COLOR_START, 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(MSG_COLOR_STOP, params.getJobId());
                jobFinished(params, false);
            }
        }, duration);*/

        
  /*      new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //Do . . . . 
                jobFinished(params, false);
            }
        }, duration);
	*/
        //When onStartJob returns true, it means that the time-consuming operation takes longer events than the execution of onStartJob, and it means that the jobFinished method must be called manually at the right time, otherwise other jobs in the application will not be executed
        return true;
    }
    @Override
    public boolean onStopJob(JobParameters params) {
        mJobHandler.removeMessages( 1 );
        //sendMessage(MSG_COLOR_STOP, params.getJobId());
        // When the system receives a request to cancel a Job and the Job is still executing (onStartJob returns true), the system will call the onStopJob method. However, no matter whether onStopJob is called or not, the system will cancel the Job as long as it receives the cancellation request
        // true requires retrying, false does not retry discarding the job
        return false;
    }
    
     private void sendMessage(int messageID, @Nullable Object params) {
        if (mActivityMessenger == null) {
            return;
        }
        Message m = Message.obtain();
        m.what = messageID;
        m.obj = params;
        mActivityMessenger.send(m);
    } 
}

Need to go to androidmanifest Add a service node in XML to let your application have the permission to bind and use this JobService.

<service android:name=".JobServiceWithHandler"
    android:permission="android.permission.BIND_JOB_SERVICE" />

1.2. Handle time-consuming operations in combination with AsyncTask

public class JobServiceWithAsyncTask extends JobService {
    private JobParameters mJobParameters;
    private final AsyncTask<Void, Void, Void> mTask = new AsyncTask<Void, Void, Void>() {

        @Override
        protected Void doInBackground(Void... params) {
            // TODO time consuming operation
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            // Inform the system after the TODO time-consuming operation is completed
            jobFinished(mJobParameters, true);
            super.onPostExecute(result);
        }
    };

    @Override
    public boolean onStartJob(JobParameters params) {
        // Return true, indicating that the work is time-consuming and needs to call jobFinished to destroy after the work processing is completed
        mJobParameters = params;
        mTask.execute();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

You also need to register in the manifest file

<service android:name=".JobServiceWithAsyncTask"
    android:permission="android.permission.BIND_JOB_SERVICE" />

2. Get instance of JobScheduler

JobScheduler mJobScheduler = (JobScheduler) getSystemService( Context.JOB_SCHEDULER_SERVICE );

3. Build job JobInfo object preset trigger condition binding JobService

Use JobInfo Builder to build a JobInfo object and bind the customized JobService.

// jobId : 0
PersistableBundle extras = new PersistableBundle();
extras.putString("DATA","xxxx");
//Create a job
JobInfo jobInfo = new
    JobInfo.Builder(0,new ComponentName(context, JobServiceWithHandler.class))
    //bInfo.Builder(0,new ComponentName(context, JobServiceWithAsyncTask.class))
    //Only when charging
    .setRequiresCharging(true)
    //Not a cellular network
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
    .setExtras(extras).build();

4. Schedule the specified job through the JobScheduler object instance

  //Submit task
if( mJobScheduler.schedule( jobInfo) <= 0 ) {
   //If something goes wrong
}

After submitting, wait for the conditions to be met, and the system will execute automatically.

Keywords: Android job

Added by klance on Fri, 04 Mar 2022 18:18:33 +0200