WorkManager và hoạt động kiểm thử nâng cao

1. Giới thiệu

Trong lớp học lập trình Làm việc ở chế độ nền bằng WorkManager, bạn đã tìm hiểu cách thực thi công việc ở chế độ nền (không phải trên luồng chính) bằng WorkManager. Trong lớp học lập trình này, bạn sẽ tiếp tục tìm hiểu chức năng của WorkManager để đảm bảo công việc riêng biệt, công việc được gắn thẻ, công việc được huỷ và quy tắc ràng buộc đối với công việc. Lớp học lập trình này sẽ kết thúc khi bạn tìm hiểu xong cách viết chương trình kiểm thử tự động để xác minh rằng worker (thành phần thực thi) của bạn hoạt động đúng cách và trả về kết quả dự kiến. Bạn cũng sẽ tìm hiểu cách sử dụng Công cụ kiểm tra tác vụ trong nền do Android Studio cung cấp để kiểm tra các worker trong hàng đợi.

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ đảm bảo công việc riêng biệt, công được gắn thẻ việc, công việc được huỷ và các quy tắc ràng buộc được triển khai đối với công việc. Sau đó, bạn sẽ tìm hiểu cách viết kiểm thử giao diện người dùng tự động cho ứng dụng Blur-O-Matic để xác minh chức năng của 3 worker được tạo trong lớp học lập trình Làm việc ở chế độ nền bằng WorkManager:

  • BlurWorker
  • CleanupWorker
  • SaveImageToFileWorker

Kiến thức bạn sẽ học được

Bạn cần có

2. Chuẩn bị

Tải mã nguồn xuống

Nhấp vào đường liên kết sau đây để tải toàn bộ mã nguồn cho lớp học lập trình này:

Hoặc nếu muốn, bạn có thể sao chép mã từ GitHub:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git
$ cd basic-android-kotlin-compose-training-workmanager
$ git checkout intermediate

Mở dự án trong Android Studio.

3. Đảm bảo công việc riêng biệt

Giờ đây, khi bạn đã biết cách tạo chuỗi cho worker, đã đến lúc xử lý một tính năng mạnh mẽ khác của WorkManager: trình tự công việc riêng biệt.

Đôi khi, bạn chỉ muốn chạy mỗi lần một chuỗi công việc. Ví dụ: có thể bạn có một chuỗi công việc đồng bộ hoá dữ liệu cục bộ với máy chủ. Bạn nên hoàn tất quá trình đồng bộ hoá dữ liệu diễn ra đầu tiên trước khi bắt đầu quá trình đồng bộ hoá mới. Để làm việc này, hãy sử dụng beginUniqueWork() thay vì beginWith() rồi đặt tên riêng biệt theo kiểu String. Dữ liệu đầu vào này đặt tên cho toàn bộ chuỗi yêu cầu công việc để bạn có thể tham chiếu và truy vấn chúng cùng nhau.

Bạn cũng phải truyền vào một đối tượng ExistingWorkPolicy. Đối tượng này cho hệ điều hành Android biết điều gì sẽ xảy ra nếu công việc đã tồn tại. Các giá trị ExistingWorkPolicy có thể có là REPLACE, KEEP, APPEND hoặc APPEND_OR_REPLACE.

Trong ứng dụng này, bạn nên sử dụng REPLACE vì nếu người dùng quyết định làm mờ một hình ảnh khác trước khi làm mờ xong hình ảnh hiện tại, thì bạn nên dừng việc làm mờ hình ảnh hiện tại để bắt đầu làm mờ hình ảnh mới.

Bạn cũng nên đảm bảo rằng nếu người dùng nhấp vào Start (Bắt đầu) khi một yêu cầu công việc đã được thêm vào hàng đợi, thì ứng dụng sẽ thay thế yêu cầu công việc trước đó bằng yêu cầu mới. Việc tiếp tục thực hiện yêu cầu trước đó là không hợp lý vì ứng dụng vẫn thay thế yêu cầu trước đó bằng yêu cầu mới.

Trong tệp data/WorkManagerBluromaticRepository.kt, bên trong phương thức applyBlur(), hãy hoàn tất các bước sau:

  1. Xoá lệnh gọi đến hàm beginWith() và thêm lệnh gọi tới hàm beginUniqueWork().
  2. Đối với tham số đầu tiên của hàm beginUniqueWork(), hãy truyền vào hằng số IMAGE_MANIPULATION_WORK_NAME.
  3. Đối với tham số thứ hai (tham số existingWorkPolicy), hãy truyền vào ExistingWorkPolicy.REPLACE.
  4. Đối với tham số thứ ba, hãy tạo một OneTimeWorkRequest mới cho CleanupWorker.

data/WorkManagerBluromaticRepository.kt

import androidx.work.ExistingWorkPolicy
import com.example.bluromatic.IMAGE_MANIPULATION_WORK_NAME
...
// REPLACE THIS CODE:
// var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))
// WITH
var continuation = workManager
    .beginUniqueWork(
        IMAGE_MANIPULATION_WORK_NAME,
        ExistingWorkPolicy.REPLACE,
        OneTimeWorkRequest.from(CleanupWorker::class.java)
    )
...

Giờ đây, Blur-O-Matic chỉ làm mờ từng hình ảnh một.

4. Gắn thẻ và cập nhật giao diện người dùng dựa trên trạng thái Công việc

Thay đổi tiếp theo bạn thực hiện liên quan đến nội dung mà ứng dụng hiển thị khi Công việc thực thi. Thông tin được trả về liên quan đến công việc trong hàng đợi sẽ xác định cách giao diện người dùng cần thay đổi.

Bảng này trình bày 3 phương thức mà bạn có thể gọi để xem thông tin về công việc:

Loại

Phương thức WorkManager

Mô tả

Xem công việc qua id (mã nhận dạng)

getWorkInfoByIdLiveData()

Hàm này trả về một LiveData<WorkInfo> cho một WorkRequest cụ thể theo ID của yêu cầu đó.

Xem công việc bằng cách sử dụng tên chuỗi riêng biệt

getWorkInfosForUniqueWorkLiveData()

Hàm này trả về LiveData<List<WorkInfo>> cho tất cả công việc trong một chuỗi WorkRequest riêng biệt.

Xem công việc qua thẻ

getWorkInfosByTagLiveData()

Hàm này trả về LiveData<List<WorkInfo>> cho một thẻ.

Đối tượng WorkInfo chứa thông tin chi tiết về trạng thái hiện tại của WorkRequest, trong đó có:

Các phương thức này trả về LiveData. LiveData là chủ thể dữ liệu có thể quan sát và nhận biết được trong vòng đời. Chúng ta chuyển đổi LiveData thành Flow của đối tượng WorkInfo bằng cách gọi .asFlow().

