笔记之Android架构组件-WorkManager

service一直被用来做后台运行的操作,包括一些保活,上传数据之类的,这个后台运行的弊端很多,比如耗电,比如设计用户隐私之类的,谷歌对这些后台行为进行了一些处理,从Android Oreo(API 26) 开始,如果一个应用的目标版本为Android 8.0,当它在某些不被允许创建后台服务的场景下,调用了ServicestartService()方法,该方法会抛出IllegalStateException。并且出台了一些新政策:

1、2018年8月: 所有新开发应用的target API level必须是26(Android 8.0)甚至更高。
2、2018年11月: 所有已发布应用的target API level必须更新至26甚至更高。
3、2019年起: 在每一次发布新版本的Android系统之后,所有新开发以及待更新的应用都必须在一年内将target API level调整至对应的系统版本甚至更高。

如果想继续使用service,必须调用Context.startForegroundService(),在前台启动新服务,系统创建服务,应用有五秒的时间来调用该服务的 startForeground()方法以显示新服务的用户可见通知。 如果应用在此时间限制内未调用startForeground(),则系统将停止服务并声明此应用为 ANR。所以,在不久的将来,service的使用范围会越来越小,取而代之的,是谷歌推出的新的技术:WorkManager
WorkManager在工作的触发器 满足时, 运行可推迟的后台工作。WorkManager会根据设备API的情况,自动选用JobScheduler, 或是AlarmManager来实现后台任务,WorkManager里面的任务在应用退出之后还可以继续执行(注意:是程序退出,并非杀死进程还可以继续执行任务),这个技术适用于在应用退出之后任务还需要继续执行的需求,对于在应用退出的之后任务也需要终止的需求,可以选择ThreadPool、AsyncTask

首先添加依赖:

//如果创建后台任务请求,编译器抛出异常,则使用
android {
 kotlinOptions {
        jvmTarget = 1.8
    }
}

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:2.3.1"

WorkManager的简单使用

  1. 创建后台任务,并且实现具体的任务逻辑。
  2. 配置后台运行任务运行条件和约束条件,并且构建后台任务请求
  3. 将后天任务请求传入WorkManager.enqueue()中,系统会在合适的时间运行。

1. 创建后台任务

class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    
    
    //该方法不会运行在主线程,所以我们可以在此处进行耗时操作。
    override fun doWork(): Result {
    
    
        Log.e("TAG","start")
        Thread.sleep(2000)
        Log.e("TAG","end")
        return Result.success() //返回成功
//      return Result.failure() //返回失败
//      return Result.retry() //重试,其实也返回失败,只是与WorkRequest.Builder的setBackoffCriteria()结合后进行重新启动
    }

}

2. 构建后台任务请求

因为可配置的条件比较多,所以等一下讲解,现在进行基本用法。

  			// 对于一次性 WorkRequest,请使用 OneTimeWorkRequest,
  			//对于周期性工作,请使用 PeriodicWorkRequest
            // 构建一次性请求,下面是两种不同的创建创建方式
            val workRequest = OneTimeWorkRequestBuilder<MyWork>()
                .build()
          	
          	//1 小时进行一次周期性任务请求
            val periodicWorkRequestBuilder =
                PeriodicWorkRequestBuilder<MyWork>(1, TimeUnit.HOURS)
                    .build()

注意:
为了降低设备性能消耗,如果创建周期性的工作,那么其运行周期不能短与15分钟。

3.将后台任务请求,传递给WorkMnager的enqueue()

	//添加一次性请求任务
  WorkManager.getInstance(this)..enqueue(workRequest)
  	//添加周期性请求任务
  WorkManager.getInstance(this).enqueue(periodicWorkRequestBuilder)

4.取消和停止工作

   val workRequest = OneTimeWorkRequestBuilder<MyWork>()
                .addTag("workmanager")
                .build()
                
            WorkManager.getInstance(this).cancelAllWorkByTag("workmanager")
			WorkManager.getInstance().cancelWorkById(request.getId());
			//会返回 LiveData 和具有该标记的所有任务的状态列表
    		WorkManager.getInstance().getWorkInfosByTagLiveData(TAG); 

