您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

JobScheduler的使用

时间:02-05来源:作者:点击数:

在一篇关于网络优化的博文中发现了这个词。我们的项目中并没有使用到这个东西,但看了一下还是非常好用的,mark一下以后一定会用到吧。

贴官方文档,喜欢自学的可以过去看看

贴官方Demo,官方给出的例子也是非常好的,里面用到了Messager,以后会对它进行介绍。


这个类有什么用呢? 我们想象一种情景:

当且仅当设备在空闲状态, 并且使用wifi时, 自动下载新的Apk。

也许我们可能会这么做: 注册一个广播接受者 , 监听屏幕熄灭状态 , 熄灭之后检查网络状态 , 然后再在广播接受者中启动一个服务去下载新的apk。可以是可以,但是这个会不会过于复杂了。而且如果出现需求变更......

比如希望在特定情况下再启动事务,比如说延迟若干时间之后,或者等手机空闲了再运行,这样一方面不会在系统资源紧张之时喧宾夺主,另一方面也起到削峰填谷提高系统效率的作用。对于这些额外的条件要求,Service并不能直接支持,往往需要加入其他手段,才能较好地满足相关的运行条件

  1. 对于延迟时间执行,通常考虑利用系统的闹钟管理器AlarmManager进行定时管理
  2. 对于是否联网、是否充电、是否空闲,一般要监听系统的相应广播,常见的系统广播说明如下:
  • 网络状态变化需要监听系统广播android.net.conn.CONNECTIVITY_CHANGE;
  • 设备是否充电需要监听系统广播Intent.ACTION_POWER_CONNECTED也就是android.intent.action.ACTION_POWER_CONNECTED;
  • 设备是否空闲需要监听系统广播Intent.ACTION_SCREEN_OFF也就是android.intent.action.SCREEN_OFF;

Android从5.0开始,增加支持一种特殊的机制,即任务调度JobScheduler,该工具集成了常见的几种运行条件,开发者只需添加少数几行代码,即可完成原来要多种组件配合的工作,使代码变得更加优(牛)雅(x)。

认识一下必须要用的工具
  1. JobScheduler: 任务调度器
  2. JobInfo : 任务概要信息
  3. JobService: 任务服务,描述具体逻辑
简单使用JobScheduler
  1. 创建JobService
    我们具体的业务逻辑还是要写在jobService中的, 所以自定义一个服务继承自JobService 并重写两个抽象方法

onStartJob:在任务开始执行时触发。返回false表示执行完毕,返回true表示需要开发者自己调用jobFinished方法通知系统已执行完成。

onStopJob,在任务停止执行时触发。

  1. Activity中配置JobInfo
    JobInfo是从来描述任务的执行时间,条件,策略等一系列的行为,使用Builder模式来获取实例,这里摘一下官方给出的代码
JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);

        String delay = mDelayEditText.getText().toString();
        if (!TextUtils.isEmpty(delay)) {
            //设置至少延迟多久后执行,单位毫秒.  
            builder.setMinimumLatency(Long.valueOf(delay) * 1000);
        }
        String deadline = mDeadlineEditText.getText().toString();
        if (!TextUtils.isEmpty(deadline)) {
            //设置最多延迟多久后执行,单位毫秒。
            builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
        }
        boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
        boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
        if (requiresUnmetered) {
        //设置需要的网络条件,有三个取值:
        //JobInfo.NETWORK_TYPE_NONE(无网络时执行,默认)、
        //JobInfo.NETWORK_TYPE_ANY(有网络时执行)、
        //JobInfo.NETWORK_TYPE_UNMETERED(网络无需付费时执行) 
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
        } else if (requiresAnyConnectivity) {  
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
        }
        //是否在空闲时执行
        builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
        
        //是否在充电时执行 
       builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());

        // Extras, work duration.
        PersistableBundle extras = new PersistableBundle();
        String workDuration = mDurationTimeEditText.getText().toString();
        if (TextUtils.isEmpty(workDuration)) {
            workDuration = "1";
        }
        extras.putLong(WORK_DURATION_KEY, Long.valueOf(workDuration) * 1000);

        builder.setExtras(extras);

        // Schedule job
        Log.d(TAG, "Scheduling job");
        JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.schedule(builder.build());
  • setRequiredNetworkType:设置需要的网络条件,有三个取值:-
    JobInfo.NETWORK_TYPE_NONE(无网络时执行,默认)、JobInfo.NETWORK_TYPE_ANY(有网络时执行)、JobInfo.NETWORK_TYPE_UNMETERED(网络无需付费时执行)
  • setPersisted:重启后是否还要继续执行,此时需要声明权限RECEIVE_BOOT_COMPLETED,否则会报错“java.lang.IllegalArgumentException: Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission.”而且RECEIVE_BOOT_COMPLETED需要在安装的时候就要声明,如果一开始没声明,而在升级时才声明,那么依然会报权限不足的错误。
  • setRequiresCharging:是否在充电时执行
  • setRequiresDeviceIdle:是否在空闲时执行
  • setPeriodic:设置时间间隔,单位毫秒。该方法不能和
    setMinimumLatency、setOverrideDeadline这两个同时调用,否则会报错“java.lang.IllegalArgumentException: Can't call setMinimumLatency() on a periodic job”,或者报错“java.lang.IllegalArgumentException: Can't call setOverrideDeadline() on a periodic job”。
  • setMinimumLatency:设置至少延迟多久后执行,单位毫秒。
  • setOverrideDeadline:设置最多延迟多久后执行,单位毫秒。
  • setBackoffCriteria: 退避策略 , 可以设置等待时间以及重连策略
  • build:完成条件设置,返回构建好的JobInfo对象。
  1. 声明MyJobService并将jobinfo加入, 执行
    就是上面代码的最后两句了, 这里也可以看到JobScheduler 的本质其实就是系统的服务。