Vì chúng ta cần biết thời điểm lưu hình ảnh cuối cùng, nên hãy thêm một thẻ vào WorkRequest SaveImageToFileWorker để có thể nhận WorkInfo qua phương thức getWorkInfosByTagLiveData().

Một lựa chọn khác là sử dụng phương thức getWorkInfosForUniqueWorkLiveData(). Phương thức này trả về thông tin liên quan đến cả ba WorkRequest (CleanupWorker, BlurWorkerSaveImageToFileWorker). Nhược điểm của phương thức này là bạn cần bổ sung một đoạn mã để dành riêng cho việc tìm thông tin SaveImageToFileWorker cần thiết.

Gắn thẻ cho yêu cầu công việc

Việc gắn thẻ cho công việc được thực hiện trong tệp data/WorkManagerBluromaticRepository.kt bên trong hàm applyBlur().

  1. Khi bạn tạo yêu cầu công việc SaveImageToFileWorker, hãy gắn thẻ cho công việc bằng cách gọi phương thức addTag() và truyền vào hằng số TAG_OUTPUT dạng String.

data/WorkManagerBluromaticRepository.kt

import com.example.bluromatic.TAG_OUTPUT
...
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
    .addTag(TAG_OUTPUT) // <- Add this
    .build()

Thay vì dùng WorkManager ID, bạn hãy sử dụng thẻ để gắn nhãn cho công việc vì nếu người dùng làm mờ nhiều hình ảnh, thì tất cả hình ảnh lưu WorkRequest sẽ có cùng một thẻ nhưng không có cùng một mã.

Xem WorkInfo

Bạn sử dụng thông tin WorkInfo qua yêu cầu công việc SaveImageToFileWorker trong logic để quyết định cho thấy thành phần kết hợp nào trong giao diện người dùng dựa trên BlurUiState.

ViewModel sử dụng thông tin này qua biến outputWorkInfo của kho lưu trữ.

Lúc này, khi đã gắn thẻ cho yêu cầu công việc SaveImageToFileWorker, bạn có thể hoàn tất các bước sau đây để truy xuất thông tin của yêu cầu công việc đó:

  1. Trong tệp data/WorkManagerBluromaticRepository.kt, hãy gọi phương thức workManager.getWorkInfosByTagLiveData() để điền biến outputWorkInfo.
  2. Truyền hằng số TAG_OUTPUT cho tham số của phương thức.

data/WorkManagerBluromaticRepository.kt

...
override val outputWorkInfo: Flow<WorkInfo?> =
    workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
...

Lệnh gọi của phương thức getWorkInfosByTagLiveData() trả về LiveData. LiveData là chủ thể dữ liệu có thể quan sát và nhận biết được trong vòng đời. Hàm .asFlow() sẽ chuyển đổi LiveData thành một Flow.

  1. Tạo chuỗi cho một lệnh gọi đến hàm .asFlow() để chuyển đổi phương thức đó thành một Flow. Bạn chuyển đổi phương thức để ứng dụng có thể hoạt động với Flow trong Kotlin thay vì LiveData.

data/WorkManagerBluromaticRepository.kt

import androidx.lifecycle.asFlow
...
override val outputWorkInfo: Flow<WorkInfo?> =
    workManager.getWorkInfosByTagLiveData(TAG_OUTPUT).asFlow()
...
  1. Tạo chuỗi cho một lệnh gọi đến hàm biến đổi .mapNotNull() để đảm bảo Flow chứa giá trị.
  2. Đối với quy tắc biến đổi, nếu phần tử không trống, hãy chọn mục đầu tiên trong bộ sưu tập. Nếu không, hãy trả về một giá trị rỗng. Sau đó, hàm biến đổi sẽ xoá chúng nếu chúng là giá trị rỗng.

data/WorkManagerBluromaticRepository.kt

import kotlinx.coroutines.flow.mapNotNull
...
    override val outputWorkInfo: Flow<WorkInfo?> =
        workManager.getWorkInfosByTagLiveData(TAG_OUTPUT).asFlow().mapNotNull {
            if (it.isNotEmpty()) it.first() else null
        }
...
  1. Do hàm biến đổi .mapNotNull() đảm bảo rằng có một giá trị tồn tại, nên bạn có thể yên tâm xoá ? khỏi kiểu Flow vì nó không còn là kiểu có giá trị rỗng.

data/WorkManagerBluromaticRepository.kt

...
    override val outputWorkInfo: Flow<WorkInfo> =
...
  1. Bạn cũng phải xóa ? khỏi giao diện BluromaticRepository.

data/BluromaticRepository.kt

...
interface BluromaticRepository {
//    val outputWorkInfo: Flow<WorkInfo?>
    val outputWorkInfo: Flow<WorkInfo>
...

Thông tin về WorkInfo được đưa ra dưới dạng Flow qua kho lưu trữ. Sau đó, ViewModel sẽ sử dụng dữ liệu đó.

Cập nhật BlurUiState

ViewModel sử dụng WorkInfo do kho lưu trữ tạo ra từ Flow outputWorkInfo để đặt giá trị cho biến blurUiState.

Mã giao diện người dùng sử dụng giá trị biến blurUiState để xác định xem cần hiển thị thành phần kết hợp nào.

Hãy hoàn tất các bước sau để thực hiện cập nhật blurUiState:

  1. Điền sẵn biến blurUiState bằng Flow outputWorkInfo qua kho lưu trữ.

ui/BlurViewModel.kt

// ...
// REMOVE
// val blurUiState: StateFlow<BlurUiState> = MutableStateFlow(BlurUiState.Default)

// ADD
val blurUiState: StateFlow<BlurUiState> = bluromaticRepository.outputWorkInfo
// ...
  1. Sau đó, bạn phải liên kết các giá trị trong Flow với các trạng thái BlurUiState, tuỳ thuộc vào trạng thái của công việc.

Khi công việc hoàn tất, hãy đặt biến blurUiState thành BlurUiState.Complete(outputUri = "").

Khi công việc bị huỷ, hãy đặt biến blurUiState thành BlurUiState.Default.

Nếu không, hãy đặt biến blurUiState thành BlurUiState.Loading.

ui/BlurViewModel.kt

import androidx.work.WorkInfo
import kotlinx.coroutines.flow.map
// ...

    val blurUiState: StateFlow<BlurUiState> = bluromaticRepository.outputWorkInfo
        .map { info ->
            when {
                info.state.isFinished -> {
                    BlurUiState.Complete(outputUri = "")
                }
                info.state == WorkInfo.State.CANCELLED -> {
                    BlurUiState.Default
                }
                else -> BlurUiState.Loading
            }
        }

// ...
  1. Vì bạn quan tâm đến StateFlow, hãy chuyển đổi Flow bằng cách tạo chuỗi cho một lệnh gọi đến hàm .stateIn().

Lệnh gọi đến hàm .stateIn() yêu cầu 3 đối số:

