版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
JobScheduler 是在 Android 21 新增加的特性,他可以把一些不是很紧急的任务,放到合适的时候进行批量处理,在电量优化中有很大的作用。
1.把任务整合起来,等到充电状态或者 Wifi 连接情况下进行执行,充电时候就不会在乎耗电量, Wifi 连接情况下比蜂窝网络省电。
2.在系统待机的时候,把任务整合到一起,一定时间批量处理,可以避免多次的唤醒 CPU,使 CPU 得不到休息,造成耗电。
JobSchedule 使用起来相对比较简单,首先需要自己实现一个 JobService。该 Service 运行在主线程中,考虑到这个情况,简单的在代码中添加一个 AsyncTask 进行配合。
MyJobService :
public class MyJobService extends JobService {
public static final String TAG = "MyJobService";
@Override
public boolean onStartJob(JobParameters params) {
//如果返回值是 false, 这个方法返回时任务已经执行完毕。
//如果返回值是 true, 那么这个任务正要被执行,我们就需要开始执行任务。
//当任务执行完毕时你需要调用 jobFinished(JobParameters params, boolean needsRescheduled)来通知系统
new MyAsyncTask().execute(params);
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
//这段注释转自 https://www.jianshu.com/p/1d4ebae39263,感觉作者写的很详细。
//当系统接收到一个取消请求时,系统会调用 onStopJob 方法取消正在等待执行的任务。
//其实 onStopJob 在 jobFinished 正常调用结束一个 job 时,也是不会调用的,
//只有在该 job 没有被执行完,就被 cancel 掉的时候回调到,
//比如某个 job 还没有执行就被 JobScheduler 给 Cancel 掉时,或者在某个运行条件不满足时。
//比如原来在 Wifi 环境允许的某个任务,执行过程中切换到了非Wifi场景,那也会调用该方法。
//该方法也返回一个 boolean 值,返回 true 表示会重新放到 JobScheduler 里 reScheduler,false 表示直接忽略。
return false;
}
class MyAsyncTask extends AsyncTask {
JobParameters jobParameters;
@Override
protected Void doInBackground(JobParameters[] objects) {
//做真正的处理
jobParameters = objects[0];
Log.i(TAG, jobParameters.getJobId() + " 任务开始执行......");
PersistableBundle extras = jobParameters.getExtras();
String location = extras.getString("data");
Log.i(TAG, jobParameters.getJobId() + " 上传:"+location);
return null;
}
@Override
protected void onPostExecute(Void s) {
//当任务执行完毕之后,需要调用 jobFinished 来让系统知道这个任务已经结束,
//系统可以将下一个任务添加到队列中
//true 表示需要重复执行
//false 反之
jobFinished(jobParameters, false);
Log.i(TAG, jobParameters.getJobId() + "任务执行完成......");
}
}
}
JobInfo 的使用
JobInfo jobInfo = new
JobInfo.Builder(0,
new ComponentName(context, MyJobService.class))
//只在充电的时候
.setRequiresCharging(true)
//不是蜂窝网络
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setExtras(extras).build();
使用 JobInfo.Builder 来创建一个 JobInfo,这边主要介绍一下 JobInfo.Builder 的几个方法。
setExtras(@NonNull PersistableBundle extras)
数据传递,设置要传递的数据,可以在 Sercice 中获取到。
setRequiredNetworkType(@NetworkType int networkType)
设置 JobInfo 启动时的网络环境。
/** Default.
默认,有没有网络都可以执行 */
public static final int NETWORK_TYPE_NONE = 0;
/** This job requires network connectivity.
任何网络情况下都可以 */
public static final int NETWORK_TYPE_ANY = 1;
/** This job requires network connectivity that is unmetered.
要求网络是非蜂窝网络 */
public static final int NETWORK_TYPE_UNMETERED = 2;
/** This job requires network connectivity that is not roaming.
要求网络是非漫游网络*/
public static final int NETWORK_TYPE_NOT_ROAMING = 3;
/** This job requires metered connectivity such as most cellular data networks.
要求网络是蜂窝网络 */
public static final int NETWORK_TYPE_METERED = 4;
setRequiresCharging(boolean requiresCharging)
设置 JobInfo 启动是否需要在充电情况下,true 表示需要。
setMinimumLatency(long minLatencyMillis)
设置 JobInfo 任务延迟执行时间。
setOverrideDeadline(long maxExecutionDelayMillis)
设置 JobInfo 任务最长延迟时间。
setPeriodic(long intervalMillis, long flexMillis)
设置任务时间间隔,不能与 setMinimumLatency 和 setOverrideDeadline 一起使用。
setRequiresDeviceIdle(boolean requiresDeviceIdle)
设置任务是否需要机器空闲,true 表示需要
setPersisted(boolean isPersisted)
设置机器重启后是否继续执行, true 表示继续。
setBackoffCriteria(long initialBackoffMillis, @BackoffPolicy int backoffPolicy)
设置 JobInfo 的重试机制和时间策略。
initialBackoffMillis 默认为 30s,最长可以设置为 5 小时。
backoffPolicy 有两个值,
//线性增长
public static final int BACKOFF_POLICY_LINEAR = 0;
//指数增长
public static final int BACKOFF_POLICY_EXPONENTIAL = 1;
public class JobManager {
private static volatile JobManager sigleton = null;
public static JobManager getInstance(){
if (sigleton == null) {
synchronized (JobManager.class) {
if (sigleton == null) {
sigleton = new JobManager();
}
}
}
return sigleton;
}
private JobScheduler scheduler;
private Context context;
public void init(Context context) {
this.context = context.getApplicationContext();
scheduler = (JobScheduler)context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
}
public void addJob(String msg){
if (scheduler == null) {
return;
}
List jobInfos = scheduler.getAllPendingJobs();
JobInfo jobInfo = null;
for (JobInfo info : jobInfos ) {
if (info.getId() == 0) {
jobInfo = info;
break;
}
}
//数据与 Intent 一样
PersistableBundle extras = jobInfo.getExtras();
String data = extras.getString("data");
data += "," + msg;
scheduler.cancel(0);
extras.putString("data", data);
jobInfo = new JobInfo.Builder(0, new ComponentName(context, MyJobService.class))
.setRequiresCharging(true) //只在充电状态下
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 非蜂窝网络
.setExtras(extras)
.build();
//提交任务
scheduler.schedule(jobInfo);
}
}
模拟定位信息收集,会把每次的定位信息进行拼接,直到符合执行 jobInfo 任务条件时,进行调用 MyJobService,在 MyJobService 中进行添加上送代码,实现上送。
JobSchedule 依赖于 JobSchedulerService 的系统服务。JobScheduler 在提交任务时候,调用的是 schedule 这个方法,通过 bundle 机制,最终会调用到 JobSchedulerService 的 schedule 方法。
JobSchedulerService 的 schedule:
public int schedule(JobInfo job, int uId) {
return scheduleAsPackage(job, uId, null, -1, null);
}
schedule 方法调用到了 scheduleAsPackage 方法,
JobSchedulerService 的 scheduleAsPackage :
public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
String tag) {
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
try {
if (ActivityManagerNative.getDefault().getAppStartMode(uId,
job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ " -- package not allowed to start");
return JobScheduler.RESULT_FAILURE;
}
} catch (RemoteException e) {
}
if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
JobStatus toCancel;
synchronized (mLock) {
// Jobs on behalf of others don't apply to the per-app job cap
if (ENFORCE_MAX_JOBS && packageName == null) {
if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
Slog.w(TAG, "Too many jobs for uid " + uId);
throw new IllegalStateException("Apps may not schedule more than "
+ MAX_JOBS_PER_APP + " distinct jobs");
}
}
toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
if (toCancel != null) {
cancelJobImpl(toCancel, jobStatus);
}
startTrackingJob(jobStatus, toCancel);
}
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
return JobScheduler.RESULT_SUCCESS;
}
JobSchedulerService 的 scheduleAsPackage 创建了一个 JobStatus ,然后调用 startTrackingJob 这个方法。
JobSchedulerService 的 startTrackingJob:
private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
synchronized (mLock) {
final boolean update = mJobs.add(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
if (update) {
controller.maybeStopTrackingJobLocked(jobStatus, null, true);
}
controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
}
}
}
}
startTrackingJob 中对一个全局变量 mControllers 进行遍历,我们先进行查看一下 mControllers 是什么东西。mControllers 的初始化,是在 JobSchedulerService 的构造函数中,可以很明显的发现,mControllers 里面存储的是网络、电池等控制器。
JobSchedulerService 的构造函数:
public JobSchedulerService(Context context) {
super(context);
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
mJobSchedulerStub = new JobSchedulerStub();
mJobs = JobStore.initAndGet(this);
// Create the controllers.
mControllers = new ArrayList();
mControllers.add(ConnectivityController.get(this));
mControllers.add(TimeController.get(this));
mControllers.add(IdleController.get(this));
mControllers.add(BatteryController.get(this));
mControllers.add(AppIdleController.get(this));
mControllers.add(ContentObserverController.get(this));
mControllers.add(DeviceIdleJobsController.get(this));
}
startTrackingJob 中遍历各个控制器,然后调用各个控制器的 maybeStartTrackingJobLocked 这个方法。
这边以 ConnectivityController 为例进行分析。我们查看 ConnectivityController 的 maybeStartTrackingJobLocked 方法。
ConnectivityController 的 maybeStartTrackingJobLocked:
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()
|| jobStatus.hasNotRoamingConstraint()) {
updateConstraintsSatisfied(jobStatus);
mTrackedJobs.add(jobStatus);
}
}
在 maybeStartTrackingJobLocked 方法里面就只有进行一个简单的吧 jobStatus 的状态添加到 mTrackedJobs集合中。
我们查看 ConnectivityController 的构造函数。
ConnectivityController 的构造函数:
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);
}
ConnectivityController 在构造函数中进行了一个网络改变的广播接收者 mConnectivityReceiver 的注册。
private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateTrackedJobs(-1);
}
};
当网络状态发生改变的时候,调用 updateTrackedJobs 方法。
ConnectivityController 的 updateTrackedJobs :
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();
}
}
}
updateTrackedJobs 的方法会调用的 mStateChangedListener 回调方法 onControllerStateChanged 进行回调。mStateChangedListener 是在构造函数中进行初始化,其实就是外部传递进来的 JobSchedulerService。
在 JobSchedulerService 的构造函数中我们可以发现, ConnectivityController 的创建是通过 ConnectivityController 的 get 方法进行获取,查看 get 方法。
* ConnectivityController 的 get :*
public static ConnectivityController get(JobSchedulerService jms) {
synchronized (sCreationLock) {
if (mSingleton == null) {
mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
}
return mSingleton;
}
}
在 get 中直接把参数 jms 作为 ConnectivityController 构造函数中的 stateChangedListener。
**注:**JobSchedulerService 实现了 StateChangedListener 接口, 所以,网络改变最终会调用到 JobSchedulerService 实现的 StateChangedListener 下的 onControllerStateChanged 方法。
JobSchedulerService 的 onControllerStateChanged:
public void onControllerStateChanged() {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
onControllerStateChanged 回调中就直接发送了一个 handle 的消息。mHandler 是一个 JobHandler,这个是 JobSchedulerService 的内部类。
JobHandler :
private class JobHandler extends Handler {
public JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
synchronized (mLock) {
if (!mReadyToRock) {
return;
}
}
switch (message.what) {
case MSG_JOB_EXPIRED:
synchronized (mLock) {
JobStatus runNow = (JobStatus) message.obj;
// runNow can be null, which is a controller's way of indicating that its
// state is such that all ready jobs should be run immediately.
if (runNow != null && !mPendingJobs.contains(runNow)
&& mJobs.containsJob(runNow)) {
mJobPackageTracker.notePending(runNow);
mPendingJobs.add(runNow);
}
queueReadyJobsForExecutionLockedH();
}
break;
case MSG_CHECK_JOB:
synchronized (mLock) {
if (mReportedActive) {
// if jobs are currently being run, queue all ready jobs for execution.
queueReadyJobsForExecutionLockedH();
} else {
// Check the list of jobs and run some of them if we feel inclined.
maybeQueueReadyJobsForExecutionLockedH();
}
}
break;
case MSG_CHECK_JOB_GREEDY:
synchronized (mLock) {
queueReadyJobsForExecutionLockedH();
}
break;
case MSG_STOP_JOB:
cancelJobImpl((JobStatus)message.obj, null);
break;
}
//真正执行任务
maybeRunPendingJobsH();
// Don't remove JOB_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_JOB);
}
...
}
这个用法跟我们自己定义的 handle 一样,主要代码是在 case MSG_CHECK_JOB 下。
case MSG_CHECK_JOB:
synchronized (mLock) {
if (mReportedActive) {
// if jobs are currently being run, queue all ready jobs for execution.
//如果当前有 job 正在运行,已经准备好的 job 将进行排队,等待执行。
queueReadyJobsForExecutionLockedH();
} else {
// Check the list of jobs and run some of them if we feel inclined.
//检查 jobs 的列表,如果满足条件,则执行。
maybeQueueReadyJobsForExecutionLockedH();
}
}
break;
没有 job 在执行的时候,走 else,调用 maybeQueueReadyJobsForExecutionLockedH,maybeQueueReadyJobsForExecutionLockedH 主要是进行状态的设置,以及把满足条件的广播添加到 mPendingJobs 中,
private void maybeQueueReadyJobsForExecutionLockedH() {
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
noteJobsNonpending(mPendingJobs);
//先进行清空
mPendingJobs.clear();
mJobs.forEachJob(mMaybeQueueFunctor);
//在这里面添加
mMaybeQueueFunctor.postProcess();
}
JobHandler 真正执行任务是在方法末尾的 maybeRunPendingJobsH 这个内部方法中。
JobHandler 的 maybeRunPendingJobsH:
private void maybeRunPendingJobsH() {
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
}
assignJobsToContextsLocked();
reportActive();
}
}
主要内容是在 assignJobsToContextsLocked 里面。
assignJobsToContextsLocked:
private void assignJobsToContextsLocked() {
if (DEBUG) {
Slog.d(TAG, printPendingQueue());
}
int memLevel;
try {
memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
} catch (RemoteException e) {
memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
}
switch (memLevel) {
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
break;
case ProcessStats.ADJ_MEM_FACTOR_LOW:
mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
break;
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
break;
default:
mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
break;
}
JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
boolean[] act = mTmpAssignAct;
int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
int numActive = 0;
int numForeground = 0;
for (int i=0; iget(i);
final JobStatus status = js.getRunningJob();
if ((contextIdToJobMap[i] = status) != null) {
numActive++;
if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
numForeground++;
}
}
act[i] = false;
preferredUidForContext[i] = js.getPreferredUid();
}
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
}
//这里开始遍历 mPendingJobs,也就是待执行的任务
for (int i=0; iget(i);
// If job is already running, go to next job.
int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
if (jobRunningContext != -1) {
continue;
}
final int priority = evaluateJobPriorityLocked(nextPending);
nextPending.lastEvaluatedPriority = priority;
// Find a context for nextPending. The context should be available OR
// it should have lowest priority among all running jobs
// (sharing the same Uid as nextPending)
int minPriority = Integer.MAX_VALUE;
int minPriorityContextId = -1;
for (int j=0; jint preferredUid = preferredUidForContext[j];
if (job == null) {
if ((numActive < mMaxActiveJobs ||
(priority >= JobInfo.PRIORITY_TOP_APP &&
numForeground < mConstants.FG_JOB_COUNT)) &&
(preferredUid == nextPending.getUid() ||
preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
minPriorityContextId = j;
break;
}
// No job on this context, but nextPending can't run here because
// the context has a preferred Uid or we have reached the limit on
// concurrent jobs.
continue;
}
if (job.getUid() != nextPending.getUid()) {
continue;
}
if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
continue;
}
if (minPriority > nextPending.lastEvaluatedPriority) {
minPriority = nextPending.lastEvaluatedPriority;
minPriorityContextId = j;
}
}
if (minPriorityContextId != -1) {
contextIdToJobMap[minPriorityContextId] = nextPending;
act[minPriorityContextId] = true;
numActive++;
if (priority >= JobInfo.PRIORITY_TOP_APP) {
numForeground++;
}
}
}
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
}
mJobPackageTracker.noteConcurrency(numActive, numForeground);
for (int i=0; ifalse;
if (act[i]) {
JobStatus js = mActiveServices.get(i).getRunningJob();
if (js != null) {
if (DEBUG) {
Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
}
// preferredUid will be set to uid of currently running job.
mActiveServices.get(i).preemptExecutingJob();
preservePreferredUid = true;
} else {
final JobStatus pendingJob = contextIdToJobMap[i];
if (DEBUG) {
Slog.d(TAG, "About to run job on context "
+ String.valueOf(i) + ", job: " + pendingJob);
}
for (int ic=0; icget(ic).prepareForExecutionLocked(pendingJob);
}
//真正的执行任务
if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
Slog.d(TAG, "Error executing " + pendingJob);
}
if (mPendingJobs.remove(pendingJob)) {
mJobPackageTracker.noteNonpending(pendingJob);
}
}
}
if (!preservePreferredUid) {
mActiveServices.get(i).clearPreferredUid();
}
}
}
真正执行任务是在 mActiveServices.get(i).executeRunnableJob(pendingJob) 里面,mActiveServices 是 List < JobServiceContext > ,我们继续查看 JobServiceContext 的代码。
JobServiceContext 的 executeRunnableJob:
boolean executeRunnableJob(JobStatus job) {
synchronized (mLock) {
if (!mAvailable) {
Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
return false;
}
mPreferredUid = NO_PREFERRED_UID;
mRunningJob = job;
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&
(job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
Uri[] triggeredUris = null;
if (job.changedUris != null) {
triggeredUris = new Uri[job.changedUris.size()];
job.changedUris.toArray(triggeredUris);
}
String[] triggeredAuthorities = null;
if (job.changedAuthorities != null) {
triggeredAuthorities = new String[job.changedAuthorities.size()];
job.changedAuthorities.toArray(triggeredAuthorities);
}
mParams = new JobParameters(this, job.getJobId(), job.getExtras(), isDeadlineExpired,
triggeredUris, triggeredAuthorities);
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
mVerb = VERB_BINDING;
scheduleOpTimeOut();
//创建一个 intent ,设置的 Component 是我们在创建 JobInfo 的时候传递的 Component。
final Intent intent = new Intent().setComponent(job.getServiceComponent());
//启动服务,就是我们设置的服务
boolean binding = mContext.bindServiceAsUser(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
new UserHandle(job.getUserId()));
if (!binding) {
if (DEBUG) {
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
}
mRunningJob = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
mVerb = VERB_FINISHED;
removeOpTimeOut();
return false;
}
try {
mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
mJobPackageTracker.noteActive(job);
mAvailable = false;
return true;
}
}
在 executeRunnableJob 里面创建 Intent,设置 Component 是我们在创建 JobInfo 的时候传递的 Component。然后进行绑定我们最初创建的 JobService。
总结:其实没有什么较复杂的内容,就是监听了一个网络改变的广播(或者其他的),当发现网络改变的时候,满足我们的条件,就会启动设置的 JobService。