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:
public final void jobFinished(JobParameters params, boolean needsReschedule) {
ensureHandler();
Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);
m.arg2 = needsReschedule ? 1 : 0;
m.sendToTarget();
}
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:
class JobHandler extends Handler {
JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
- ... ... ... ...
case MSG_JOB_FINISHED:
final boolean needsReschedule = (msg.arg2 == 1);
// callback is JobService Context
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
" away.");}
} else {
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
default:
Log.e(TAG, "Unrecognised message received.");
break;
}
}
}
JObServiceContext's jobFinished method is called here!
2 JSC.jobFinished
Let's look at the jobFinished method of JobService Context:
@Override
public void jobFinished(int jobId, boolean reschedule) {
if (!verifyCallingUid()) {
return;
}
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
.sendToTarget();
}
Here we call JobService Context's verifyCalling Uid to verify uid!
private boolean verifyCallingUid() {
synchronized (mLock) {
if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
if (DEBUG) {
Slog.d(TAG, "Stale callback received, ignoring.");
}
return false;
}
return true;
}
}
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:
case MSG_CALLBACK:
if (DEBUG) {
Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
+ " v:" + VERB_STRINGS[mVerb]);
}
removeOpTimeOut();
if (mVerb == VERB_STARTING) {
final boolean workOngoing = message.arg2 == 1;
handleStartedH(workOngoing);
} else if (mVerb == VERB_EXECUTING || // At this point the state of mVerb is VERB_EXECUTING
mVerb == VERB_STOPPING) {
final boolean reschedule = message.arg2 == 1;
handleFinishedH(reschedule);
} else {
if (DEBUG) {
Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
}
}
break;
Called handleFinishedH:
private void handleFinishedH(boolean reschedule) {
switch (mVerb) {
case VERB_EXECUTING:
case VERB_STOPPING:
closeAndCleanupJobH(reschedule);
break;
default:
Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
"executed. Was " + VERB_STRINGS[mVerb] + ".");
}
}
It's easy to call close AndCleanup JobH to un bind and restore the initialization of JObService Context!
private void closeAndCleanupJobH(boolean reschedule) {
final JobStatus completedJob;
synchronized (mLock) {
if (mVerb == VERB_FINISHED) {
return;
}
completedJob = mRunningJob;
mJobPackageTracker.noteInactive(completedJob);
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
mRunningJob.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
if (mWakeLock != null) {
mWakeLock.release();
}
mContext.unbindService(JobServiceContext.this);
mWakeLock = null;
mRunningJob = null;
mParams = null;
mVerb = VERB_FINISHED; // State changed to
VERB_FINISHEDmCancelled.set(false);
service = null;
mAvailable = true;
}
removeOpTimeOut();
removeMessages(MSG_CALLBACK);
removeMessages(MSG_SERVICE_BOUND);
removeMessages(MSG_CANCEL);
removeMessages(MSG_SHUTDOWN_EXECUTION);
// Notify Job Scheduler Service that the job has been finished
mCompletedListener.onJobCompleted(completedJob, reschedule);
}
}
Next, we enter the JobScheduler Service method:
4 JSS.onJobCompleted
The onJobCompleted method is called to
@Override
public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
if (DEBUG) {
Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
}
// Do not write back immediately if this is a periodic job. The job may get lost if system
// shuts down before it is added back.
// Stop track job, this is the first stop, so remove job from JobStore!
if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
if (DEBUG) {
Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
}
// We still want to check for jobs to execute, because this job may have
// scheduled a new job under the same job id, and now we can run it.
mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
return;
}
// Note: there is a small window of time in here where, when rescheduling a job,
// we will stop monitoring its content providers. This should be fixed by stopping
// the old job after scheduling the new one, but since we have no lock held here
// that may cause ordering problems if the app removes jobStatus while in here.
// Determine whether to reschedule the job and, if necessary, start Tracking Job
if (needsReschedule) {
JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
startTrackingJob(rescheduled, jobStatus);
} else if (jobStatus.getJob().isPeriodic()) {
JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
startTrackingJob(rescheduledPeriodic, jobStatus);
}
reportActive();
// Continue to start a new round of job execution!
mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
}
This is the same as cancel, so I won't go into details here!