  1. Đối với tham số đầu tiên, hãy truyền viewModelScope. Đây là phạm vi coroutine liên kết với ViewModel.
  2. Đối với tham số thứ hai, hãy truyền SharingStarted.WhileSubscribed(5_000). Phương thức này kiểm soát thời điểm bắt đầu và ngừng chia sẻ.
  3. Đối với tham số thứ ba, hãy truyền BlurUiState.Default. Đây là giá trị ban đầu của flow trạng thái.

ui/BlurViewModel.kt

import kotlinx.coroutines.flow.stateIn
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
// ...

    val blurUiState: StateFlow<BlurUiState> = bluromaticRepository.outputWorkInfo
        .map { info ->
            when {
                info.state.isFinished -> {
                    BlurUiState.Complete(outputUri = "")
                }
                info.state == WorkInfo.State.CANCELLED -> {
                    BlurUiState.Default
                }
                else -> BlurUiState.Loading
            }
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = BlurUiState.Default
        )

// ...

ViewModel cho thấy thông tin trạng thái giao diện người dùng dưới dạng StateFlow thông qua biến blurUiState. Flow chuyển đổi từ Flow nguội sang StateFlow nóng bằng cách gọi hàm stateIn().

Cập nhật giao diện người dùng

Trong tệp ui/BluromaticScreen.kt, bạn sẽ lấy trạng thái giao diện người dùng từ biến blurUiState của ViewModel rồi cập nhật giao diện người dùng.

Khối when kiểm soát giao diện người dùng của ứng dụng. Khối when này có một nhánh cho mỗi trạng thái trong số 3 trạng thái BlurUiState.

Giao diện người dùng cập nhật trong thành phần kết hợp BlurActions bên trong thành phần kết hợp Row. Hãy hoàn tất các bước sau:

  1. Xoá mã Button(onStartClick) bên trong Thành phần kết hợp Row rồi thay thế mã đó bằng một khối whenblurUiState làm đối số.

ui/BluromaticScreen.kt

...
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        // REMOVE
        // Button(
        //     onClick = onStartClick,
        //     modifier = Modifier.fillMaxWidth()
        // ) {
        //     Text(stringResource(R.string.start))
        // }
        // ADD
        when (blurUiState) {
        }
    }
...

Khi mở, ứng dụng sẽ ở trạng thái mặc định. Trạng thái này trong mã được biểu thị dưới dạng BlurUiState.Default.

  1. Bên trong khối when, hãy tạo một nhánh cho trạng thái này như trong mã ví dụ sau đây:

ui/BluromaticScreen.kt

...
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        when (blurUiState) {
            is BlurUiState.Default -> {}
        }
    }
...

Đối với trạng thái mặc định, ứng dụng cho thấy nút Start (Bắt đầu).

  1. Đối với tham số onClick ở trạng thái BlurUiState.Default, hãy truyền biến onStartClick đang được truyền đến thành phần kết hợp.
  2. Đối với tham số stringResourceId, hãy truyền mã tài nguyên chuỗi của R.string.start.

ui/BluromaticScreen.kt

...
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        when (blurUiState) {
            is BlurUiState.Default -> {
                Button(
                    onClick = onStartClick,
                    modifier = Modifier.fillMaxWidth()
                ) {
                    Text(stringResource(R.string.start))
                }
        }
    }
...

Khi ứng dụng chủ động làm mờ hình ảnh, đó là trạng thái BlurUiState.Loading. Đối với trạng thái này, ứng dụng cho thấy nút Cancel Work (Huỷ công việc) và một chỉ báo tiến trình vòng tròn.

  1. Đối với tham số onClick của nút ở trạng thái BlurUiState.Loading, hãy truyền biến onCancelClick đang được truyền đến thành phần kết hợp.
  2. Đối với tham số stringResourceId của nút, hãy truyền mã tài nguyên chuỗi của R.string.cancel_work.

ui/BluromaticScreen.kt

import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilledTonalButton
...
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        when (blurUiState) {
            is BlurUiState.Default -> {
                Button(onStartClick) { Text(stringResource(R.string.start)) }
            }
            is BlurUiState.Loading -> {
               FilledTonalButton(onCancelClick) { Text(stringResource(R.string.cancel_work)) }
               CircularProgressIndicator(modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)))
            }
        }
    }
...

Trạng thái cuối cùng cần định cấu hình là trạng thái BlurUiState.Complete, xuất hiện sau khi hình ảnh được làm mờ và lưu. Hiện tại, ứng dụng chỉ cho thấy nút Start (Bắt đầu).

  1. Đối với tham số onClick ở trạng thái BlurUiState.Complete, hãy truyền biến onStartClick.
  2. Đối với tham số stringResourceId, hãy truyền mã tài nguyên chuỗi của R.string.start.

ui/BluromaticScreen.kt

...
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        when (blurUiState) {
            is BlurUiState.Default -> {
                Button(onStartClick) { Text(stringResource(R.string.start)) }
            }
            is BlurUiState.Loading -> {
                FilledTonalButton(onCancelClick) { Text(stringResource(R.string.cancel_work)) }
                CircularProgressIndicator(modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)))
            }
            is BlurUiState.Complete -> {
                Button(onStartClick) { Text(stringResource(R.string.start)) }
            }
        }
    }
...

Chạy ứng dụng

  1. Chạy ứng dụng rồi nhấp vào Start (Bắt đầu).
  2. Tham khảo cửa sổ Background Task Inspector (Công cụ kiểm tra tác vụ trong nền) để xem các trạng thái tương ứng với giao diện người dùng mà bạn đang thấy.

SystemJobService là thành phần chịu trách nhiệm quản lý quá trình thực thi Worker.

Khi worker đang chạy, giao diện người dùng sẽ cho thấy nút Cancel Work (Huỷ công việc) và một chỉ báo tiến trình dạng vòng tròn.

3395cc370b580b32.png

c5622f923670cf67.png

Sau khi worker chạy xong, giao diện người dùng sẽ cập nhật để cho thấy nút Start (Bắt đầu) như dự kiến.

97252f864ea042aa.png

81ba9962a8649e70.png

5. Cho thấy kết quả cuối cùng

Trong phần này, bạn định cấu hình ứng dụng để cho thấy nút được gắn nhãn See File (Xem tệp) bất cứ khi nào có hình ảnh được làm mờ sẵn sàng xuất hiện.

Tạo nút See File (Xem tệp)

