JobScheduler的使用

在一篇关于网络优化的博文中发现了这个词。我们的项目中并没有使用到这个东西,但看了一下还是非常好用的,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, 看看会不会有什么发现



通过这种方式 , 我们可以找到获取到的真正的服务: 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的操作, 即命令服务执行一个任务。

这样就将整个流程大致穿起来了,细节的地方大家自行查看吧,我觉得基本o98k,看源码看困了,睡会去- --

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容