这里吧把官方demo中的JobService拿出来说一下吧,其大致流程就是我们使用JobScheduler的方法。它本质上是一个Service

public class MyJobService extends JobService {

    private static final String TAG = MyJobService.class.getSimpleName();

    private Messenger mActivityMessenger;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Service created");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Service destroyed");
    }

    /**
     * When the app's MainActivity is created, it starts this service. This is so that the
     * activity and this service can communicate back and forth. See "setUiCallback()"
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mActivityMessenger = intent.getParcelableExtra(MESSENGER_INTENT_KEY);
        return START_NOT_STICKY;
    }

    @Override
    public boolean onStartJob(final JobParameters params) {
        // The work that this service "does" is simply wait for a certain duration and finish
        // the job (on another thread).

        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);
        Log.i(TAG, "on start job: " + params.getJobId());

        // Return true as there's more work to be done with this job.
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // Stop tracking these job parameters, as we've 'finished' executing.
        sendMessage(MSG_COLOR_STOP, params.getJobId());
        Log.i(TAG, "on stop job: " + params.getJobId());

        // Return false to drop the job.
        return false;
    }

    private void sendMessage(int messageID, @Nullable Object params) {
        // If this service is launched by the JobScheduler, there's no callback Messenger. It
        // only exists when the MainActivity calls startService() with the callback in the Intent.
        if (mActivityMessenger == null) {
            Log.d(TAG, "Service is bound, not started. There's no callback to send a message to.");
            return;
        }
        Message m = Message.obtain();
        m.what = messageID;
        m.obj = params;
        try {
            mActivityMessenger.send(m);
        } catch (RemoteException e) {
            Log.e(TAG, "Error passing service object back to activity.");
        }
    }
}

首先看必须重写的两个方法:

onStartJob和onStopJob,

  • 在onStartJob方法中使用Messager发送了一条消息,Tag为MSG_COLOR_START, 我们暂时先不管消息发送到了哪里,先宏观的看一下整个流程。然后发送了一个延时的消息MSG_COLOR_STOP,最后调用jobFinished方法结束。
  • 在onStopJob方法中,直接发送了一条MSG_COLOR_STOP的消息

看来这里发送消息应该是重头戏了。

首先回到Activity中,我们在onStart方法中找到了这样的代码

Intent startServiceIntent = new Intent(this, MyJobService.class);
Messenger messengerIncoming = new Messenger(mHandler);
startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming);
startService(startServiceIntent);

在Messager创建的时候就将一个handler作为参数来构造,之后作为extra来启动了服务。所以我们就能在服务中心的onStartCommand方法中获取到Messager的实例。接下来在服务的sendMessage方法中,也是直接用到了获取到的messager来发送消息,那肯定是发送到MainActivity传入的Handler中了,这里面的代码就不赘述了,都懂得。


实现过程的源码

JobScheduler的创建, 是使用的getSystemService , 我们首先点开JobScheduler类 , 尼玛居然是一个抽象类? 喵喵喵?怎么办呢? 可以去翻一下getSystemService 方法 , emm你会很惊喜的

既然是获取系统服, 按照规范, 其命名方式一定是xxxService , 所以我们直接去源码包下面搜JobScheduler, 看看会不会有什么发现

0

通过这种方式 , 我们可以找到获取到的真正的服务: JobSchedulerService

但是问题来了, 我们的App是一个进程 , 系统服务是在系统的进程, 两者之间怎么进行通讯呢? 对了 , 进程间通讯 , 一定是用到了AIDL

继续在JobSchedulerService中找 , 在第762行会发现

final class JobSchedulerStub extends IJobScheduler.Stub

那么根据AIDL的规范 , 一定有 IJobScheduler对应的.aidl文件

打开这个文件 IJobScheduler.aidl :

interface IJobScheduler {
    int schedule(in JobInfo job);
    void cancel(int jobId);
    void cancelAll();
    List<JobInfo> getAllPendingJobs();
}

定义了aidl之后会自动生成相应的接口类

既然这个接口的存根(Stub)被继承 , 自然会重写接口中的方法 ,这些重写方法可以在JobSchedulerService$JobSchedulerStub中找到. 所以 , 我们在使用JobScheduler的一些列方法都应该是在这里了吧 , 但是JobScheduler和JobScheduler和JobSchedulerService是怎么关联在一起的呢?

因为JobScheduler是一个抽象类 , 必然有实现类 , 按照规范 , 明明应该为xxxImpl. 我们在之前的搜索中已经看到过它了 , 接下来点开看看详情

public class JobSchedulerImpl extends JobScheduler

恩 , 没毛病就是它了 , 实现类当然会实现抽象方法中的抽象方法了

在看构造方法 :

IJobScheduler mBinder;

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

这里甚至直接命名为binder , 和我们绑定服务的套路一模一样:

bindService中 , 重写onServiceConnected , 将传入的iBinder对象调用asInterface方法 ,就能获得aidl对应的接口实现 , 也就可以调用对应接口实现的方法.

再回到内部类JobSchedulerStub 中: 首先我们来看Scheduler方法

@Override
        public int schedule(JobInfo job) throws RemoteException {
            if (DEBUG) {
                Slog.d(TAG, "Scheduling job: " + job.toString());
            }
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();

            enforceValidJobRequest(uid, job);
            if (job.isPersisted()) {
                if (!canPersistJobs(pid, uid)) {
                    throw new IllegalArgumentException("Error: requested job be persisted without"
                            + " holding RECEIVE_BOOT_COMPLETED permission.");
                }
            }

            long ident = Binder.clearCallingIdentity();
            try {
                return JobSchedulerService.this.schedule(job, uid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

在针对pid和uid进行一系列的验证之后, 最终还是调用了外部类的scheduler方法:

/**
     * Entry point from client to schedule the provided job.
     * This cancels the job if it's already been scheduled, and replaces it with the one provided.
     * @param job JobInfo object containing execution parameters
     * @param uId The package identifier of the application this job is for.
     * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
     */
