Android 后台任务管理调度

Categories: android

android后台任务执行调度一直问题蛮多的,在Android5.0之后加入了一个JobScheduler,管理这些后台执行的任务,并且给了一些限制策略,一方面控制耗电,一方面也尽量保证任务执行。项目中一直用这个管理器来执行一些后台的任务。

用起来也很简单,首先建一个任务的Service要继承自JobService,并且把这个Service注册到Manifest文件里面:

<service
            android:name=".DemoJobService"
            android:exported="true"
            android:permission="android.permission.BIND_JOB_SERVICE" />

这个Service里面要实现两个方法:

override fun onStartJob(params: JobParameters?): Boolean {
	XLog.info("onStartJob :" + params?.jobId)
	executeTask(params)
	return true//true还在执行,任务执行完成后需要手动调用jobFinished, false 表示任务执行完了
}

override fun onStopJob(params: JobParameters?): Boolean {
	XLog.info("onStopJob:" + params?.jobId)
	return false//true 表示异常结束的任务,重新安排任务 false是完成任务
}

最后就把这个任务放入管理调度器,由系统帮你管理执行:

val componentName = ComponentName(this, DemoJobService::class.java)
val jobInfo = JobInfo.Builder(JOB_ID, componentName)
    .setPersisted(true)//手机重启之后是否继续
    .setRequiresCharging(true)//充电的时候才执行
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//不收费的网络连接,比如wifi  
    .setPeriodic(12 * 60 * 60 * 1000)//任务执行间隔
    .build()

上面代码可以看到任务信息中可以加入一些限制条件,比如:需要网络、充电的时候执行,还有一些比如空闲的时候、电量低的时候不执行等等。

WorkManager

今年Google大会的时候,google给我们提供了一个叫 Android Jetpack 的Android 开发工具箱。它里面就包含了一个叫WorkManager的任务管理工具,专门用来管理Android的后台任务。

先引入:

def work_version = "1.0.0-alpha02"
implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin
implementation "android.arch.work:work-runtime-ktx:$work_version"

上面引入两个包,一个work-runtime就是这个任务管理工具的包,下面的加了-ktx的是kotlin的扩展,里面加了很多方便kotlin开发者的扩展工具。

说下主要的几个类:

周期任务

比如后台需要有一个12小时执行一次的任务。

首先得有个任务Worker:

class DemoWorker: Worker() {
    override fun doWork(): WorkerResult {
        //需要做的工作在这里完成
        doSomeWork()
 
        // 返回 WorkerResult.SUCCESS  
        // WorkerResult.FAILURE(失败,不继续执行工作) 
        // WorkerResult.RETRY(重试,就是任务失败了,后面还会再执行这个任务)
        return WorkerResult.SUCCESS
    }
}

然后用PeriodicWorkRequest,创建一个任务请求,传给WorkManager进行任务安排:

// 条件限制
val constraints = Constraints.Builder()
					.setRequiresDeviceIdle(true)
        			.setRequiresCharging(true)
                    .setRequiredNetworkType(NetworkType.CONNECTED)//网络连接
                    .build()
val periodicWork = PeriodicWorkRequestBuilder<DemoWorker>(12, TimeUnit.HOURS)//任务执行周期12小时一次
                    .setConstraints(constraints)
                    .build()
WorkManager.getInstance().enqueue(periodicWork)

这样任务就安排好了。

单次任务

单次任务和周期任务生成添加的方式一样,只是WorkRequest不一样:

// 条件限制
val constraints = Constraints.Builder()
					.setRequiresDeviceIdle(true)
        			.setRequiresCharging(true)
                    .setRequiredNetworkType(NetworkType.CONNECTED)//网络连接
                    .build()
val oneTimeWorker = OneTimeWorkRequestBuilder<DemoWorker>()
                    .setConstraints(constraints)
                    .build()
WorkManager.getInstance().enqueue(oneTimeWorker)

但是,单次任务有更高级的玩法。

任务链 就是可以将多个任务按照一定的顺序自行

比如顺序执行:

WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
    .then(workC)
    .enqueue()

这上面的任务就按照workA 、workB、workC这样的顺序执行下来,但是如果里面有一个任务返回结果是:WorkerResult.FAILURE 那整个链条就断了,不往下执行了。

然后还可以并行:

WorkManager.getInstance()
    .beginWith(workA1, workA2, workA3)
    .then(workB)
    .then(workC1, workC2)
    .enqueue()

这样写的话,workA1、workA2、workA3先执行,他们三个没有先后,等这个三个都执行完了就开始执行workB,workB完成厚就执行workC1、workC2,C1、C2也是一样随机的没有顺序先后。

最后还有一个更高级了,连接多个任务链,就是下图这样的意思:

img

代码:

val chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
val chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD)
val chain3 = WorkContinuation
    .combine(chain1, chain2)
    .then(workE)
chain3.enqueue()

大概任务管理就这些,经过Google封装后,任务安排调度起来变得相当简单。

对了还有一些其它必要的操作:

取消任务

val demoWorkId:UUID = demoWork.getId()
WorkManager.getInstance().cancelWorkById(demoWorkId)

WorkStatus观察

WorkManager.getInstance().getStatusById(demoWork.id).observe(lifecycleOwner, Observer { workStatus->
                if (workStatus != null && workStatus.state.isFinished) {
                        // ...
                    }
            })

参数输入输出

class DemoWorker: Worker() {
    override fun doWork(): WorkerResult {
    	//得到输入的参数
        val inputArg = inputData.getString(INPUT_ARGUMENT_KEY, "")
        //需要做的工作在这里完成
        doSomeWork()
 
        //输出参数定义
        val output = mapOf<String, String>( OUTPUT_ARGUMENT_KEY to "输出内容")
        outputData = output.toWorkData()//输出
        
        // 返回 WorkerResult.SUCCESS  
        // WorkerResult.FAILURE(失败,不继续执行工作) 
        // WorkerResult.RETRY(重试,就是任务失败了,后面还会再执行这个任务)
        return WorkerResult.SUCCESS
    }
}
//输入参数定义
val inputData = mapOf(INPUT_ARGUMENT_KEY to "单次任务").toWorkData()
val oneTimeWorker = OneTimeWorkRequestBuilder<DemoWorker>()
					.setInputData(inputData)//输入
                    .build()
WorkManager.getInstance().enqueue(oneTimeWorker)

WorkManager.getInstance().getStatusById(demoWork.id).observe(lifecycleOwner, Observer { workStatus->
                if (workStatus != null && workStatus.state.isFinished) {
                	val out = workStatus.outputData.getString(OUTPUT_ARGUMENT_KEY, "")
                    Log.i("Status", "获得输出内容 :$out ")
                        // ...
                    }
            })

添加Tag

val oneTimeWorker = OneTimeWorkRequestBuilder<DemoWorker>()
					.addTag("myTag")//add tag
                    .build()

ok, The End !