使用id只能取消单个后台任务请求,而使用标签的话,则可以将同一标签名的所有后台任务请求全部取消。

进阶使用

1.约束条件

Constraints.Builder的 API

        val uri = Uri.parse("xxxxx")
        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED) //指定需要在有网的情况下
            .setRequiresBatteryNotLow(true)//指定电量在可接受范围内运行
            .setRequiresStorageNotLow(true)//指定在存储量在可接受范围内运行
            .addContentUriTrigger(uri, true)//当Uri发生变化的时候运行
            .setRequiresDeviceIdle(true)//当设备处于空闲状态时运行
            .setRequiresCharging(true)//当设备处于充电状态时运行
            .build()
        
        //创建请求
        val request = OneTimeWorkRequestBuilder<MyWork>()
            .setConstraints(constraints)//添加约束
            .build()
        //当满足约束条件后才会执行该任务
        WorkManager.getInstance(this).enqueue(request)

注意:如果指定了多个约束,你的任务将仅在满足所有约束时才会运行。

如果在任务运行期间某个约束不再得到满足,则 WorkManager 将停止工作器。当约束继续得到满足时,系统将重新尝试执行该任务。

2. 延时处理

  val workRequest = OneTimeWorkRequestBuilder<MyWork>()
                .setInitialDelay(1,TimeUnit.MINUTES) //延迟一分钟后执行
                .build()

3.设置回退/重试的策略,当doWork()返回Result.retry()时启用

  val workRequest = OneTimeWorkRequestBuilder<MyWork>()
                .setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.MINUTES)
                .build()

该方法接受三个参数:
第一个参数:指定任务再次执行失败,下次重试的时间应该以什么样的形式延迟。这个很好理解,假如任务一直执行失败,不断地重新执行也没什么意义,只会徒增设备的性能消耗。而随着失败次数增多,下次重试的时间也应该进行适当的延迟。第一个参数可选值有两种,分别是LINEAREXPONENTIAL,前者表示下次重试时间以线性的方式延迟,后者代表下次重试时间为指数的方式延迟。
第二个参数:时间
第三个参数:时间类型
注意:最短时间不能少于10秒钟

4.传入参数/标记请求任务

   btn.setOnClickListener {
    
    
            //创建参数
            val imageData:Data=Data.Builder()
                .putString(DATA_KEY,"开始执行")
                .build()

            val request = OneTimeWorkRequestBuilder<MyWork>()
                .setInputData(imageData) //传入参数
                .build()

            mWorkManager.enqueue(request)
        }

class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    
    

    override fun doWork(): Result {
    
    
        val inputData = inputData
        var string = inputData.getString(DATA_KEY)
        Log.e("Tag",string)

        //创建输出结果
        val build = Data.Builder()
            .putString(DATA_KEY, "我接受到了参数了")
            .build();
        return Result.success(build)
    }

}

5.监听工作状态

  WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.getId())
                .observe(this, object : Observer<WorkInfo>{
    
    
                    override fun onChanged(@Nullable workInfo: WorkInfo?) {
    
    
                        if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
    
    
                            //获取成功返回的结果
                            Log.e("TAG",workInfo.outputData.getString(DATA_KEY))
                        }
                    }
                })

6. 指定并发或者按照顺序执行任务

        val imageData:Data=Data.Builder()
            .putString(DATA_KEY,"开始执行")
            .build()
        val imageData2:Data=Data.Builder()
            .putString(DATA_KEY,"开始执行1")
            .build()

        val request = OneTimeWorkRequestBuilder<MyWork>()
            .setInputData(imageData) //传入参数
            .build()

        val request1 =  OneTimeWorkRequestBuilder<MyWork>().
            setInputData(imageData2) //传入参数
            .build()
        val request2 = OneTimeWorkRequestBuilder<MyWork>().build()
        val request3 = OneTimeWorkRequestBuilder<MyWork>()
                .setInputMerger(OverwritingInputMerger::class.java)
                .build();
        val request4 =  OneTimeWorkRequestBuilder<MyWork>().build();
