JSS Article 5 - Job Scheduler Service - Job Finished

Keywords: Java Android

Source Code Analysis Based on Android 7.1.1

Preface

When the task is completed, the application needs to call the jobFinished method manually, which belongs to JobService:
  1. public final void jobFinished(JobParameters params, boolean needsReschedule) {
  2. ensureHandler();
  3. Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);
  4. m.arg2 = needsReschedule ? 1 : 0;
  5. m.sendToTarget();
  6. }
In fact, the parameters are very simple. This message MSG_JOB_FINISHED will be sent to JobHandler!

1 JS.JobHandler

Go into JobHandler and see:
  1. class JobHandler extends Handler {
  2. JobHandler(Looper looper) {
  3. super(looper);
  4. }
  5. @Override
  6. public void handleMessage(Message msg) {
  7. final JobParameters params = (JobParameters) msg.obj;
  8. switch (msg.what) {
  9. ... ... ... ...
  10. case MSG_JOB_FINISHED:
  11. final boolean needsReschedule = (msg.arg2 == 1);
  12. // callback is JobService Context
  13. IJobCallback callback = params.getCallback();
  14. if (callback != null) {
  15. try {
  16. callback.jobFinished(params.getJobId(), needsReschedule);
  17. } catch (RemoteException e) {
  18. Log.e(TAG, "Error reporting job finish to system: binder has gone" + " away.");
  19. }
  20. } else {
  21. Log.e(TAG, "finishJob() called for a nonexistent job id.");
  22. }
  23. break;
  24. default:
  25. Log.e(TAG, "Unrecognised message received.");
  26. break;
  27. }
  28. }
  29. }
JObServiceContext's jobFinished method is called here!

2 JSC.jobFinished

Let's look at the jobFinished method of JobService Context:
  1. @Override
  2. public void jobFinished(int jobId, boolean reschedule) {
  3. if (!verifyCallingUid()) {
  4. return;
  5. }
  6. mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
  7. .sendToTarget();
  8. }
Here we call JobService Context's verifyCalling Uid to verify uid!
  1. private boolean verifyCallingUid() {
  2. synchronized (mLock) {
  3. if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
  4. if (DEBUG) {
  5. Slog.d(TAG, "Stale callback received, ignoring.");
  6. }
  7. return false;
  8. }
  9. return true;
  10. }
  11. }
Then a message from MSG_CALLBACK was sent to JobService Handler!

3 JSC.JobServiceHandler

Then we go to JobService Handler to see the main code:
  1. case MSG_CALLBACK:
  2. if (DEBUG) {
  3. Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
  4. + " v:" + VERB_STRINGS[mVerb]);
  5. }
  6. removeOpTimeOut();
  7. if (mVerb == VERB_STARTING) {
  8. final boolean workOngoing = message.arg2 == 1;
  9. handleStartedH(workOngoing);
  10. } else if (mVerb == VERB_EXECUTING || // At this point the state of mVerb is VERB_EXECUTING
  11. mVerb == VERB_STOPPING) {
  12. final boolean reschedule = message.arg2 == 1;
  13. handleFinishedH(reschedule);
  14. } else {
  15. if (DEBUG) {
  16. Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
  17. }
  18. }
  19. break;
Called handleFinishedH:
  1. private void handleFinishedH(boolean reschedule) {
  2. switch (mVerb) {
  3. case VERB_EXECUTING:
  4. case VERB_STOPPING:
  5. closeAndCleanupJobH(reschedule);
  6. break;
  7. default:
  8. Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
  9. "executed. Was " + VERB_STRINGS[mVerb] + ".");
  10. }
  11. }
It's easy to call close AndCleanup JobH to un bind and restore the initialization of JObService Context!
  1. private void closeAndCleanupJobH(boolean reschedule) {
  2. final JobStatus completedJob;
  3. synchronized (mLock) {
  4. if (mVerb == VERB_FINISHED) {
  5. return;
  6. }
  7. completedJob = mRunningJob;
  8. mJobPackageTracker.noteInactive(completedJob);
  9. try {
  10. mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
  11. mRunningJob.getSourceUid());
  12. } catch (RemoteException e) {
  13. // Whatever.
  14. }
  15. if (mWakeLock != null) {
  16. mWakeLock.release();
  17. }
  18. mContext.unbindService(JobServiceContext.this);
  19. mWakeLock = null;
  20. mRunningJob = null;
  21. mParams = null;
  22. mVerb = VERB_FINISHED; // State changed to VERB_FINISHED
  23. mCancelled.set(false);
  24. service = null;
  25. mAvailable = true;
  26. }
  27. removeOpTimeOut();
  28. removeMessages(MSG_CALLBACK);
  29. removeMessages(MSG_SERVICE_BOUND);
  30. removeMessages(MSG_CANCEL);
  31. removeMessages(MSG_SHUTDOWN_EXECUTION);
  32. // Notify Job Scheduler Service that the job has been finished
  33. mCompletedListener.onJobCompleted(completedJob, reschedule);
  34. }
  35. }
Next, we enter the JobScheduler Service method:

4 JSS.onJobCompleted

The onJobCompleted method is called to
  1. @Override
  2. public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
  3. if (DEBUG) {
  4. Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
  5. }
  6. // Do not write back immediately if this is a periodic job. The job may get lost if system
  7. // shuts down before it is added back.
  8. // Stop track job, this is the first stop, so remove job from JobStore!
  9. if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
  10. if (DEBUG) {
  11. Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
  12. }
  13. // We still want to check for jobs to execute, because this job may have
  14. // scheduled a new job under the same job id, and now we can run it.
  15. mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
  16. return;
  17. }
  18. // Note: there is a small window of time in here where, when rescheduling a job,
  19. // we will stop monitoring its content providers. This should be fixed by stopping
  20. // the old job after scheduling the new one, but since we have no lock held here
  21. // that may cause ordering problems if the app removes jobStatus while in here.
  22. // Determine whether to reschedule the job and, if necessary, start Tracking Job
  23. if (needsReschedule) {
  24. JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
  25. startTrackingJob(rescheduled, jobStatus);
  26. } else if (jobStatus.getJob().isPeriodic()) {
  27. JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
  28. startTrackingJob(rescheduledPeriodic, jobStatus);
  29. }
  30. reportActive();
  31. // Continue to start a new round of job execution!
  32. mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
  33. }
This is the same as cancel, so I won't go into details here!

































Posted by php12342005 on Tue, 12 Feb 2019 18:30:20 -0800