• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

使用WorkManager在后台处理工作 - Kotlin下

武飞扬头像
谋爱先谋生爱人先爱己
帮助1

六、串连各个步骤

现在,您将执行一项工作任务:对图片进行模糊处理。这是非常不错的第一步,但缺少一些核心功能:

  • 此操作不会清理临时文件
  • 实际上它不会将图片保存到永久性文件中
  • 而是始终对图片进行相同程度的模糊处理。

我们将使用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检索resourceUrioutput值。

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链

您需要修改BlurViewModelapplyBlur方法以执行WorkRequest链,而不是仅执行一个请求。目前,代码如下所示:

BlurViewModel.kt

val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
    .setInputData(createInputDataForUri())
    .build()
    
workManager.enqueue(blurRequest)

调用workMnager.beginWith(),而不是调用workManager.enqueue()。此调用会返回WorkContinuation,其定义了WorkRequest链。您可以通过调用then()方法向此工作请求链中添加请求对象。例如,如果您拥有了三个WorkRequest对象,即workAworkBworkC,则可以编写以下代码:

val continuation = workManager.beginWith(workA)

continuation.then(workB)
    .then(workC)
    .enqueue()

此代码将生成并运行以下WorkRequest链:

学新通

applyBlur中创建一个CleanupWorker WorkRequestBlurImage WorkRequestSaveImageToFile 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。选项包括REPLACEKEEPAPPEND

您将使用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是否为BLOCKEDCANELLEDENQUEUEDFAILEDRUNNINGSUCCEEDED
  • 如果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:

  1. BlurViewModel中,声明一个名为outputWorkInfos的新类变量,该变量是LiveData<List<WorkInfo>>
  2. 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

现在您已拥有适用于WorkInfoLiveData,可以在BlurActivity中进行观察。在观察器中:

  1. 检查WorkInfo列表是否不为null并且其中是否包含任何WorkInfo对象。如果尚未点击Go按钮,则返回。
  2. 获取列表中的第一个WorkInfo;只有一个标记为TAG_OUTPUTWorkInfo,因为我们的工作链是唯一的。
  3. 使用workinfo.state.isFinished检查工作状态是否已完成。
  4. 如果未完成,请调用showWorkInProgress()以隐藏Go按钮并显示Cancel Work按钮和进度条。
  5. 如果已完成,请调用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

BlurActivityonCreate()中,为该按钮设置点击监听器。此操作应获取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观察器应用一些最后的调整,才能达到预期效果:

  1. 如果WorkInfo完成,请使用workInfo.outputData获取输出数据。
  2. 然后获取输入URI,请记住,它是使用Constants.KEY_IMAGE_URI键存储的。
  3. 如果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知道您的设备插入充电。

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgiiecj
系列文章
更多 icon
同类精品
更多 icon
继续加载