六、串连各个过程
现在,您将履行一项作业任务:对图片进行含糊处理。这是非常不错的第一步,但短少一些中心功用:
- 此操作不会整理临时文件
- 实际上它不会将图片保存到永久性文件中
- 而是始终对图片进行相同程度的含糊处理。
咱们将运用WorkManager作业链增加此功用。
WorkManager允许您创立按次序运转或并行运转的单独WorkRequest
。在此过程中,您将创立一个如下所示的作业链:
WorkRequest
表示为方框。
链接的另一个简介功用时,一个WorkRequest
的输出会成为链中下一个WorkRequest
的输入。在每个WorkRequest
之间传递的输入和输出均显现为蓝色文本。
6.1、创立整理和保存作业器
首要,您需求界说所需的一切Worker
类。您现已有了用于对图片进行含糊处理的Worker
,但还需求用于整理临时文件的Worker
以及用于永久保存图片的Worker
。
请在workers
软件包中创立两个扩展Worker
的新类。
第一个类的称号为CleanupWorler
,第二个类的称号应为SaveImageFileWorker
。
6.2、扩展作业器
从Worker
类扩展CleanupWorker
类。增加所需的构造函数参数。
class CleanupWorker(ctx: Context, params: WorkerParameters): Worker(ctx, params) {
}
6.3、替换和完结doWork()以用于CleanupWorker
CleanupWorker
不需求获取任何输入或传递任何输出。它只是删去临时文件(假如存在)。
CleanupWorker.kt
package com.example.background.workers
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.OUTPUT_PATH
import java.io.File
private const val TAG = "CleamupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters): Worker(ctx, params) {
override fun doWork(): Result {
// Makes a notification when the work starts and slows down the work so that
// it's easier to see each WorkRequest start, even no emulated devices
makeStatusNotification("Cleaning up old temporary files", applicationContext)
sleep()
return try {
val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
if (outputDirectory.exists()) {
val entries = outputDirectory.listFiles()
if (entries != null) {
val name = entry.name
if (name.isNotEmpty() && name.endsWith(".png")) {
val deleted = entry.delete()
Log.i(TAG, "Deleted $name = $deleted")
}
}
Result.success()
} catch (exception: Exception) {
exception.printStackTrace()
Result.failure()
}
}
}
}
6.4、替换和完结doWork()以用于SaveImageToFileWorker
SaveImageToFileWorker
将获取输入和输出。输入是运用键KEY_IMAGE_URI
存储的String
,即暂时含糊处理的图片URI,而输出也将是运用KEY_IMAGE_URI
存储的String
,即保存的含糊处理图片的URI。
请留意,系统会运用键KEY_IMAGE_URI
检索resourceUri
和output
值。
SaveImageToFileWorker.kt
package com.example.backgound.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.workDataOf
import androidx.work.Worker
import androidx.work.WorkerParamters
import com.example.background.KEY_IMAGE_URI
import java.text.SimpleDataFormat
import java.util.Date
import jaga.util.Locale
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters): Worker(ctx, params) {
private val title = "Blurred Image"
private val dateFormatter = SimpleDataFormat(
"yyyy.MM.dd 'at' HH:mm:ss z",
Local.getDefault()
)
override fun doWorl(): Result {
makeStatusNotification("Saving image", applicationContext)
sleep()
val resolver = applicationContext.contentResolver
return try {
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
val imageUrl = MediaStore.Image.Media.insertImage(resolver, bitmap, title, dataFormatter.format(Date()))
if (!imageUrl.isNullOrEmpty()) {
val output = workDataOf(KEY_IMAGE_URI to imageUrl)
Result.success(output)
} else {
Log.e(TAG, "Writing to MediaStore failed")
Result.failure()
}
} catch(exception: Exception) {
exception.printStaceTrace()
Result.failure()
}
}
}
6.5、修正BlurWorker告诉
现在,咱们有了用于将图片保存到正确文件夹的Wokrer
链,咱们能够运用WorkerUtils
类中界说的sleep()
办法减慢作业速度,以便更轻松的做到检查每个WorkRequest
的发动状况,即便在模拟设备上也不例外。BlurWorker
的终究版别如下所示:
BlurWorker.kt
class BlurWorker(ctx: Context, params: WorkerParameters): Worker(ctx, params) {
override fun doWork(): Result {
val appContext: applicationContext
val resourceUri = inputData.getString(KEY_IMAGE_URI)
// ADD THIS TO SLOW DOWN THE WORKER
sleep()
// ^^^^
return try {
if (TextUtils.isEmpty(resourceUri)) {
Timber.e("Invalide input uri")
throw IllegalArgumentException("Invalid input uri")
}
val resolver = appContext.contentResolver
val picture = BitmapFactory.decodeStream(resolver.openInputStream(Uri.parse(resourceUri)))
val output = blurBitmap(picture, appContext)
// Write bitmap to a temp file
val outputUri = writeBitmapToFile(context, output)
val outputData = worlDataOf(KEY_IMAGE_URI to outputUri.toString())
Result.success(outputData)
} catch (throwable: Throwable) {
throwable.printStackTrace()
Result.failure()
}
}
}
6.6、创立WorkRequest链
您需求修正BlurViewModel
的applyBlur
办法以履行WorkRequest
链,而不是仅履行一个恳求。目前,代码如下所示:
BlurViewModel.kt
val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
.setInputData(createInputDataForUri())
.build()
workManager.enqueue(blurRequest)
调用workMnager.beginWith()
,而不是调用workManager.enqueue()
。此调用会回来WorkContinuation
,其界说了WorkRequest
链。您能够通过调用then()
办法向此作业恳求链中增加恳求目标。例如,假如您拥有了三个WorkRequest
目标,即workA
、workB
和workC
,则能够编写以下代码:
val continuation = workManager.beginWith(workA)
continuation.then(workB)
.then(workC)
.enqueue()
此代码将生成并运转以下WorkRequest
链:
在applyBlur
中创立一个CleanupWorker WorkRequest
、BlurImage WorkRequest
和SaveImageToFile WorkRequest
链。将输入传递到BlurImage WorkRequest
中。
此操作的代码如下:
BlurViewModel.kt
internal fun applyBlur(blurLevel: Int) {
// Add WorkRequest to Cleanup temporary images
var continuation = workManaget
.beginWith(OneTimeWorkRequest)
.from(CleanupWorker::class.java)
// Add WorkRequest to blur the image
val blurRequest = OneTimeWorkRequest.Builder(BlurWorker::class.java)
.setInputData(createInputDataForUri())
.build()
continuation = continuation.then(blurRequest)
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequest.Builder(SaveImageToFileWorker::class.java)
continuation = continuation.then(save)
// Actually start the work
continuation.enquene()
}
此代码应该编译和运转。现在,您应该能够点击Go按钮,并能够在不同作业器运转时看到告诉。您依然能够在设备文件浏览器中检查通过含糊处理的图片,鄙人一步中,您将再增加一个按钮,以便用户能够在设备上检查现已模处理的图片。
鄙人面的屏幕截图中,您会发现告诉消息中显现当时正在运转的作业器。
### 6.7、重复运用BlurWorker 现在,咱们需求增加对图片进行不同程度的含糊处理的功用。请获取传递到`applyBlur`中的`blurLevel`参数,并向链中增加多个含糊处理`WorkRequest`操作。只是第一个`WorkRequest`需求应该获取URI输入。
BlurViewModel.kt
internal fun applyBlur(blurLevel: Int) {
// Add WorkRequest to Cleanup temporary images
var continuation = workManager
.beginWith(OneTimeWorkRequest)
.from(CleanupWork::class.java))
// Add WorkRequests to blur the image the number of times requested
for (i in 0 until blurLevel) {
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// Input the Uri if this is the first blur operation
// After the first blur operation the input will the output of previous blur operations
if (i == 0) {
blurBuilder.setInputData(createInputDataForUri())
}
continuation = continuation.then(blurBuilder.build())
}
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>().build()
continuation = continuation.then(save)
// Actually start the work
continuation.enqueue()
}
翻开设备文件浏览器,检查通过含糊处理的图片。请留意,输出文件夹中包括多张含糊处理过的图片、处于含糊处理中间阶段的图片,以及根据您选择的含糊处理程度显现通过含糊处理的终究图片。
七、保证作业不重复
现在,您已学会运用链,接下来应该掌握的事WorkManager的另一项强壮功用——仅有作业链。
有时,你一次只期望运转一个作业链。例如,您或许有一个将本地数据与服务器同步的作业链-您或许期望先让第一批数据完毕同步,然后再开端新的同步。为此,请运用beginUniqueWork
而非beginWith
;而且要提供仅有的String
称号。这会命名整个作业恳求链,以便您一起引用和查询这些恳求。
请运用beginUniqueWork
保证对文件进行含糊处理的作业链是仅有的。传入IMAGE_MANIPULATION_WORK_NAME
作为键。您还需求传入ExistingWorkPolicy
。选项包括REPLACE
、KEEP
或APPEND
。
您将运用REPLACE
,由于假如用户在当时图片完结之前决议对另一张图片进行含糊处理,咱们需求中止当时图片并开端对新图片进行含糊处理。
用于发动仅有作业连续的代码如下:
BlurViewModel.kt
// REPLACE THIS CODE
// var continuation = workManager
// .beginWith(OneTimeWorkRequest
// .from(CleanupWork::class.java))
// WITH
var continuation = workManager
.beginUniqueWork(
IMAGE_MANIPULATION_WORK_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.from(CleanupWorler::class.java)
)
现在,Blur-O-Matic一次只会对一张图片进行含糊处理。
八、符号和现实Work状况
本部分大量运用了LiveData
,因此,假如要充沛了解您自己的状况,您应该书序如何运用LiveData。LiveData是一种具有生命周期感知才干的数据容器。
您能够通过获取保留WorkInfo
目标的LiveData
来获取任何WorkRequest
的状况。WorkInfo
是一个包括WorkRequest
当时状况详细信息的目标,其间包括:
- Work是否为
BLOCKED
、CANELLED
、ENQUEUED
、FAILED
、RUNNING
或SUCCEEDED
。 - 假如
WorkRequest
完结,则为作业的任何输出数据。
下标显现了获取LiveData<WorkInfo>
或LiveData<WorkInfo>
目标的三种不同办法,以及每种办法相应的用途。
类型 | WorkManager 办法 | 阐明 |
---|---|---|
运用id获取Work | getWorkInfoByIdLiveData | 每个WorkRequest都有一个由WorkManager生成的仅有ID;您能够用此ID获取适用于该确切WorkRequest的单个LiveData |
运用仅有链名获取Work | getWorkInfosForUniqueWorkLiveData | 如您所见,WorkRequest或许是仅有链的一部分。这会在单一仅有WorkRequest链中为一切作业回来LiveData |
运用符号获取Work | getWorkInfosByTagLiveData | 最终,您能够选择运用字符串符号任何WorkRequest。您能够运用同一符号多个WorkRequest,并将它们关联起来。这样会回来用于任何单个符号的LiveData |
您将符号SaveImageForFileWorker WorkRequest
,以便您能够运用getWorkInfosByTag
获取该符号。您将运用一个符号为您的作业加上标签,而不是运用WorkManager ID。由于假如您的用户对多张图片进行含糊处理,则一切保存的图片WorkRequest
将具有相同的符号,而不是相同ID。因此,您也能够选择标签。
请不要运用getWorkInfosFoeUniqueWork
,由于它将为一切含糊处理WorkRequest
和整理WorkRequest
回来WorkInfo
,还需求额定的逻辑来查找保存的图片WorkRequest
。
8.1、符号您的Work
在applyBlur
中,在创立SaveImageToFileWorker
时,请运用String
常量TAG_OUTPUT
符号您的作业:
BlurViewModel.kt
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.addTag(TAG_OUTPUT)
.build()
8.2、获取WorkInfo
现在您现已符号了作业,能够获取WorkInfo
:
- 在
BlurViewModel
中,声明一个名为outputWorkInfos
的新类变量,该变量是LiveData<List<WorkInfo>>
- 在
BlurViewModel
中增加init块以运用WorkManager.getWorkInfosByTagLiveData
获取WorkInfo
您需求的代码如下:
BlurViewModel.kt
// New instance variable for the WorkInfo
internal val outputWorkInfos: LiveData<List<WorkInfo>>
// Modifier the existing init block in the BlurViewModel class to this:
init {
imageUri = getImageUri(application.applicationContext)
// This transformation making sure that whenever the current work Id changes the WorkInfo
// the UI is listening to changes
outputWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
}
8.3、显现WorkInfo
现在您已拥有适用于WorkInfo
的LiveData
,能够在BlurActivity
中进行调查。在调查器中:
- 检查
WorkInfo
列表是否不为null而且其间是否包括任何WorkInfo
目标。假如没有点击Go按钮,则回来。 - 获取列表中的第一个
WorkInfo
;只要一个符号为TAG_OUTPUT
的WorkInfo
,由于咱们的作业链是仅有的。 - 运用
workinfo.state.isFinished
检查作业状况是否已完结。 - 假如未完结,请调用
showWorkInProgress()
以躲藏Go按钮并显现Cancel Work按钮和进度条。 - 假如已完结,请调用
showWorkFinished()
以躲藏Cancel Work按钮和进度条,并显现Go按钮。
代码如下:
留意:在收到恳求时,导入androidx.lifecycle.Observer
.
BlurActivity.kt
override fun onCreate(saveInstanceState: Bundle?) {
...
// Observe work status, added in onCreate()
viewModel.outputWorkInfos.observe(this, workInfosObverser())
}
// Define the observer function
private fun workInfosObserver(): Observer<List<WorkInfo>> {
return Observer { listOfWorkInfo ->
// Note that these next few lines grab a single WorkInfo if it exists
// This code could be in a Transformation in the ViewModel; they are included here
// so that the entire process of displaying a WorkInfo is in one location.
// If there are no matching work info, do nothing
if (listOfWorkInfo.isNullOrEmpty()) {
return@Observer
}
// We only care about the one output status.
// Every continuation has only one worker tagged TAG_OUTPUT
val workInfo = listOfWorkInfo[0]
if (workInfo.state.isFinished) {
showWorkFinished()
} else {
showWorlInProgress()
}
}
}
8.4、运转您的运用
运转您的运用,它应该编译并运转,且现在能够在作业时显现进度条以撤销按钮:
## 九、显现终究输出 每个`WorkInfo`还有一个`getOutputData`办法,该办法可让您获取包括终究保存的图片的输出`Data`目标。在Kotlin中,您能够运用该言语为您生成的变量`outputData`访问此办法。每当有通过含糊处理的图片准备就绪可供显现时,便在屏幕上显现**See File**按钮。
9.1、创立“See File”按钮
activity_blur.xml
布局中有一个躲藏的按钮。它坐落BlurActivity
中,名为outputButton
。
在BlurActivity
的onCreate()
中,为该按钮设置点击监听器。此操作应获取URI,然后翻开一个activity以检查URI。
BlurActivity.kt
override fun onCreate(savedInstanceState: Bundle?> {
// Setup view output image file button
binding.seeFileButton.setOnClickListener {
viewModel.ourputUri?.let { cuttentUri ->
val actionView = Intent(Intent.ACTION_VIEW, currentUri)
actionView.resolveActivity(packageManager)?.run {
startActivity(actionView)
}
}
}
}
9.2、设置URI并显现按钮
您需求对WorkInfo
调查器运用一些最终的调整,才干达到预期作用:
- 假如
WorkInfo
完结,请运用workInfo.outputData
获取输出数据。 - 然后获取输入URI,请记住,它是运用
Constants.KEY_IMAGE_URI
键存储的。 - 假如URI不为空,则会正保证存;系统会显现
outputButton
并运用该URI对视图模型调用setOutputUri
.
BlurActivity.kt
private fun workInfosObserver(): Observer<List<WorkInfl>> {
return Observer { listOfWorkInfo ->
// Note that these next few lines grab a single WorkInfo if it exists
// This code could be in a Transformation in the ViewModel; they are included here
// so that the entire process of displaying a WorkInfo is in one location
// If there are no matching work info, do nothing
if (listOfWorkInfo.isNullOrEmpty()) {
return@Observer
}
// We only care about the one output status
// Every continuation has only one worker tagged TAG_OUTPUT
val workInfo = listOfWorkInfo[0]
if (workInfo.state.isFinished) {
showWorkFinished()
// Normally this progressing, which is not directly related to drawing views on
// screen would be in the ViewModel. Foe simplicity we are keeping it here.
val outputImageUri = workInfo.outputData.getString(KEY_IMAGE_URI)
// If there is an output file show "See File" button
if (!outputImageUri.isNullOrEmpty()) {
viewModel.setOutputUri(outputImageUri)
binding.seeFileButton.visibility = View.VISIBLE
}
} else {
showWorkInProgress()
}
}
}
9.3、运转您的代码
运转您的代码。您应该会看到新的可点击的See File按钮,该按钮会将您的输出的文件:
十、撤销Work
您已增加此撤销Work按钮,所以咱们要增加一些代码来履行操作。借助WorkManager,您能够运用ID、按符号和仅有链称号撤销Work。
在这种状况下,您需求按仅有链名撤销作业,由于您想要撤销链中的一切作业,而不仅仅是某个特定过程。
10.1、按称号撤销作业
在BlurViewModel
中,增加一个名为cancelWork()
的新办法以撤销仅有作业。在函数内,对workManager
调用cancelUniqueWork
,并传入IMAGE_MANIPULATION_WORK_NAME
符号。
BlurViewModel.kt
internal fun cancelWork() {
workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
}
10.2、调用撤销办法
然后,运用cancelButton
按钮调用cancelWork
:
BlurActivity.kt
// In onCreate()
// Hookup the Cancel button
binding.cancelButton.setOnClickListener { viewModel.cancelWork() }
10.3、运转和撤销作业
运转您的运用。它应该能够正常编译。先对图片进行含糊处理,然后点击“撤销”按钮。这个链都会被撤销!
十一、Work约束
最终,很重要的一点是,WorkManager
支撑Constraints
。关于Blur-O-Matic,您将运用设备有必要充电的约束条件。也就是说,您的作业恳求只会在设备充电的状况下运转。
11、1、创立并增加充电约束条件
如需创立Constraints
目标,请运用Constrainits.Builder
。然后,您能够设置所需的约束条件,并运用办法setRequiresCharging()
将其增加到WorkRequest
:
在收到恳求时,导入androidx.work.Constraints
BlurViewModel.kt
// Put this inside the applyBlur() function, above the save work request.
// Create charging constraint
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.build()
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.setConsteaints(constrains)
.addTag(TAG_OUTPUT)
.build()
continuation = continuation.then(save)
// Actually start the work
continuation.enqueue()
11.2、运用模拟器或设备进行测验
现在您就能够运转Blur-O-Matics了。假如您运用的是一台设备,则能够移除或刺进您的设备。在模拟器上,您能够正在“Extended control”(扩展控件)窗口中更改充电状况:
当设备不充电时,应会暂停履行SaveImageToFileWorker
知道您的设备刺进充电。