public int schedule(JobInfo job, int uId) {
        JobStatus jobStatus = new JobStatus(job, uId);
        cancelJob(uId, job.getId());
        startTrackingJob(jobStatus);
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
        return JobScheduler.RESULT_SUCCESS;
    }

大家看下注释中的解释 , 大致了解一下流程

这里重点说一下startTrackingJob方法

private void startTrackingJob(JobStatus jobStatus) {
        boolean update;
        boolean rocking;
        synchronized (mJobs) {
            update = mJobs.add(jobStatus);
            rocking = mReadyToRock;
        }
        if (rocking) {
            for (int i=0; i<mControllers.size(); i++) {
                StateController controller = mControllers.get(i);
                if (update) {
                    controller.maybeStopTrackingJob(jobStatus);
                }
                controller.maybeStartTrackingJob(jobStatus);
            }
        }
    }

首先将jobStatus添加到JobStore中 , 根据是否添加成功 , 来决定是否执行

controller.maybeStopTrackingJob(jobStatus); 这里也循环遍历了控制器 . 乍一看是不是懵逼了 , 什么狗东西?

这里要提一下系统服务的创建过程了

Zygote--Linux的核心

启动系统进程SystemServer时 , 就会创建一些关键的服务,比如AMS,PMS,WMS等,其中包括JobSchedulerService。我们来看一下SystemServer这个类,一共一千多行不是很难。

首先这是一个java程序,自然要先去找main函数,然后一步一步跟。

在run方法中,我们可以看到一大堆的设置,往后直接找服务相关的(267行),三种服务:

startBootstrapServices();
startCoreServices();
startOtherServices();

我们在startOtherServices中可以发现,众多的服务都是通过SystemServiceManager去开启,它通过类名来反射构造器来实例化相应的Service,然后添加到系统服务集合中,最后启动服务。

我们在开发中使用getSystemService的方式得到的服务,就是从刚刚提到了系统服务集合中获取的

既然是从构造器来实例化服务的,所以我们再回到JobSchedulerService看他的构造函数,发现在构造函数中,构造器的集合添加了一堆构造器,然后hander分发事件,读取本地的文件(data/system/job/jobs.xml)并执行任务。

最终在maybeRunPendingJobsH()方法中,调用了executeRunnableJob方法,这个方法在JobServiceContext中。该方法中有onServiceConnected方法,即建立链接。同时也有很多很熟悉的方法,就不一一列举了。

在服务建立链接的同时,还进行防止睡眠等wakeLock操作,emmm,坏坏的。接下来使用Handler发送了消息,what值为MSG_SERVICE_BOUND, 跟进去方法, 最后我们还能看到service.startJob的操作, 即命令服务执行一个任务。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门