定时执行周期任务之JobScheduler

JobScheduler是Android5.0及以后的版本由系统提供的用于解决高效处理计划后台工作系统服务 即使应用程序进程当前未运行,计划的任务也会运行,框架自动处理为计划作业获得唤醒锁的工作,从而甚至在设备空闲时,工作也会继续进行.

应用场景

- 后台周期性任务
- 非及时性工作如定期数据库数据更新
- 设备充电时需要执行的工作,一般是不需要经常执行但是比较耗时耗电的操作如上传一些资源,网络拉取一些统计数据之类的
- 设备空闲时(未激活,休眠时)需要执行的工作
- 特定网络状态时才需要执行的工作

适用版本

 Android 5.0以上即SDK>=21

优点

1. 避免频繁的唤醒硬件模块,造成不必要的电量消耗
2. 避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量
3. Android Framework层会尝试尽可能的**成批执行**操作,从而最大限度减少对设备电池和网络使用率的影响 

缺点

非严格的计时,即计时的默认操作可能是不准确的

看代码

WorkJobService

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class WorkJobService extends JobService {

    private static final int MSG_JOB = 1;

    private Handler mJobProcessor = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if(msg.what==MSG_JOB){
                JobParameters params = (JobParameters) msg.obj;
                /**
                 * 完成异步工作之后 必须触发jobFinished以使下一个计划任务运行
                 */
                doSomething();
                jobFinished(params, false);
            }
            return true;
        }
    });

    private void doSomething() {
        Toast.makeText(this, "time:" + System.currentTimeMillis(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        mJobProcessor.sendMessageDelayed(
                Message.obtain(mJobProcessor, MSG_JOB, params),
                5000);
        /**
         * 1.如果此处异步完成作业,false;
         * 2.如果需要执行更多后台工作,true;
         * 对于第二种情况必须调用jobFinished()以通知系统作业完成
         */
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        //停止作业时,必须取消挂起的作业
        mJobProcessor.removeMessages(MSG_JOB);
        /**
         * true计划作业
         * false删除作业
         */
        return false;
    }
}

注意:不要忘记在清单文件中注册,JobService本身继承Service,和普通的service区别是需要加上这个权限 android:permission="android.permission.BIND_JOB_SERVICE",否则在执行计划任务时会产生异常,我们需要将此服务暴露给framework,因此Android要求通过BIND_JOB_SERVICE权限保护该服务,只有framework可以拥有该权限,它保护我们的服务不被其他应用程序访问,

 <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:testOnly="false"
        android:theme="@style/AppTheme">
        ......
      <service android:name=".WorkJobService"
               android:permission="android.permission.BIND_JOB_SERVICE"/>
        ......
</application>

JobSchedulerActivity

public class JobSchedulerActivity extends AppCompatActivity {
    JobScheduler jobScheduler;
    int interval = 5000;
    public static final int JOB_ID = 1;
    private JobInfo mJobInfo;

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_job_scheduler);
        jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        mJobInfo = new JobInfo.Builder(
                JOB_ID,
                new ComponentName(getPackageName(), WorkJobService.class.getName())
        )
                /**
                 * 表示应在给定时间间隔内定期运行作业,直到其被明确取消
                 */
                .setPeriodic(interval)
                /**
                 * 描述作业运行必备的网络条件
                 * NETWORK_TYPE_NONE = 0 默认值,表示作业不需要网络访问
                 * NETWORK_TYPE_ANY = 1 需要网络连接
                 * NETWORK_TYPE_UNMETERED = 2 必须是wifi网络状态,否则不会触发作业
                 */
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                /**
                 * 作业运行,必须时在设备充电状态
                 * 适合较少执行,但是比较耗电的任务
                 */
                .setRequiresCharging(true)
                /**
                 * 作业运行,必须时在设备未激活或睡眠状态
                 * 适合较少执行,但是比较耗电的任务
                 */
                .setRequiresDeviceIdle(true)
                /**
                 * 设置设备重启时,如何启动计划作业
                 * true 由framework自动启动计划作业
                 * false 由应用手动启动计划作业(默认)
                 */
                .setPersisted(false)
                /**
                 * 设置对于失败的作业如何何时重新计划使其再次运行
                 * 如:在资源(网络访问等)临时不可用时尽可能减少不必要的重试次数
                 */
                .setBackoffCriteria(DEFAULT_INITIAL_BACKOFF_MILLIS, BACKOFF_POLICY_EXPONENTIAL)
                .build();
    }


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void start(View view) {
        /**
         * 多次执行同一个任务返回同一个任务id,不会重复执行任务
         */
        int result = jobScheduler.schedule(mJobInfo);
        if (result <= 0) {
            Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(this, "成功", Toast.LENGTH_SHORT).show();
        }
    }
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void stop(View view) {
        /**
         * 任务id必须匹配传递给JobInfo的id,需要保存该id
         */
        jobScheduler.cancel(JOB_ID);
    }
}