Nút See File (Xem tệp) chỉ xuất hiện khi BlurUiState ở trạng thái Complete.

  1. Mở tệp ui/BluromaticScreen.kt rồi chuyển đến thành phần kết hợp BlurActions.
  2. Để thêm khoảng cách giữa nút Start (Bắt đầu) và nút See File (Xem tệp), hãy thêm một thành phần kết hợp Spacer trong khối BlurUiState.Complete.
  3. Thêm một thành phần kết hợp FilledTonalButton mới.
  4. Đối với tham số onClick, hãy truyền onSeeFileClick(blurUiState.outputUri).
  5. Thêm một thành phần kết hợp Text cho tham số nội dung của Button.
  6. Đối với tham số text của Text, hãy sử dụng mã tài nguyên chuỗi R.string.see_file.

ui/BluromaticScreen.kt

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width

// ...
is BlurUiState.Complete -> {
    Button(onStartClick) { Text(stringResource(R.string.start)) }
    // Add a spacer and the new button with a "See File" label
    Spacer(modifier = Modifier.width(dimensionResource(R.dimen.padding_small)))
    FilledTonalButton({ onSeeFileClick(blurUiState.outputUri) })
    { Text(stringResource(R.string.see_file)) }
}
// ...

Cập nhật blurUiState

Trạng thái BlurUiState được đặt trong ViewModel, phụ thuộc vào trạng thái của yêu cầu công việc và có thể là biến bluromaticRepository.outputWorkInfo.

  1. Trong tệp ui/BlurViewModel.kt, bên trong phương thức biến đổi map(), hãy tạo một biến outputImageUri mới.
  2. Điền URI của hình ảnh đã lưu biến mới này qua đối tượng dữ liệu outputData.

Bạn có thể truy xuất chuỗi này bằng khoá KEY_IMAGE_URI.

ui/BlurViewModel.kt

import com.example.bluromatic.KEY_IMAGE_URI