//        为了管理来自多个父级 OneTimeWorkRequest 的输入,WorkManager 使用 InputMerger。
//        WorkManager 提供两种不同类型的 InputMerger:
//        OverwritingInputMerger 会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。
//        ArrayCreatingInputMerger 会尝试合并输入,并在必要时创建数组。



        WorkManager.getInstance(this)
            //使用beginWith()可以并行执行request、request1、request2
            .beginWith(  mutableListOf<OneTimeWorkRequest>(request,request1,request2))
            //使用then()可以按顺序执行任务
            .then(request3)//在执行request3
            .then(request4)//在执行request4
            .enqueue();

这里我们需要注意一下如果我们使用这种链式调用的话,WorkManager还要求,必须在前一个后台任务运行完成之后,下一个后台任务才会运行。也就是说,如果某一个后台任务运行失败,或者取消,那么接下来的后台任务就都得不到运行了。

7.唯一工作序列

我们要想创建一个唯一的工作序列,只需调用beginUniqueWork()而不是beginWith().来开始序列。每个唯一的工作序列都有一个名字,WorkManager一次只允许一个工作序列使用该名称,当我们创建一个新的唯一工作序列时,如果已经有一个未完成的序列具有相同的名称,则指定WorkManager应执行的操作:

取消现有的序列并用新序列其替换 保持现有顺序并忽略新的请求 将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务
如果我们有一个不应该多次入队的任务,则唯一工作序列可能很有用。例如,如果我们的应用需要将其数据同步到网络,我们可能会排列一个名为“sync”的序列,并指定如果已经有一个具有该名称的序列,则应该忽略我们的新任务。如果我们需要逐步建立一个长期的任务链,那么唯一的工作序列也会很有用,例如,照片编辑应用可能会让用户撤消一长串的操作,每个撤销操作可能需要一段时间,但必须按正确的顺序执行,在这种情况下,应用程序可以创建一个“撤消”链并根据需要将每个撤销操作追加到链中。

如果进程被杀死,或者不满足约束条件时,那么WorkManager是不会运行的。当约束继续得到满足时,或者程序重新启动时,系统将重新尝试执行该任务。
例子:

class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
    
    
    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
    
    

        textViewA.text=sharedPreferences?.getInt("A", 0).toString()
        textViewB.text=sharedPreferences?.getInt("B", 0).toString()
    }

    private val mWorkManager by lazy {
    
    
        WorkManager.getInstance(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val sharedPreferences = getSharedPreferences("WORK", 0)
        sharedPreferences.registerOnSharedPreferenceChangeListener(this)
        btn.setOnClickListener {
    
    
            val build = OneTimeWorkRequestBuilder<MyWork>()
                .setInputData(workDataOf("A" to "A"))
                .build()
            val build2 = OneTimeWorkRequestBuilder<MyWork>()
                .setInputData(workDataOf("A" to "B"))
                .build()

            mWorkManager.beginWith(build)
                .then(build2)
                .enqueue()
        }


    }
}

open class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    
    

    override fun doWork(): Result {
    
    
        Thread.sleep(6000)
        val sp =applicationContext.getSharedPreferences("WORK", 0)
        val key = inputData.getString("A")

        var int = sp.getInt(key, 0)
        ++int;

        sp.edit().putInt(key,int).apply()
        return Result.success()
    }

}

如果有什么地方有问题,也欢迎大家提出。

参考博客:

Android Jetpack架构组件之WorkManager入门
WorkManager 入门指南
WorkManger

猜你喜欢

转载自blog.csdn.net/weixin_44710164/article/details/106475218
今日推荐