使用JobScheduler解决后台Service限制

6,807 阅读5分钟

Android O对后台Service限制 这篇文章说明了Android O版本对后台Service的限制,并且在文章的末尾提到了解决方案,那就是使用JobScheduler。

JobScheduler的作用

从Android O(8.0, API 26)开始,系统只允许前台app创建和使用后台Service。这样就可以限制后台Service无限运行,从而可以提升设备性能和用户体验。

JobScheduler是一种替代方案,它可以在某些条件满足的情况下执行任务。例如当网络连接上,并且处于充电状态,应用可以向云端同步数据。又例如可以监听ContentProvider的URI,当它发生改变时,执行一些任务。而且更牛逼的是,即使应用不处于前台甚至没有运行的情况下,当条件满足时,还是可以执行任务。

使用JobScheduler

本文将使用JobScheduler来完成下载图片的任务,并将图片显示到界面上。

创建JobService

JobScheduler是使用JobService来执行任务的,JobService是一个标准的Service,它是一个抽象类,我们需要创建一个它的子类

public class MyJobService extends JobService {
    @Override
    public boolean onStartJob(JobParameters params) {
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

onStartJob()被调用时,表明任务开始执行,你需要在这个方法中实现执行任务的逻辑。但是呢,它是在主线程中执行的,所以你如果执行一些耗时的操作,你需要把任务放到工作线程中执行,可以考虑使用Thread/Handler/AsyncTask。

onStartJob()的返回值我们需要注意,如果返回true,表明任务还要继续执行,例如我们开了一个工作线程与云端服务器同步数据,这个任务非常耗时,所以通过让onStartJob()返回true,表明后台任务还在继续执行。

但是任务总有执行完成的时刻,此时我们需要调用jobFinished()方法来告诉系统任务已经完成。

如果onStartJob()返回false,那么表示任务已经执行完毕。

现在来谈下onStopJob()。这个方法只有在条件不满足时才触发,例如任务的触发条件是有网络,如果网络断开,那么就会调用onStopJob()。此时我们要注意,这也意味着要停止在onStartJob()中执行的任务。

onStopJob()如果返回true,表示任务将被再次调度执行,如果返回false表示任务完全结束。

在任务执行期间,系统会持有唤醒锁(wakelock),并且直到调用jobFinished()或者onStopJob()后才会释放。

现在来创建一个JobService的子类,并实现异步线程的框架

public class MyJobService extends JobService {

    private class JobServiceHandler extends Handler {
        public JobServiceHandler(@NonNull Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            // 执行任务...
            
            // 任务执行完毕,通知系统
            jobFinished(parameters, false);
        }
    }

    private JobServiceHandler mHandler;

    @Override
    public void onCreate() {
        super.onCreate();
        // 启动一个工作线程
        HandlerThread handlerThread = new HandlerThread("JobService_thread");
        handlerThread.start();
        // 创建一个工作线程相关的Handler
        mHandler = new JobServiceHandler(handlerThread.getLooper());
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        // 把任务交给工作线程处理
        final Message message = mHandler.obtainMessage();
        message.what = params.getJobId();
        message.obj = params;
        message.sendToTarget();
        // 返回true表示任务还是继续执行
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

MyJobService是一个标准的Android Service,在它创建的时候,也就是在onCreate()中,利用HandlerThread创建一个工作线程,并创建了与之关联的Handler

然后在任务启动时,也就是在onStartJob()中,通过向Handler发送消息,把任务交给了工作线程处理。

如此一来,就避免了任务在主线程中处理,从而可能导致的ANR。

onStartJob()返回的是true,表示任务在工作线程中继续执行。那么同时我们还需要在任务完成时,调用jobFinished()通知系统任务已经完成,这就是在HandlerhandleMessage()中调用。

onStopJob()直接返回false,表明任务不再需要再次调度执行。注意,这个方法被调用是因为触发任务的条件被打破,此时需要停止正在执行任务,具体如何停止任务,要看实际情况决定。

注册JobService

由于JobService是一个标准的Android Service,因此需要在AndroidManifest.xml中注册,但是它的注册有点要求,必须要设置一个固定的权限。

        <service
            android:name=".MyJobService"
            android:permission="android.permission.BIND_JOB_SERVICE" />

使用JobScheduler计划任务

JobScheduler是Job Scheduler Service的一个client代理 类,获取方式如下

JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

这个命名是真的奇怪,为何不叫做JobSchedulerManager呢?

现在需要创建一个任务,它由JobInfo类表示。JobInfo是由Builder构造的,代码如下

JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_DOWNLOAD_MEIZI, new ComponentName(this, MyJobService.class))
	.setExtras(createExtras()) // 传入参数
	.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // 设置需要网络

Builder构造函数需要传入一个Job ID和一个JobService的实现类。

通过setExtras()给任务设置的参数,通过setRequiredNetworkType()设置任务需要网络。

现在万事俱备,只欠东风。我们需要把这个任务发布给系统

jobScheduler.schedule(builder.build());

代码参考

github.com/buxiliulian…

这个Demo使用JobScheduler下载了一个妹子图片,并显示到Activity界面。在测试的过程中,首先不要联网,然后启动App,再通过Home键把App置于后台,最后联网,通过Log你可以发现任务执行了完毕,那么再次打开应用,就可以看到妹子图片显示到了Activity界面。这就说明JobScheduler解决了后台Service的限制。