// ...
.map { info ->
    val outputImageUri = info.outputData.getString(KEY_IMAGE_URI)
    when {
// ...
  1. Nếu worker này đã hoàn tất và biến được điền, thì tức là có hình ảnh đã làm mờ để hiển thị.

Bạn có thể kiểm tra xem biến này đã được điền hay chưa bằng cách gọi outputImageUri.isNullOrEmpty().

  1. Cập nhật nhánh isFinished để kiểm tra xem biến đó đã được điền hay chưa, sau đó truyền biến outputImageUri vào đối tượng dữ liệu BlurUiState.Complete.

ui/BlurViewModel.kt

// ...
.map { info ->
    val outputImageUri = info.outputData.getString(KEY_IMAGE_URI)
    when {
        info.state.isFinished && !outputImageUri.isNullOrEmpty() -> {
            BlurUiState.Complete(outputUri = outputImageUri)
        }
        info.state == WorkInfo.State.CANCELLED -> {
// ...

Tạo mã sự kiện nhấp chuột See File (xem tệp)

Khi người dùng nhấp vào nút See File (Xem tệp), trình xử lý onClick sẽ gọi hàm được chỉ định. Hàm này truyền dưới dạng một đối số trong lệnh gọi thành phần kết hợp BlurActions().

Mục đích của hàm này là cho thấy hình ảnh đã lưu qua URI của hàm. Hàm này gọi hàm trợ giúp showBlurredImage() rồi truyền vào URI. Hàm trợ giúp tạo ý định và dùng ý định này để bắt đầu một hoạt động mới nhằm cho thấy hình ảnh đã lưu.

  1. Mở tệp ui/BluromaticScreen.kt.
  2. Trong hàm BluromaticScreenContent(), trong lệnh gọi đến hàm có khả năng kết hợp BlurActions(), hãy bắt đầu tạo một hàm lambda cho tham số onSeeFileClick để nhận một tham số có tên là currentUri. Phương pháp này lưu trữ URI của hình ảnh đã lưu.

ui/BluromaticScreen.kt

// ...
BlurActions(
    blurUiState = blurUiState,
    onStartClick = { applyBlur(selectedValue) },
    onSeeFileClick = { currentUri ->
    },
    onCancelClick = { cancelWork() },
    modifier = Modifier.fillMaxWidth()
)
// ...
  1. Bên trong phần nội dung của hàm lambda, hãy gọi hàm trợ giúp showBlurredImage().
  2. Đối với tham số đầu tiên, hãy truyền biến context.
  3. Đối với tham số thứ hai, hãy truyền biến currentUri.

ui/BluromaticScreen.kt

// ...
BlurActions(
    blurUiState = blurUiState,
    onStartClick = { applyBlur(selectedValue) },
    // New lambda code runs when See File button is clicked
    onSeeFileClick = { currentUri ->
        showBlurredImage(context, currentUri)
    },
    onCancelClick = { cancelWork() },
    modifier = Modifier.fillMaxWidth()
)
// ...

Chạy ứng dụng

Bây giờ, khi chạy ứng dụng, bạn sẽ thấy nút See File (Xem tệp) mới, có thể nhấp được. Nút này sẽ đưa bạn đến tệp đã lưu:

9d76d5d7f231c6b6.png

926e532cc24a0d4f.png

6. Huỷ công việc

5cec830cc8ef647e.png

Trước đó, bạn đã thêm nút Cancel Work (Huỷ công việc) nên giờ đây, bạn có thể thêm mã để nút này làm gì đó. Với WorkManager, bạn có thể huỷ công việc bằng cách dùng mã, thẻ và tên chuỗi riêng biệt.

Trong trường hợp này, bạn nên huỷ công việc qua tên chuỗi riêng biệt vì bạn muốn huỷ tất cả công việc trong chuỗi chứ không chỉ một bước cụ thể.

Huỷ công việc theo tên

  1. Mở tệp data/WorkManagerBluromaticRepository.kt.
  2. Trong hàm cancelWork(), hãy gọi hàm workManager.cancelUniqueWork().
  3. Truyền tên chuỗi riêng biệt IMAGE_MANIPULATION_WORK_NAME để lệnh gọi chỉ huỷ công việc đã lên lịch có tên đó.

data/WorkManagerBluromaticRepository.kt

override fun cancelWork() {
    workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
}

Theo nguyên tắc thiết kế tách biệt vấn đề, các hàm có khả năng kết hợp không được tương tác trực tiếp với kho lưu trữ. Các hàm có khả năng kết hợp tương tác với ViewModel, rồi ViewModel tương tác với kho lưu trữ.

Đây là một nguyên tắc thiết kế phù hợp khi các thay đổi đối với kho lưu trữ không yêu cầu bạn thay đổi các hàm có khả năng kết hợp vì chúng không tương tác trực tiếp.

  1. Mở tệp ui/BlurViewModel.kt.
  2. Tạo một hàm mới có tên là cancelWork() để huỷ công việc.
  3. Bên trong hàm, trên đối tượng bluromaticRepository, hãy gọi phương thức cancelWork().

ui/BlurViewModel.kt

/**
 * Call method from repository to cancel any ongoing WorkRequest
 * */
fun cancelWork() {
    bluromaticRepository.cancelWork()
}

Thiết lập sự kiện nhấp chuột Cancel Work (Huỷ công việc)

  1. Mở tệp ui/BluromaticScreen.kt.
  2. Chuyển đến hàm có khả năng kết hợp BluromaticScreen().

ui/BluromaticScreen.kt

fun BluromaticScreen(blurViewModel: BlurViewModel = viewModel(factory = BlurViewModel.Factory)) {
    val uiState by blurViewModel.blurUiState.collectAsStateWithLifecycle()
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        modifier = Modifier
            .fillMaxSize()
            .statusBarsPadding()
            .padding(
                start = WindowInsets.safeDrawing
                    .asPaddingValues()
                    .calculateStartPadding(layoutDirection),
                end = WindowInsets.safeDrawing
                    .asPaddingValues()
                    .calculateEndPadding(layoutDirection)
            )
    ) {
        BluromaticScreenContent(
            blurUiState = uiState,
            blurAmountOptions = blurViewModel.blurAmount,
            applyBlur = blurViewModel::applyBlur,
            cancelWork = {},
            modifier = Modifier
                .verticalScroll(rememberScrollState())
                .padding(dimensionResource(R.dimen.padding_medium))
        )
    }
}

Bên trong lệnh gọi thành phần kết hợp BluromaticScreenContent, bạn sẽ muốn phương thức cancelWork() của ViewModel chạy khi người dùng nhấp vào nút.

  1. Gán giá trị blurViewModel::cancelWork cho tham số cancelWork.

ui/BluromaticScreen.kt

// ...
        BluromaticScreenContent(
            blurUiState = uiState,
            blurAmountOptions = blurViewModel.blurAmount,
            applyBlur = blurViewModel::applyBlur,
            cancelWork = blurViewModel::cancelWork,
            modifier = Modifier
                .verticalScroll(rememberScrollState())
                .padding(dimensionResource(R.dimen.padding_medium))
        )
// ...

Chạy ứng dụng và huỷ công việc

Hãy chạy ứng dụng của bạn, ứng dụng sẽ biên dịch hiệu quả. Bắt đầu làm mờ ảnh rồi nhấp vào Cancel Work (Huỷ công việc). Toàn bộ chuỗi này đã bị huỷ!

81ba9962a8649e70.png

Sau khi bạn huỷ công việc, chỉ có nút Start (Bắt đầu) xuất hiện vì WorkInfo.StateCANCELLED. Thay đổi này khiến biến blurUiState được đặt thành BlurUiState.Default. Quá trình này đặt lại giao diện người dùng về trạng thái ban đầu và chỉ cho thấy nút Start (Bắt đầu).

Công cụ kiểm tra tác vụ trong nền cho thấy trạng thái Cancelled (Đã huỷ) như dự kiến.

7656dd320866172e.png

7. Các quy tắc ràng buộc đối với công việc

Cuối cùng nhưng không kém phần quan trọng, WorkManager hỗ trợ Constraints. Quy tắc ràng buộc là một yêu cầu mà bạn phải đáp ứng trước khi WorkRequest chạy.

Ví dụ về một số quy tắc ràng buộc: requiresDeviceIdle()requiresStorageNotLow().

  • Đối với quy tắc ràng buộc requiresDeviceIdle(), nếu được truyền giá trị true, công việc chỉ chạy nếu thiết bị ở trạng thái rảnh.
  • Đối với quy tắc ràng buộc requiresStorageNotLow(), nếu được truyền giá trị true thì công việc chỉ chạy nếu bộ nhớ vẫn còn nhiều.

Đối với Blur-O-Matic, bạn thêm quy tắc ràng buộc rằng thiết bị phải còn nhiều pin trước khi chạy yêu cầu công việc blurWorker. Quy tắc ràng buộc này đồng nghĩa với việc yêu cầu công việc của bạn bị trì hoãn và chỉ chạy khi pin của thiết bị vẫn còn nhiều.

Tạo quy tắc ràng buộc rằng pin của thiết bị phải còn nhiều

Trong tệp data/WorkManagerBluromaticRepository.kt, hãy hoàn tất các bước sau đây:

  1. Chuyển đến phương thức applyBlur().
  2. Sau khi mã khai báo biến continuation, hãy tạo một biến mới có tên là constraints. Biến này chứa đối tượng Constraints cho quy tắc ràng buộc đang được tạo.
  3. Tạo trình tạo cho đối tượng Quy tắc ràng buộc bằng cách gọi hàm Constraints.Builder() rồi chỉ định hàm đó cho biến mới.

data/WorkManagerBluromaticRepository.kt

import androidx.work.Constraints

// ...
    override fun applyBlur(blurLevel: Int) {
        // ...

        val constraints = Constraints.Builder()
// ...
  1. Tạo chuỗi cho phương thức setRequiresBatteryNotLow() đến cuộc gọi rồi truyền phương thức này một giá trị true để WorkRequest chỉ chạy khi pin của thiết bị vẫn còn nhiều.

data/WorkManagerBluromaticRepository.kt

// ...
    override fun applyBlur(blurLevel: Int) {
        // ...

        val constraints = Constraints.Builder()
            .setRequiresBatteryNotLow(true)
// ...
  1. Tạo đối tượng bằng cách tạo chuỗi cho lệnh gọi đến phương thức .build().

data/WorkManagerBluromaticRepository.kt

// ...
    override fun applyBlur(blurLevel: Int) {
        // ...

        val constraints = Constraints.Builder()
            .setRequiresBatteryNotLow(true)
            .build()
// ...
  1. Để thêm đối tượng ràng buộc vào yêu cầu công việc blurBuilder, hãy tạo chuỗi cho một lệnh gọi đến phương thức .setConstraints() rồi truyền vào đối tượng ràng buộc.

data/WorkManagerBluromaticRepository.kt

// ...
blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))

blurBuilder.setConstraints(constraints) // Add this code
//...

Kiểm thử bằng trình mô phỏng

  1. Trên trình mô phỏng, hãy thay đổi Charge level (Mức pin) trong cửa sổ Extended Controls (Chế độ điều khiển mở rộng) thành 15% trở xuống để mô phỏng tình huống pin yếu, Charger connection (Kết nối bộ sạc) thành AC charger (Bộ sạc AC) và Battery status (Trạng thái pin) thành Not charging (Đang không sạc).

9b0084cb6e1a8672.png

  1. Chạy ứng dụng rồi nhấp vào Start (Bắt đầu) để bắt đầu làm mờ hình ảnh.

Mức pin của trình mô phỏng được đặt thành thấp, nên WorkManager không chạy yêu cầu công việc blurWorker vì có quy tắc ràng buộc. Yêu cầu công việc này được đưa vào hàng đợi nhưng bị hoãn cho đến khi đáp ứng quy tắc ràng buộc. Bạn có thể xem độ trễ này trong thẻ Công cụ kiểm tra tác vụ trong nền.

7518cf0353d04f12.png

  1. Sau khi bạn xác nhận rằng công việc không chạy, hãy tăng dần mức pin.

Quy tắc ràng buộc này được đáp ứng sau khi mức pin đạt khoảng 25% và công việc bị trì hoãn sẽ chạy. Kết quả này xuất hiện trong thẻ Công cụ kiểm tra tác vụ trong nền.

ab189db49e7b8997.png

8. Viết chương trình kiểm thử cho phương thức triển khai Worker

Cách kiểm thử WorkManager

Việc viết kiểm thử cho Worker và kiểm thử bằng WorkManager API có thể khá trái ngược. Công việc được thực hiện trong Worker không có quyền truy cập trực tiếp vào giao diện người dùng, đây hoàn toàn là logic nghiệp vụ. Thường thì bạn kiểm thử logic nghiệp vụ bằng các kiểm thử đơn vị cục bộ. Tuy nhiên, có thể bạn còn nhớ lớp học lập trình về cách Làm việc ở chế độ nền bằng WorkManager rằng WorkManger yêu cầu phải có Ngữ cảnh trên Android thì mới chạy được. Theo mặc định, ngữ cảnh không có sẵn trong kiểm thử đơn vị cục bộ. Do đó, bạn phải kiểm thử Worker bằng kiểm thử giao diện người dùng, mặc dù không có thành phần trực tiếp nào trên giao diện người dùng để kiểm thử.

Thiết lập phần phụ thuộc

Bạn cần thêm 3 phần phụ thuộc gradle vào dự án. Hai phần phụ thuộc đầu tiên cho phép JUnit và espresso đối với kiểm thử giao diện người dùng. Phần phụ thuộc thứ ba cung cấp API kiểm thử công việc.

app/build.gradle.kts

dependencies {
    // Espresso
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    // Junit
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    // Work testing
    androidTestImplementation("androidx.work:work-testing:2.8.1")
}

Bạn phải sử dụng phiên bản phát hành ổn định mới nhất của work-runtime-ktx trong ứng dụng. Nếu bạn thay đổi phiên bản này, đừng quên nhấp vào Sync Now (Đồng bộ hoá ngay) để đồng bộ hoá dự án với các tệp gradle đã cập nhật.

Tạo lớp kiểm thử

  1. Tạo thư mục cho các kiểm thử giao diện người dùng trong thư mục app > src. a7768e9b6ea994d3.png

20cc54de1756c884.png

  1. Tạo một lớp Kotlin mới trong thư mục androidTest/java có tên là WorkerInstrumentationTest.

Viết một chương trình kiểm thử CleanupWorker

Làm theo các bước để viết kiểm thử nhằm xác minh quy trình triển khai CleanupWorker. Hãy cố gắng tự triển khai phương thức xác minh này dựa trên hướng dẫn. Giải pháp sẽ được cung cấp ở cuối các bước.

  1. Trong WorkerInstrumentationTest.kt, hãy tạo một biến lateinit để lưu giữ một thực thể của Context.
  2. Tạo một phương thức setUp() được chú thích bằng @Before.
  3. Trong phương thức setUp(), hãy khởi tạo biến ngữ cảnh lateinit có ngữ cảnh ứng dụng qua ApplicationProvider.
  4. Tạo một hàm kiểm thử có tên cleanupWorker_doWork_resultSuccess().
  5. Trong kiểm thử cleanupWorker_doWork_resultSuccess(), hãy tạo một thực thể của CleanupWorker.

WorkerInstrumentationTest.kt

class WorkerInstrumentationTest {
   private lateinit var context: Context

   @Before
   fun setUp() {
       context = ApplicationProvider.getApplicationContext()
   }

   @Test
   fun cleanupWorker_doWork_resultSuccess() {
   }
}

Khi viết ứng dụng Blur-O-Matic, bạn sẽ sử dụng OneTimeWorkRequestBuilder để tạo worker. Kiểm thử Worker cần nhiều trình tạo công việc. WorkManager API cung cấp hai trình tạo:

Cả hai trình tạo này đều cho phép bạn kiểm thử logic nghiệp vụ của worker. Đối với CoroutineWorkers, chẳng hạn như CleanupWorker, BlurWorkerSaveImageToFileWorker, hãy sử dụng TestListenableWorkerBuilder để kiểm thử vì nó xử lý độ phức tạp của luồng của coroutine.

  1. CoroutineWorker chạy không đồng bộ vì sử dụng coroutine. Để thực thi worker này song song, hãy sử dụng runBlocking. Cung cấp cho worker này một phần nội dung lambda trống để bắt đầu, nhưng bạn sử dụng runBlocking để hướng dẫn nó trực tiếp đến doWork() thay vì xếp nó vào hàng đợi.

WorkerInstrumentationTest.kt

class WorkerInstrumentationTest {
   private lateinit var context: Context

   @Before
   fun setUp() {
       context = ApplicationProvider.getApplicationContext()
   }

   @Test
   fun cleanupWorker_doWork_resultSuccess() {
       val worker = TestListenableWorkerBuilder<CleanupWorker>(context).build()
       runBlocking {
       }
   }
}
  1. Trong phần nội dung lambda của runBlocking, hãy gọi doWork() trên thực thể của CleanupWorker mà bạn đã tạo ở bước 5 rồi lưu dưới dạng giá trị.

Bạn có thể nhớ lại rằng CleanupWorker sẽ xoá mọi tệp PNG đã lưu trong cấu trúc tệp của ứng dụng Blur-O-Matic. Quá trình này liên quan đến việc nhập/xuất tệp, tức là có thể các trường hợp ngoại lệ sẽ được gửi trong khi bạn cố gắng xoá tệp. Vì lý do này, nỗ lực xoá tệp được gói trong một khối try.

CleanupWorker.kt

...
            return@withContext try {
                val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
                if (outputDirectory.exists()) {
                    val entries = outputDirectory.listFiles()
                    if (entries != null) {
                        for (entry in entries) {
                            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) {
                Log.e(
                    TAG,
                    applicationContext.resources.getString(R.string.error_cleaning_file),
                    exception
                )
                Result.failure()
            }

Hãy lưu ý rằng ở cuối khối try, hệ thống trả về Result.success(). Nếu mã được gửi đến Result.success() thành công thì sẽ không có lỗi khi truy cập vào thư mục tệp.

Giờ là lúc xác nhận rằng worker đã thành công.

  1. Xác nhận rằng kết quả của worker là ListenableWorker.Result.success().

Hãy xem mã giải pháp sau:

WorkerInstrumentationTest.kt

class WorkerInstrumentationTest {
   private lateinit var context: Context

   @Before
   fun setUp() {
       context = ApplicationProvider.getApplicationContext()
   }

   @Test
   fun cleanupWorker_doWork_resultSuccess() {
       val worker = TestListenableWorkerBuilder<CleanupWorker>(context).build()
       runBlocking {
           val result = worker.doWork()
           assertTrue(result is ListenableWorker.Result.Success)
       }
   }
}

Viết kiểm thử BlurWorker

Làm theo các bước để viết kiểm thử nhằm xác minh quy trình triển khai BlurWorker. Hãy cố gắng tự triển khai phương thức xác minh này dựa trên hướng dẫn. Giải pháp sẽ được cung cấp ở cuối các bước.

  1. Trong WorkerInstrumentationTest.kt, hãy tạo một hàm kiểm thử mới có tên là blurWorker_doWork_resultSuccessReturnsUri().

BlurWorker cần có hình ảnh để xử lý. Do đó, việc xây dựng một thực thể của BlurWorker yêu cầu một số dữ liệu đầu vào bao gồm cả hình ảnh như vậy.

  1. Ngoài hàm kiểm thử, hãy tạo một dữ liệu đầu vào URI mô phỏng. URI mô phỏng là một cặp chứa một khoá và một giá trị URI. Hãy sử dụng mã ví dụ sau đây cho cặp khoá-giá trị:
KEY_IMAGE_URI to "android.resource://com.example.bluromatic/drawable/android_cupcake"
  1. Tạo BlurWorker bên trong hàm blurWorker_doWork_resultSuccessReturnsUri() và đảm bảo truyền dữ liệu đầu vào URI mô phỏng mà bạn tạo dưới dạng dữ liệu công việc thông qua phương thức setInputData().

Tương tự như kiểm thử CleanupWorker, bạn phải gọi phương thức triển khai worker bên trong runBlocking.

  1. Tạo một khối runBlocking.
  2. Gọi doWork() bên trong khối runBlocking.

Không giống như CleanupWorker, BlurWorker có một số dữ liệu đầu ra rất phù hợp để kiểm thử!

  1. Để truy cập vào dữ liệu đầu ra, hãy trích xuất URI qua kết quả của doWork().

WorkerInstrumentationTest.kt

@Test
fun blurWorker_doWork_resultSuccessReturnsUri() {
    val worker = TestListenableWorkerBuilder<BlurWorker>(context)
        .setInputData(workDataOf(mockUriInput))
        .build()
    runBlocking {
        val result = worker.doWork()
        val resultUri = result.outputData.getString(KEY_IMAGE_URI)
    }
}
  1. Xác nhận rằng worker đã thành công. Ví dụ: hãy xem mã sau đây qua BlurWorker:

BlurWorker.kt

val resourceUri = inputData.getString(KEY_IMAGE_URI)
val blurLevel = inputData.getInt(BLUR_LEVEL, 1)

...
val picture = BitmapFactory.decodeStream(
    resolver.openInputStream(Uri.parse(resourceUri))
)

val output = blurBitmap(picture, blurLevel)

// Write bitmap to a temp file
val outputUri = writeBitmapToFile(applicationContext, output)

val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())

Result.success(outputData)
...

BlurWorker lấy URI và mức độ mờ trong dữ liệu đầu vào rồi tạo một tệp tạm thời. Nếu thành công thì thao tác này sẽ trả về một cặp khoá-giá trị chứa URI. Để kiểm tra xem nội dung của dữ liệu đầu ra có chính xác hay không, hãy xác nhận rằng dữ liệu đầu ra chứa khoá KEY_IMAGE_URI.

  1. Xác nhận rằng dữ liệu đầu ra chứa URI bắt đầu bằng chuỗi "file:///data/user/0/com.example.bluromatic/files/blur_filter_outputs/blur-filter-output-"
  1. Xem lại kiểm thử của bạn bằng mã giải pháp sau:

WorkerInstrumentationTest.kt

    @Test
    fun blurWorker_doWork_resultSuccessReturnsUri() {
        val worker = TestListenableWorkerBuilder<BlurWorker>(context)
            .setInputData(workDataOf(mockUriInput))
            .build()
        runBlocking {
            val result = worker.doWork()
            val resultUri = result.outputData.getString(KEY_IMAGE_URI)
            assertTrue(result is ListenableWorker.Result.Success)
            assertTrue(result.outputData.keyValueMap.containsKey(KEY_IMAGE_URI))
            assertTrue(
                resultUri?.startsWith("file:///data/user/0/com.example.bluromatic/files/blur_filter_outputs/blur-filter-output-")
                    ?: false
            )
        }
    }

Viết kiểm thử SaveImageToFileWorker

Đúng như tên gọi, SaveImageToFileWorker sẽ ghi một tệp vào ổ đĩa. Hãy nhớ rằng trong WorkManagerBluromaticRepository, bạn sẽ thêm SaveImageToFileWorker vào WorkManager dưới dạng phần tiếp theo của BlurWorker. Do đó, phương thức này có cùng dữ liệu đầu vào. Phương thức này lấy URI trong dữ liệu đầu vào, tạo bitmap rồi ghi bitmap đó vào ổ đĩa dưới dạng tệp. Nếu thao tác thành công thì kết quả sẽ là một URL hình ảnh. Kiểm thử cho SaveImageToFileWorker rất giống với kiểm thử cho BlurWorker, điểm khác biệt duy nhất là dữ liệu đầu ra.

Hãy xem liệu bạn có thể tự viết kiểm thử cho SaveImageToFileWorker hay không! Khi hoàn tất, bạn có thể xem giải pháp ở phần bên dưới. Hãy nhớ lại phương pháp bạn đã thực hiện đối với kiểm thử cho BlurWorker:

  1. Tạo worker, truyền dữ liệu đầu vào.
  2. Tạo một khối runBlocking.
  3. Gọi doWork() trên worker.
  4. Kiểm tra xem kết quả có thành công hay không.
  5. Kiểm tra dữ liệu đầu ra để tìm khoá và giá trị chính xác.

Sau đây là giải pháp:

@Test
fun saveImageToFileWorker_doWork_resultSuccessReturnsUrl() {
    val worker = TestListenableWorkerBuilder<SaveImageToFileWorker>(context)
        .setInputData(workDataOf(mockUriInput))
        .build()
    runBlocking {
        val result = worker.doWork()
        val resultUri = result.outputData.getString(KEY_IMAGE_URI)
        assertTrue(result is ListenableWorker.Result.Success)
        assertTrue(result.outputData.keyValueMap.containsKey(KEY_IMAGE_URI))
        assertTrue(
            resultUri?.startsWith("content://media/external/images/media/")
                ?: false
        )
    }
}

9. Gỡ lỗi WorkManager bằng Công cụ kiểm tra tác vụ trong nền

Kiểm tra worker

Kiểm thử tự động là một cách hay để xác minh chức năng của Worker. Tuy nhiên, chúng không cung cấp nhiều tiện ích khi bạn muốn gỡ lỗi cho Worker. May mắn là Android Studio có một công cụ hỗ trợ bạn trực quan hoá, giám sát và gỡ lỗi cho Worker theo thời gian thực. Công cụ kiểm tra tác vụ trong nền hoạt động với trình mô phỏng và thiết bị chạy API cấp 26 trở lên.

Trong phần này, bạn sẽ tìm hiểu một số tính năng mà Công cụ kiểm tra tác vụ trong nền cung cấp để kiểm tra worker trong Blur-O-Matic.

  1. Chạy ứng dụng Blur-O-Matic trên một thiết bị hoặc trình mô phỏng.
  2. Chuyển đến phần View > Tool Windows > App Inspection (Xem > Cửa sổ công cụ > Kiểm tra ứng dụng).

798f10dfd8d74bb1.png

  1. Chọn thẻ Background Task Inspector (Công cụ kiểm tra tác vụ trong nền).

d601998f3754e793.png

  1. Nếu cần, hãy chọn thiết bị và quy trình đang chạy trong trình đơn thả xuống.

Trong các hình ảnh ví dụ, quy trình là com.example.bluromatic. Có thể quy trình sẽ tự động được chọn cho bạn. Nếu hệ thống chọn sai quy trình thì bạn có thể thay đổi quy trình đó.

6428a2ab43fc42d1.png

  1. Nhấp vào trình đơn thả xuống của Worker. Hiện tại, không có worker nào đang chạy, điều này hợp lý vì chưa có lượt thử làm mờ hình ảnh nào.

cf8c466b3fd7fed1.png

  1. Trong ứng dụng, hãy chọn More blurred (Làm mờ hơn) rồi nhấp vào Start (Bắt đầu). Bạn sẽ thấy ngay một số nội dung trong trình đơn thả xuống Workers.

Bây giờ, bạn sẽ thấy nội dung tương tự trong trình đơn thả xuống của Worker.

569a8e0c1c6993ce.png

Bảng Worker cho thấy tên của Worker, Dịch vụ (trong trường hợp này là SystemJobService), trạng thái của mỗi Worker và dấu thời gian. Trong ảnh chụp màn hình ở bước trước, hãy lưu ý rằng BlurWorkerCleanupWorker đã hoàn tất công việc.

Bạn cũng có thể huỷ công việc bằng cách sử dụng công cụ kiểm tra.

  1. Chọn một worker trong hàng đợi rồi nhấp vào biểu tượng Cancel Selected Worker (Huỷ worker đã chọn) 7108c2a82f64b348.png trong thanh công cụ.

Kiểm tra thông tin chi tiết về tác vụ

  1. Nhấp vào một worker trong bảng Worker. 97eac5ad23c41127.png

Thao tác này sẽ mở cửa sổ Task Details (Thông tin chi tiết về tác vụ).

9d4e17f7d4afa6bd.png

  1. Xem xét thông tin xuất hiện trong Task Details (Thông tin chi tiết về tác vụ). 59fa1bf4ad8f4d8d.png

Thông tin chi tiết này đưa ra những danh mục sau:

  • Description (Mô tả): Phần này liệt kê tên lớp Worker cùng gói đủ điều kiện, cũng như thẻ được chỉ định và mã nhận dạng duy nhất (UUID) của worker này.
  • Execution (Thực thi): Phần này cho thấy các quy tắc ràng buộc của worker (nếu có), tần suất chạy, trạng thái của worker và lớp nào đã tạo và đưa worker này vào hàng đợi. Hãy nhớ rằng BlurWorker có một quy tắc ràng buộc ngăn không cho phương thức này thực thi khi pin yếu. Khi bạn kiểm tra một Worker có các quy tắc ràng buộc, chúng sẽ xuất hiện trong phần này.
  • WorkContuniation: Phần này cho biết vị trí của worker này trong chuỗi công việc. Để kiểm tra thông tin chi tiết của một worker khác trong chuỗi công việc, hãy nhấp vào mã nhận dạng duy nhất (UUID) của worker đó.
  • Results (Kết quả): Phần này cho biết thời gian bắt đầu, số lần thử lại và dữ liệu đầu ra của worker được chọn.

Chế độ xem biểu đồ

Hãy nhớ là worker trong Blur-O-Matic đều được tạo chuỗi. Công cụ kiểm tra tác vụ trong nền cung cấp chế độ xem biểu đồ giúp thể hiện các phần phụ thuộc của worker một cách trực quan.

Ở góc cửa sổ Công cụ kiểm tra tác vụ trong nền, có 2 nút để chuyển đổi: Show Graph View (Hiện chế độ xem biểu đồ) và Show List View (Hiện chế độ xem danh sách).

4cd96a8b2773f466.png

  1. Nhấp vào biểu tượng Show Graph View (Hiện chế độ xem biểu đồ) 6f871bb00ad8b11a.png:

ece206da18cfd1c9.png

Chế độ xem biểu đồ cho biết chính xác phần phụ thuộc Worker được triển khai trong ứng dụng Blur-O-Matic.

  1. Nhấp vào biểu tượng Show List View (Hiện chế độ xem danh sách) 669084937ea340f5.png để thoát khỏi chế độ xem biểu đồ.

Các tính năng khác

Ứng dụng Blur-O-Matic chỉ triển khai Worker để hoàn thành tác vụ ở chế độ nền. Tuy nhiên, bạn có thể đọc thêm về các công cụ hiện có để kiểm tra các loại công việc khác ở chế độ nền trong tài liệu về Công cụ kiểm tra tác vụ trong nền.

10. Lấy đoạn mã giải pháp

Để tải mã này xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng các lệnh sau:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git
$ cd basic-android-kotlin-compose-training-workmanager
$ git checkout main

Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp ZIP, sau đó giải nén rồi mở trong Android Studio.

Nếu bạn muốn xem mã giải pháp cho lớp học lập trình này, hãy xem mã đó trên GitHub.

11. Xin chúc mừng

Xin chúc mừng! Bạn đã tìm hiểu về chức năng bổ sung của WorkManger, viết kiểm thử tự động cho worker Blur-O-Matic và sử dụng Công cụ kiểm tra tác vụ trong nền để kiểm tra chúng. Trong lớp học lập trình này, bạn đã tìm hiểu:

  • Cách đặt tên cho các chuỗi WorkRequest riêng biệt.
  • Cách gắn thẻ cho WorkRequest.
  • Cách cập nhật giao diện người dùng dựa trên WorkInfo.
  • Cách huỷ WorkRequest.
  • Cách thêm quy tắc ràng buộc cho WorkRequest.
  • API kiểm thử cho WorkManager.
  • Cách tiếp cận quy trình triển khai worker kiểm thử.
  • Cách kiểm thử CoroutineWorker.
  • Cách kiểm thử worker theo cách thủ công và xác minh chức năng của worker.