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
- Đảm bảo công việc riêng biệt.
- Cách huỷ công việc.
- Cách xác định các quy tắc ràng buộc đối với công việc.
- Cách viết kiểm thử tự động để xác minh chức năng của Worker.
- Thông tin cơ bản về việc kiểm tra worker trong hàng đợi bằng Công cụ kiểm tra tác vụ trong nền.
Bạn cần có
- Phiên bản ổn định mới nhất của Android Studio
- Đã hoàn thành lớp học lập trình Làm việc ở chế độ nền bằng WorkManager
- Trình mô phỏng hoặc thiết bị Android
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:
- Xoá lệnh gọi đến hàm
beginWith()
và thêm lệnh gọi tới hàmbeginUniqueWork()
. - Đố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
. - Đối với tham số thứ hai (tham số
existingWorkPolicy
), hãy truyền vàoExistingWorkPolicy.REPLACE
. - Đối với tham số thứ ba, hãy tạo một
OneTimeWorkRequest
mới choCleanupWorker
.
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) | 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 | 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ẻ | 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ó:
- Liệu trạng thái công việc là
BLOCKED
,CANCELLED
,ENQUEUED
,FAILED
,RUNNING
haySUCCEEDED
. - Liệu
WorkRequest
có hoàn tất và công việc có dữ liệu đầu ra của không.
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
, BlurWorker
và SaveImageToFileWorker
). 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()
.
- 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ứcaddTag()
và truyền vào hằng sốTAG_OUTPUT
dạngString
.
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 đó:
- Trong tệp
data/WorkManagerBluromaticRepository.kt
, hãy gọi phương thứcworkManager.getWorkInfosByTagLiveData()
để điền biếnoutputWorkInfo
. - 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.
- 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()
...
- 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ị. - Đố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
}
...
- 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> =
...
- Bạn cũng phải xóa
?
khỏi giao diệnBluromaticRepository
.
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
:
- Điền sẵn biến
blurUiState
bằng FlowoutputWorkInfo
qua kho lưu trữ.
ui/BlurViewModel.kt
// ...
// REMOVE
// val blurUiState: StateFlow<BlurUiState> = MutableStateFlow(BlurUiState.Default)
// ADD
val blurUiState: StateFlow<BlurUiState> = bluromaticRepository.outputWorkInfo
// ...
- 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
}
}
// ...
- 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ố:
- Đố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. - Đố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ẻ. - Đố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:
- Xoá mã
Button(onStartClick)
bên trong Thành phần kết hợpRow
rồi thay thế mã đó bằng một khốiwhen
cóblurUiState
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
.
- 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).
- Đối với tham số
onClick
ở trạng tháiBlurUiState.Default
, hãy truyền biếnonStartClick
đang được truyền đến thành phần kết hợp. - Đối với tham số
stringResourceId
, hãy truyền mã tài nguyên chuỗi củaR.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.
- Đối với tham số
onClick
của nút ở trạng tháiBlurUiState.Loading
, hãy truyền biếnonCancelClick
đang được truyền đến thành phần kết hợp. - Đối với tham số
stringResourceId
của nút, hãy truyền mã tài nguyên chuỗi củaR.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).
- Đối với tham số
onClick
ở trạng tháiBlurUiState.Complete
, hãy truyền biếnonStartClick
. - Đối với tham số
stringResourceId
, hãy truyền mã tài nguyên chuỗi củaR.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
- Chạy ứng dụng rồi nhấp vào Start (Bắt đầu).
- 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.
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.
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
.
- Mở tệp
ui/BluromaticScreen.kt
rồi chuyển đến thành phần kết hợpBlurActions
. - Để 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ốiBlurUiState.Complete
. - Thêm một thành phần kết hợp
FilledTonalButton
mới. - Đối với tham số
onClick
, hãy truyềnonSeeFileClick(blurUiState.outputUri)
. - Thêm một thành phần kết hợp
Text
cho tham số nội dung củaButton
. - Đối với tham số
text
củaText
, hãy sử dụng mã tài nguyên chuỗiR.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
.
- Trong tệp
ui/BlurViewModel.kt
, bên trong phương thức biến đổimap()
, hãy tạo một biếnoutputImageUri
mới. - Đ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 {
// ...
- 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()
.
- Cập nhật nhánh
isFinished
để kiểm tra xem biến đó đã được điền hay chưa, sau đó truyền biếnoutputImageUri
vào đối tượng dữ liệuBlurUiState.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.
- Mở tệp
ui/BluromaticScreen.kt
. - Trong hàm
BluromaticScreenContent()
, trong lệnh gọi đến hàm có khả năng kết hợpBlurActions()
, 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()
)
// ...
- Bên trong phần nội dung của hàm lambda, hãy gọi hàm trợ giúp
showBlurredImage()
. - Đối với tham số đầu tiên, hãy truyền biến
context
. - Đố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:
6. Huỷ công việc
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
- Mở tệp
data/WorkManagerBluromaticRepository.kt
. - Trong hàm
cancelWork()
, hãy gọi hàmworkManager.cancelUniqueWork()
. - 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.
- Mở tệp
ui/BlurViewModel.kt
. - Tạo một hàm mới có tên là
cancelWork()
để huỷ công việc. - Bên trong hàm, trên đối tượng
bluromaticRepository
, hãy gọi phương thứccancelWork()
.
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)
- Mở tệp
ui/BluromaticScreen.kt
. - 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.
- 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ỷ!
Sau khi bạn huỷ công việc, chỉ có nút Start (Bắt đầu) xuất hiện vì WorkInfo.State
là CANCELLED
. 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.
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()
và 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:
- Chuyển đến phương thức
applyBlur()
. - 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ượngConstraints
cho quy tắc ràng buộc đang được tạo. - 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()
// ...
- 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)
// ...
- 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()
// ...
- Để 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
- 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).
- 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.
- 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.
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ử
- Tạo một thư mục cho các chương trình kiểm thử giao diện người dùng trong thư mục app > src.
- 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.
- Trong
WorkerInstrumentationTest.kt
, hãy tạo một biếnlateinit
để lưu giữ một thực thể củaContext
. - Tạo một phương thức
setUp()
được chú thích bằng@Before
. - Trong phương thức
setUp()
, hãy khởi tạo biến ngữ cảnhlateinit
có ngữ cảnh ứng dụng quaApplicationProvider
. - Tạo một hàm kiểm thử có tên
cleanupWorker_doWork_resultSuccess()
. - Trong kiểm thử
cleanupWorker_doWork_resultSuccess()
, hãy tạo một thực thể củaCleanupWorker
.
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
, BlurWorker
và SaveImageToFileWorker
, hãy sử dụng TestListenableWorkerBuilder
để kiểm thử vì nó xử lý độ phức tạp của luồng của coroutine.
CoroutineWorker
chạy không đồng bộ vì sử dụng coroutine. Để thực thi worker này song song, hãy sử dụngrunBlocking
. 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ụngrunBlocking
để hướng dẫn nó trực tiếp đếndoWork()
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 {
}
}
}
- Trong phần nội dung lambda của
runBlocking
, hãy gọidoWork()
trên thực thể củaCleanupWorker
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.
- 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.
- 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 đó, khi bạn xây dựng một thực thể của BlurWorker
, hệ thống sẽ yêu cầu một số dữ liệu đầu vào bao gồm cả hình ảnh như vậy.
- 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"
- Tạo một
BlurWorker
bên trong hàmblurWorker_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ứcsetInputData()
.
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
.
- Tạo một khối
runBlocking
. - Gọi
doWork()
bên trong khốirunBlocking
.
Không giống như CleanupWorker
, BlurWorker
có một số dữ liệu đầu ra rất phù hợp để kiểm thử!
- Để 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)
}
}
- 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
.
- 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-"
- 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
:
- Tạo worker, truyền dữ liệu đầu vào.
- Tạo một khối
runBlocking
. - Gọi
doWork()
trên worker. - Kiểm tra xem kết quả có thành công hay không.
- 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.
- Chạy ứng dụng Blur-O-Matic trên một thiết bị hoặc trình mô phỏng.
- Chuyển đến phần View > Tool Windows > App Inspection (Xem > Cửa sổ công cụ > Kiểm tra ứng dụng).
- Chọn thẻ Background Task Inspector (Công cụ kiểm tra tác vụ trong nền).
- 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 đó.
- 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.
- 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 như sau trong trình đơn thả xuống của Worker.
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 BlurWorker
và CleanupWorker
đã 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.
- 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) trong thanh công cụ.
Kiểm tra thông tin chi tiết về tác vụ
- Nhấp vào một worker trong bảng Worker.
Thao tác này sẽ mở cửa sổ Task Details (Thông tin chi tiết về tác vụ).
- Xem xét thông tin xuất hiện trong Task Details (Thông tin chi tiết về tác vụ).
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).
- Nhấp vào biểu tượng Show Graph View (Hiện chế độ xem biểu đồ) :
Khung hiển thị 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.
- Nhấp vào biểu tượng Show List View (Hiện chế độ xem danh sách) để 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.