1. Trước khi bắt đầu
Lớp học lập trình này xoay quanh WorkManager, một thư viện đơn giản, linh hoạt và có khả năng tương thích ngược dành cho công việc có thể trì hoãn ở chế độ nền. WorkManager
là trình lập lịch biểu được đề xuất cho tác vụ trên Android đối với công việc có thể trì hoãn nhưng đảm bảo được thực thi.
Điều kiện tiên quyết
- Có kiến thức về StateFlow và ViewModel. Nếu bạn mới biết về các lớp này, hãy xem Lớp học lập trình về ViewModel và Trạng thái trong Compose (dành riêng cho ViewModel và Trạng thái) hoặc Lớp học lập trình về cách Đọc và cập nhật dữ liệu bằng Room (dành riêng cho Flow và StateFlow).
- Có kiến thức về kho lưu trữ và tính năng chèn phần phụ thuộc. Để ôn lại kiến thức, hãy xem phần Thêm kho lưu trữ và DI thủ công.
- Có thể triển khai coroutine trong ứng dụng.
Kiến thức bạn sẽ học được
- Cách thêm WorkManager vào dự án.
- Cách lên lịch cho tác vụ đơn giản.
- Cách định cấu hình tham số đầu vào và đầu ra cho worker (thành phần thực thi).
- Cách tạo chuỗi worker.
Bạn sẽ thực hiện
- Sửa đổi ứng dụng khởi đầu để dùng WorkManager.
- Triển khai yêu cầu công việc để làm mờ hình ảnh.
- Triển khai nhóm công việc nối tiếp bằng cách tạo chuỗi công việc.
- Truyền dữ liệu vào và ra khỏi công việc đã được lên lịch.
Bạn cần có
- Phiên bản ổn định mới nhất của Android Studio
- Kết nối Internet
2. Tổng quan về ứng dụng
Ngày nay, khả năng chụp ảnh của điện thoại thông minh gần như đã quá tốt. Còn đâu cái thời mà nhiếp ảnh gia có thể chụp ra một bức ảnh mờ đáng tin về điều gì đó bí ẩn.
Trong lớp học lập trình này, bạn sẽ làm việc trên Blur-O-Matic, một ứng dụng làm mờ ảnh rồi lưu kết quả vào tệp. Liệu đó là quái vật hồ Loch Ness hay tàu ngầm đồ chơi? Nhờ Blur-O-Matic, sẽ chẳng ai biết được đâu!
Màn hình có các nút chọn để bạn chọn độ mờ của hình ảnh. Thao tác nhấp vào nút Start (Bắt đầu) sẽ làm mờ rồi lưu hình ảnh.
Hiện tại, Blur-O-Matic chưa áp dụng tính năng làm mờ hay lưu hình ảnh cuối cùng.
Lớp học lập trình này tập trung vào việc thêm WorkManager vào Blur-O-Matic, tạo ra worker giúp dọn dẹp tệp tạm thời được tạo bằng cách làm mờ hình ảnh rồi lưu bản sao cuối cùng của hình ảnh mà bạn xem được khi nhấp vào nút See File (Xem tệp). Bạn cũng tìm hiểu cách theo dõi trạng thái hoạt động ở chế độ nền và cập nhật giao diện người dùng của ứng dụng cho phù hợp.
3. Khám phá ứng dụng khởi đầu Blur-O-Matic
Lấy mã khởi đầu
Để bắt đầu, hãy tải mã khởi đầu xuống:
Ngoài ra, bạn có thể sao chép kho lưu trữ GitHub cho đoạn mã:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout starter
Bạn có thể duyệt xem mã của ứng dụng Blur-O-Matic trong kho lưu trữ GitHub này.
Chạy mã khởi đầu
Để làm quen với mã khởi đầu, hãy hoàn thành các bước sau:
- Mở dự án bằng mã khởi đầu trong Android Studio.
- Chạy ứng dụng trên thiết bị Android hoặc trình mô phỏng.
Màn hình có các nút chọn để bạn chọn độ mờ của hình ảnh. Khi bạn nhấp vào nút Start (Bắt đầu), ứng dụng sẽ làm mờ rồi lưu hình ảnh.
Hiện tại, ứng dụng này chưa làm mờ khi bạn nhấp vào nút Start (Bắt đầu).
Hướng dẫn từng bước về mã khởi đầu
Trong nhiệm vụ này, bạn sẽ làm quen với cấu trúc của dự án. Dưới đây là các danh sách đưa ra hướng dẫn từng bước về các tệp và thư mục quan trọng trong dự án.
WorkerUtils
: Các phương thức thuận tiện mà sau này bạn sẽ sử dụng để cho thấyNotifications
và mã để lưu bitmap vào tệp.BlurViewModel
: Mô hình chế độ xem này lưu trữ trạng thái của ứng dụng và tương tác với kho lưu trữ.WorkManagerBluromaticRepository
: Lớp mà bạn bắt đầu công việc ở chế độ nền bằng WorkManager.Constants
: Một lớp tĩnh có một số hằng số bạn dùng trong lớp học lập trình này.BluromaticScreen
: Chứa các hàm có khả năng kết hợp cho giao diện người dùng và tương tác vớiBlurViewModel
. Các hàm có khả năng kết hợp cho thấy hình ảnh và bao gồm các nút chọn để chọn độ mờ mong muốn.
4. WorkManager là gì?
WorkManager là một phần của Android Jetpack kiêm một Thành phần kiến trúc dành cho công việc ở chế độ nền cần kết hợp khả năng thực thi theo cơ hội và khả năng thực thi được đảm bảo. Thực thi theo cơ hội tức là WorkManager thực hiện công việc ở chế độ nền ngay khi có thể. Thực thi được đảm bảo tức là WorkManager xử lý logic để bắt đầu công việc của bạn trong nhiều tình huống, ngay cả khi bạn rời khỏi ứng dụng.
WorkManager là một thư viện cực kỳ linh hoạt kèm theo nhiều lợi ích khác. Có thể kể đến một số lợi ích như sau:
- Hỗ trợ cả tác vụ định kỳ một lần và tác vụ một lần không đồng bộ.
- Hỗ trợ quy tắc ràng buộc, chẳng hạn như điều kiện mạng, không gian lưu trữ và trạng thái sạc.
- Tạo chuỗi yêu cầu công việc phức tạp, chẳng hạn như chạy công việc song song.
- Tạo dữ liệu đầu ra cho một yêu cầu công việc được dùng làm dữ liệu đầu vào cho yêu cầu tiếp theo.
- Xử lý khả năng tương thích cấp độ API dựa trên API cấp 14 (xem ghi chú).
- Làm việc với hoặc không có Dịch vụ Google Play.
- Làm theo các phương pháp hay nhất về tình trạng hệ thống.
- Hỗ trợ để dễ dàng hiển thị trạng thái của yêu cầu công việc trong giao diện người dùng của ứng dụng.
5. Trường hợp sử dụng WorkManager
Thư viện WorkManager là lựa chọn hiệu quả cho nhiều tác vụ bạn cần hoàn thành. Quá trình chạy các tác vụ này không phụ thuộc vào việc ứng dụng có tiếp tục chạy sau khi tác vụ được đưa vào hàng đợi hay không. Các tác vụ sẽ chạy ngay cả khi ứng dụng đã đóng hoặc người dùng quay lại màn hình chính.
Dưới đây là một số ví dụ về tác vụ sử dụng hiệu quả WorkManager:
- Định kỳ truy vấn các tin tức mới nhất.
- Áp dụng bộ lọc cho hình ảnh rồi lưu hình ảnh.
- Định kỳ đồng bộ hoá dữ liệu cục bộ với mạng.
WorkManager là một lựa chọn để chạy tác vụ ngoài luồng chính nhưng không phải là giải pháp chung để chạy mọi loại tác vụ ngoài luồng chính. Coroutine là một lựa chọn khác mà các lớp học lập trình trước đó đã thảo luận.
Để biết thêm thông tin chi tiết về thời điểm sử dụng WorkManager, hãy xem Hướng dẫn về công việc ở chế độ nền.
6. Thêm WorkManager vào ứng dụng
WorkManager
yêu cầu phần phụ thuộc gradle theo sau. Phần này đã có trong tệp bản dựng:
app/build.gradle.kts
dependencies {
// WorkManager dependency
implementation("androidx.work:work-runtime-ktx: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, đừ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.
7. Thông tin cơ bản về WorkManager
Có một số lớp WorkManager mà bạn cần biết:
Worker
/CoroutineWorker
: Worker là lớp thực hiện đồng bộ công việc trên luồng nền. Khi quan tâm đến công việc không đồng bộ, chúng ta có thể sử dụng CoroutineWorker, công cụ có khả năng tương tác với coroutine của Kotlin. Trong ứng dụng này, bạn mở rộng từ lớp CoroutineWorker rồi ghi đè phương thứcdoWork()
. Phương thức này là nơi bạn đặt mã cho công việc thực tế mà bạn muốn thực hiện ở chế độ nền.WorkRequest
: Lớp này thể hiện yêu cầu thực hiện một số công việc.WorkRequest
là nơi bạn xác định xem worker cần được chạy một lần hay định kỳ. Bạn cũng có thể đặt quy tắc giới hạn trênWorkRequest
để yêu cầu một số điều kiện cụ thể trước khi chạy công việc. Một ví dụ là thiết bị đang sạc trước khi bắt đầu công việc được yêu cầu. Bạn truyềnCoroutineWorker
vào quá trình tạoWorkRequest
.WorkManager
: Lớp này thực sự lên lịch choWorkRequest
và buộc phương thức này phải chạy. Lớp này lên lịch choWorkRequest
theo cách phân bổ tải cho các tài nguyên hệ thống trong khi vẫn tuân thủ các quy tắc ràng buộc mà bạn chỉ định.
Trong trường hợp của bạn, bạn xác định một lớp BlurWorker
mới, trong đó chứa mã để làm mờ hình ảnh. Khi bạn nhấp vào nút Start (Bắt đầu), WorkManager sẽ tạo rồi xếp một đối tượng WorkRequest
vào hàng đợi.
8. Tạo BlurWorker
Ở bước này, bạn chụp ảnh trong thư mục res/drawable
có tên là android_cupcake.png
rồi chạy một vài hàm cho ảnh đó ở chế độ nền. Các hàm này làm mờ hình ảnh.
- Nhấp chuột phải vào gói
com.example.bluromatic.workers
trong ngăn dự án Android rồi chọn New -> Kotlin Class/File (Mới -> Tệp/Lớp Kotlin). - Đặt tên cho lớp mới trong Kotlin là
BlurWorker
. Hãy mở rộng lớp này từCoroutineWorker
bằng các tham số hàm khởi tạo cần thiết.
workers/BlurWorker.kt
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
}
Lớp BlurWorker
mở rộng lớp CoroutineWorker
thay vì lớp Worker
chung hơn. Quá trình triển khai lớp CoroutineWorker
của doWork()
là một hàm tạm ngưng cho phép chạy mã không đồng bộ, trong khi Worker
không làm được việc này. Như đã nêu chi tiết trong hướng dẫn Tạo luồng trong WorkManager, "CoroutineWorker là phương thức triển khai được đề xuất cho người dùng Kotlin".
Tại thời điểm này, Android Studio sẽ vẽ một đường gợn sóng màu đỏ trong class BlurWorker
cho biết có lỗi.
Nếu bạn đặt con trỏ lên văn bản class BlurWorker
, IDE sẽ cho thấy một cửa sổ bật lên có thông tin bổ sung về lỗi đó.
Thông báo lỗi cho biết bạn đã không ghi đè phương thức doWork()
theo yêu cầu.
Trong phương thức doWork()
, hãy viết mã để làm mờ hình ảnh bánh cupcake mà bạn thấy.
Hãy làm theo các bước sau để khắc phục lỗi và triển khai phương thức doWork()
:
- Đặt con trỏ vào trong mã lớp bằng cách nhấp vào văn bản "BlurWorker".
- Trên trình đơn của Android Studio, hãy chọn Code > Override Methods (Mã > Phương thức ghi đè)
- Trong cửa sổ Override Members (Ghi đè thành viên) bật lên, hãy chọn
doWork()
- Nhấp vào OK.
- Ngay trước khi khai báo lớp, hãy tạo một biến có tên
TAG
rồi chỉ định giá trịBlurWorker
cho biến đó. Lưu ý rằng biến này không liên quan cụ thể đến phương thứcdoWork()
, nhưng bạn sẽ sử dụng biến này cho các lệnh gọi đếnLog()
.
workers/BlurWorker.kt
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
...
- Để xem rõ hơn thời điểm thực thi công việc, bạn cần sử dụng hàm
makeStatusNotification()
củaWorkerUtil
. Hàm này giúp bạn dễ dàng hiển thị biểu ngữ thông báo ở đầu màn hình.
Bên trong phương thức doWork()
, hãy dùng hàm makeStatusNotification()
để hiển thị thông báo trạng thái và thông báo cho người dùng rằng worker làm mờ đã bắt đầu và đang làm mờ hình ảnh.
workers/BlurWorker.kt
import com.example.bluromatic.R
...
override suspend fun doWork(): Result {
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
...
- Thêm một khối mã
return try...catch
, nơi thực hiện công việc làm mờ hình ảnh thực tế.
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
} catch (throwable: Throwable) {
}
...
- Trong khối
try
, hãy thêm lệnh gọi đếnResult.success()
. - Trong khối
catch
, hãy thêm lệnh gọi đếnResult.failure()
.
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
Result.success()
} catch (throwable: Throwable) {
Result.failure()
}
...
- Trong khối
try
, hãy tạo một biến mới có tên làpicture
, điền biến đó bằng bitmap được trả về từ phương thức gọiBitmapFactory.decodeResource
()
rồi truyền vào gói tài nguyên của ứng dụng và mã tài nguyên của hình ảnh bánh cupcake.
workers/BlurWorker.kt
...
return try {
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
Result.success()
...
- Làm mờ bitmap đó bằng cách gọi hàm
blurBitmap()
rồi truyền vào biếnpicture
cùng giá trị1
(một) cho tham sốblurLevel
. - Lưu kết quả trong một biến mới có tên là
output
.
workers/BlurWorker.kt
...
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
val output = blurBitmap(picture, 1)
Result.success()
...
- Tạo một biến
outputUri
mới rồi điền vào biến này bằng lệnh gọi hàmwriteBitmapToFile()
. - Trong lệnh gọi đến
writeBitmapToFile()
, hãy truyền ngữ cảnh ứng dụng và biếnoutput
dưới dạng đối số.
workers/BlurWorker.kt
...
val output = blurBitmap(picture, 1)
// Write bitmap to a temp file
val outputUri = writeBitmapToFile(applicationContext, output)
Result.success()
...
- Thêm mã để cho người dùng thấy thông báo chứa biến
outputUri
.
workers/BlurWorker.kt
...
val outputUri = writeBitmapToFile(applicationContext, output)
makeStatusNotification(
"Output is $outputUri",
applicationContext
)
Result.success()
...
- Trong khối
catch
, hãy ghi lại một thông báo lỗi để cho biết đã xảy ra lỗi khi cố làm mờ hình ảnh. Lệnh gọi đếnLog.e()
truyền biếnTAG
được xác định trước đó, một thông báo thích hợp cũng như trường hợp ngoại lệ được gửi.
workers/BlurWorker.kt
...
} catch (throwable: Throwable) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_applying_blur),
throwable
)
Result.failure()
}
...
Theo mặc định, CoroutineWorker,
sẽ chạy dưới dạng Dispatchers.Default
nhưng có thể thay đổi bằng cách gọi withContext()
rồi truyền vào thành phần điều phối mong muốn.
- Tạo một khối
withContext()
. - Bên trong lệnh gọi đến
withContext()
, hãy truyềnDispatchers.IO
để hàm lambda chạy trong một nhóm luồng đặc biệt nhằm chặn các hoạt động IO. - Di chuyển mã
return try...catch
đã viết trước đó vào khối này.
...
return withContext(Dispatchers.IO) {
return try {
// ...
} catch (throwable: Throwable) {
// ...
}
}
...
Android Studio cho thấy lỗi sau đây vì bạn không gọi được return
từ trong một hàm lambda.
Bạn có thể sửa lỗi này bằng cách thêm một nhãn như trong cửa sổ bật lên.
...
//return try {
return@withContext try {
...
Vì Worker này chạy rất nhanh, bạn nên thêm độ trễ trong mã để mô phỏng công việc chạy chậm hơn.
- Bên trong hàm lambda
withContext()
, hãy thêm một lệnh gọi vào hàm số hiệu dụngdelay()
rồi truyền vào hằng sốDELAY_TIME_MILLIS
. Lệnh gọi này chỉ dành cho lớp học lập trình này nhằm cung cấp độ trễ cho nội dung thông báo.
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay
...
return withContext(Dispatchers.IO) {
// This is an utility function added to emulate slower work.
delay(DELAY_TIME_MILLIS)
val picture = BitmapFactory.decodeResource(
...
9. Cập nhật WorkManagerBluromaticRepository
Kho lưu trữ xử lý mọi hành động tương tác với WorkManager. Cấu trúc này tuân theo nguyên tắc thiết kế phân tách các vấn đề và là mẫu kiến trúc Android được đề xuất.
- Trong tệp
data/WorkManagerBluromaticRepository.kt
, bên trong lớpWorkManagerBluromaticRepository
, hãy tạo một biến riêng tư có tên làworkManager
rồi lưu trữ một thực thểWorkManager
trong đó bằng cách gọiWorkManager.getInstance(context)
.
data/WorkManagerBluromaticRepository.kt
import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
// New code
private val workManager = WorkManager.getInstance(context)
...
Tạo và thêm WorkRequest vào hàng đợi trong WorkManager
Được rồi, giờ là lúc tạo WorkRequest
và yêu cầu WorkManager chạy phương thức này! Có hai loại WorkRequest
:
OneTimeWorkRequest
: MộtWorkRequest
chỉ thực thi một lần.PeriodicWorkRequest
: MộtWorkRequest
thực thi nhiều lần trong một chu kỳ.
Bạn chỉ muốn làm mờ hình ảnh một lần khi nhấp vào nút Start (Bắt đầu).
Công việc này diễn ra trong phương thức applyBlur()
mà bạn gọi khi nhấp vào nút Start (Bắt đầu).
Đã hoàn tất các bước sau đây bên trong phương thức applyBlur()
.
- Điền một biến mới có tên là
blurBuilder
bằng cách tạo mộtOneTimeWorkRequest
cho worker làm mờ rồi gọi hàm mở rộngOneTimeWorkRequestBuilder
qua WorkManager KTX.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
- Bắt đầu công việc bằng cách gọi phương thức
enqueue()
trên đối tượngworkManager
.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// Start the work
workManager.enqueue(blurBuilder.build())
}
- Chạy ứng dụng và xem thông báo khi bạn nhấp vào nút Start (Bắt đầu).
Hiện tại, hình ảnh được làm mờ như nhau, bất kể bạn chọn phương án nào. Ở các bước tiếp theo, thời gian làm mờ sẽ thay đổi tuỳ theo lựa chọn của bạn.
Để xác nhận hình ảnh đã được làm mờ thành công, bạn có thể mở Device Explorer (Trình khám phá thiết bị) trong Android Studio:
Sau đó, hãy chuyển đến data > data > com.example.bluromatic > files > blur_filter_outputs > <URI> rồi xác nhận rằng hình ảnh bánh cupcake đã thật sự được làm mờ:
10. Dữ liệu đầu vào và dữ liệu đầu ra
Việc làm mờ thành phần hình ảnh trong thư mục tài nguyên đã rất tốt, nhưng để Blur-O-Matic thực sự là ứng dụng chỉnh sửa hình ảnh mang tính cách mạng, bạn phải tạo điều kiện để người dùng làm mờ hình ảnh họ thấy trên màn hình rồi đưa ra thành quả.
Để làm điều này, chúng ta cung cấp URI của hình ảnh bánh cupcake được hiển thị làm dữ liệu đầu vào cho WorkRequest
, sau đó sử dụng kết quả của WorkRequest
để cho thấy hình ảnh được làm mờ cuối cùng.
Dữ liệu đầu vào và đầu ra được chuyển vào và ra khỏi một worker thông qua đối tượng Data
. Đối tượng Data
là vùng chứa nhẹ cho cặp khoá/giá trị. Chúng lưu trữ một lượng nhỏ dữ liệu có thể truyền vào và ra khỏi worker từ WorkRequest
.
Trong bước tiếp theo, bạn sẽ truyền URI đến BlurWorker
bằng cách tạo đối tượng dữ liệu đầu vào.
Tạo đối tượng dữ liệu đầu vào
- Trong tệp
data/WorkManagerBluromaticRepository.kt
, bên trong lớpWorkManagerBluromaticRepository
, hãy tạo một biến riêng tư mới có tên làimageUri
. - Điền sẵn URI hình ảnh vào biến bằng cách gọi phương thức ngữ cảnh
getImageUri()
.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
private var imageUri: Uri = context.getImageUri() // <- Add this
private val workManager = WorkManager.getInstance(context)
...
Mã ứng dụng chứa hàm trợ giúp createInputDataForWorkRequest()
để tạo đối tượng dữ liệu đầu vào.
data/WorkManagerBluromaticRepository.kt
// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
val builder = Data.Builder()
builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
return builder.build()
}
Trước tiên, hàm trợ giúp sẽ tạo một đối tượng Data.Builder
. Tiếp theo, hàm trợ giúp thiết lập imageUri
và blurLevel
vào đối tượng này dưới dạng cặp khoá/giá trị. Sau đó, một đối tượng Dữ liệu được tạo rồi trả về khi gọi return builder.build()
.
- Để thiết lập đối tượng dữ liệu đầu vào cho WorkRequest, hãy gọi phương thức
blurBuilder.setInputData()
. Bạn có thể tạo và truyền đối tượng dữ liệu trong một bước bằng cách gọi hàm trợ giúpcreateInputDataForWorkRequest()
làm đối số. Đối với lệnh gọi đến hàmcreateInputDataForWorkRequest()
, hãy truyền biếnblurLevel
và biếnimageUri
.
data/WorkManagerBluromaticRepository.kt
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// New code for input data object
blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))
workManager.enqueue(blurBuilder.build())
}
Truy cập đối tượng dữ liệu đầu vào
Bây giờ, hãy cập nhật phương thức doWork()
trong lớp BlurWorker
để lấy URI và mức độ mờ mà đối tượng dữ liệu đầu vào truyền vào. Nếu bạn không cung cấp giá trị cho blurLevel
thì giá trị này sẽ được đặt mặc định là 1
.
Bên trong phương thức doWork()
:
- Tạo một biến mới có tên là
resourceUri
rồi điền biến bằng cách gọiinputData.getString()
và truyền vào hằng sốKEY_IMAGE_URI
được dùng làm khoá khi tạo đối tượng dữ liệu đầu vào.
val resourceUri = inputData.getString(KEY_IMAGE_URI)
- Tạo biến mới có tên là
blurLevel
. Điền biến bằng cách gọiinputData.getInt()
và truyền vào hằng sốBLUR_LEVEL
dùng làm khoá khi tạo đối tượng dữ liệu đầu vào. Trong trường hợp bạn chưa tạo cặp khoá/giá trị này, hãy cung cấp giá trị mặc định là1
(một).
workers/BlurWorker.kt
import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {
// ADD THESE LINES
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)
// ... rest of doWork()
}
Với URI, bây giờ, hãy làm mờ hình ảnh bánh cupcake trên màn hình.
- Kiểm tra để đảm bảo biến
resourceUri
được điền sẵn. Nếu mã này không được điền thì mã của bạn sẽ gửi một ngoại lệ. Mã theo sau sử dụng câu lệnhrequire()
sẽ gửiIllegalArgumentException
nếu đối số đầu tiên có giá trị false.
workers/BlurWorker.kt
return@withContext try {
// NEW code
require(!resourceUri.isNullOrBlank()) {
val errorMessage =
applicationContext.resources.getString(R.string.invalid_input_uri)
Log.e(TAG, errorMessage)
errorMessage
}
Do nguồn hình ảnh được truyền vào dưới dạng URI, nên chúng ta cần đối tượng ContentResolver để đọc nội dung do URI trỏ đến.
- Thêm đối tượng
contentResolver
vào giá trịapplicationContext
.
workers/BlurWorker.kt
...
require(!resourceUri.isNullOrBlank()) {
// ...
}
val resolver = applicationContext.contentResolver
...
- Vì nguồn hình ảnh đã được truyền vào URI, hãy sử dụng
BitmapFactory.decodeStream()
thay vìBitmapFactory.decodeResource()
để tạo đối tượng Bitmap.
workers/BlurWorker.kt
import android.net.Uri
...
// val picture = BitmapFactory.decodeResource(
// applicationContext.resources,
// R.drawable.android_cupcake
// )
val resolver = applicationContext.contentResolver
val picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
- Truyền biến
blurLevel
trong lệnh gọi đến hàmblurBitmap()
.
workers/BlurWorker.kt
//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)
Tạo đối tượng dữ liệu đầu ra
Lúc này, bạn đã tìm hiểu xong Worker này và có thể trả về URI đầu ra dưới dạng đối tượng dữ liệu đầu ra trong Result.success()
. Việc cung cấp URI đầu ra dưới dạng đối tượng dữ liệu đầu ra giúp các worker khác có thể dễ dàng truy cập các hoạt động khác. Phương pháp này hữu ích trong phần tiếp theo khi bạn tạo chuỗi woker.
Để làm việc này, vui lòng hoàn thành các bước sau:
- Trước mã
Result.success()
, hãy tạo một biến mới có tên làoutputData
. - Điền biến này bằng cách gọi hàm
workDataOf()
rồi sử dụng hằng sốKEY_IMAGE_URI
cho khoá và biếnoutputUri
làm giá trị. HàmworkDataOf()
tạo đối tượng Dữ liệu qua cặp khoá và giá trị đã truyền.
workers/BlurWorker.kt
import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
- Cập nhật mã
Result.success()
để lấy đối tượng Dữ liệu mới này làm đối số.
workers/BlurWorker.kt
//Result.success()
Result.success(outputData)
- Xoá mã cho thấy thông báo vì không còn cần thiết nữa khi đối tượng Dữ liệu đầu ra đang sử dụng URI.
workers/BlurWorker.kt
// REMOVE the following notification code
//makeStatusNotification(
// "Output is $outputUri",
// applicationContext
//)
Chạy ứng dụng
Tại thời điểm này, khi chạy ứng dụng, bạn có thể kỳ vọng ứng dụng sẽ tiến hành biên dịch. Bạn có thể thấy hình ảnh được làm mờ thông qua Trình khám phá thiết bị nhưng chưa thấy trên màn hình.
Lưu ý rằng có thể bạn phải Đồng bộ hoá để xem được hình ảnh:
Tuyệt vời! Bạn đã làm mờ xong hình ảnh đầu vào bằng WorkManager
!
11. Kết hợp Work thành chuỗi
Hiện tại, bạn đang làm một việc duy nhất là làm mờ hình ảnh. Tác vụ này là bước đầu tiên tuyệt vời, nhưng ứng dụng vẫn thiếu một số chức năng cốt lõi:
- Ứng dụng không xoá các tệp tạm thời.
- Ứng dụng không thực sự lưu hình ảnh vào một tệp vĩnh viễn.
- Ứng dụng luôn làm mờ hình ảnh như nhau.
Bạn có thể sử dụng một chuỗi công việc trong WorkManager để thêm chức năng này. WorkManager hỗ trợ bạn tạo các WorkerRequest
riêng biệt chạy theo thứ tự hoặc song song.
Trong phần này, bạn sẽ tạo một chuỗi công việc có dạng như sau:
Các hộp này đại diện cho các WorkRequest
.
Tính năng tạo chuỗi cũng có khả năng chấp nhận dữ liệu đầu vào và đầu ra. Dữ liệu đầu ra của một WorkRequest
sẽ trở thành dữ liệu đầu vào của WorkRequest
tiếp theo trong chuỗi.
Bạn đã có CoroutineWorker
để làm mờ hình ảnh, nhưng cũng cần có CoroutineWorker
để dọn dẹp các tệp tạm thời và CoroutineWorker
để lưu hình ảnh vĩnh viễn.
Tạo CleanupWorker
CleanupWorker
sẽ xoá các tệp tạm thời, nếu có.
- Nhấp chuột phải vào gói
com.example.bluromatic.workers
trong ngăn dự án Android rồi chọn New -> Kotlin Class/File (Mới -> Tệp/Lớp Kotlin). - Đặt tên cho lớp mới trong Kotlin là
CleanupWorker
. - Sao chép mã cho CleanupWorker.kt như trong mã ví dụ sau đây.
Vì thao tác đối với tệp nằm ngoài phạm vi của lớp học lập trình này, nên bạn có thể sao chép mã sau đây cho CleanupWorker
.
workers/CleanupWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
/**
* Cleans up temporary files generated during blurring process
*/
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend 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 on emulated devices
*/
makeStatusNotification(
applicationContext.resources.getString(R.string.cleaning_up_files),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
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()
}
}
}
}
Tạo SaveImageToFileWorker
Lớp SaveImageToFileWorker
sẽ lưu tệp tạm thời vào một tệp vĩnh viễn.
SaveImageToFileWorker
nhận dữ liệu đầu vào và đầu ra. Dữ liệu đầu vào là String
của URI hình ảnh được làm mờ tạm thời, được lưu trữ bằng khoá KEY_IMAGE_URI
. Dữ liệu đầu ra là String
của URI hình ảnh được làm mờ đã lưu, được lưu trữ bằng khoá KEY_IMAGE_URI
.
- Nhấp chuột phải vào gói
com.example.bluromatic.workers
trong ngăn dự án Android rồi chọn New -> Kotlin Class/File) (Mới -> Tệp/Lớp Kotlin). - Đặt tên cho lớp mới trong Kotlin là
SaveImageToFileWorker
. - Sao chép mã SaveImageToFileWorker.kt như trong mã ví dụ sau đây.
Vì thao tác đối với tệp nằm ngoài phạm vi của lớp học lập trình này, nên bạn có thể sao chép mã sau đây cho SaveImageToFileWorker
. Trong mã được cung cấp, hãy lưu ý cách truy xuất và lưu trữ các giá trị resourceUri
và output
bằng khoá KEY_IMAGE_URI
. Quá trình này rất giống với mã bạn đã viết trước đây cho các đối tượng dữ liệu đầu vào và đầu ra.
workers/SaveImageToFileWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
/**
* Saves the image to a permanent file
*/
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
private val title = "Blurred Image"
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z",
Locale.getDefault()
)
override suspend 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 on emulated devices
makeStatusNotification(
applicationContext.resources.getString(R.string.saving_image),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
val resolver = applicationContext.contentResolver
return@withContext try {
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
val imageUrl = MediaStore.Images.Media.insertImage(
resolver, bitmap, title, dateFormatter.format(Date())
)
if (!imageUrl.isNullOrEmpty()) {
val output = workDataOf(KEY_IMAGE_URI to imageUrl)
Result.success(output)
} else {
Log.e(
TAG,
applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
)
Result.failure()
}
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_saving_image),
exception
)
Result.failure()
}
}
}
}
Tạo chuỗi công việc
Hiện tại, mã chỉ tạo và chạy một WorkRequest
duy nhất.
Ở bước này, bạn sửa đổi mã để tạo và thực thi một chuỗi WorkRequest thay vì chỉ một yêu cầu hình ảnh mờ.
Trong chuỗi WorkRequest, yêu cầu công việc đầu tiên của bạn là dọn dẹp các tệp tạm thời.
- Thay vì gọi
OneTimeWorkRequestBuilder
, hãy gọiworkManager.beginWith()
.
Việc gọi phương thức beginWith()
sẽ trả về một đối tượng WorkContinuation
và tạo điểm bắt đầu cho một chuỗi WorkRequest
có yêu cầu công việc đầu tiên trong chuỗi.
data/WorkManagerBluromaticRepository.kt
import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
override fun applyBlur(blurLevel: Int) {
// Add WorkRequest to Cleanup temporary images
var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))
// Add WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...
Bạn có thể thêm vào chuỗi yêu cầu công việc này bằng cách gọi phương thức then()
và truyền một đối tượng WorkRequest
.
- Xoá lệnh gọi đến
workManager.enqueue(blurBuilder.build())
vì phương thức này chỉ đưa một yêu cầu công việc vào hàng đợi. - Thêm yêu cầu công việc tiếp theo vào chuỗi bằng cách gọi phương thức
.then()
.
data/WorkManagerBluromaticRepository.kt
...
//workManager.enqueue(blurBuilder.build())
// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
- Tạo một yêu cầu công việc để lưu hình ảnh rồi thêm hình ảnh đó vào chuỗi.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.SaveImageToFileWorker
...
continuation = continuation.then(blurBuilder.build())
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.build()
continuation = continuation.then(save)
...
- Để bắt đầu công việc, hãy gọi phương thức
enqueue()
trên đối tượng tiếp tục.
data/WorkManagerBluromaticRepository.kt
...
continuation = continuation.then(save)
// Start the work
continuation.enqueue()
...
Mã này tạo và chạy chuỗi WorkRequest sau đây: một CleanupWorker
WorkRequest
theo sau là một BlurWorker
WorkRequest
rồi đến một SaveImageToFileWorker
WorkRequest
.
- Chạy ứng dụng.
Giờ đây, bạn có thể nhấp vào Start (Bắt đầu) và xem thông báo khi các worker khác nhau thực thi. Bạn vẫn có thể thấy hình ảnh được làm mờ trong Trình khám phá thiết bị. Trong phần sắp tới, bạn sẽ thêm một nút bổ sung để người dùng có thể thấy hình ảnh được làm mờ trên thiết bị.
Trong những ảnh chụp màn hình sau đây, hãy chú ý rằng nội dung thông báo sẽ cho thấy worker nào hiện đang chạy.
Lưu ý rằng thư mục đầu ra chứa nhiều hình ảnh được làm mờ: hình ảnh đang ở giai đoạn làm mờ trung gian và hình ảnh hoàn thiện cho thấy hình ảnh theo độ mờ bạn đã chọn.
Tuyệt vời! Giờ đây, bạn đã có thể dọn dẹp các tệp tạm thời, làm mờ hình ảnh và lưu hình ảnh!
12. 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 intermediate
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.
13. Kết luận
Xin chúc mừng! Bạn đã tìm hiểu xong về ứng dụng Blur-O-Matic và sắp tìm hiểu về việc:
- Thêm WorkManager vào dự án
- Lên lịch cho
OneTimeWorkRequest
- Tham số đầu vào và đầu ra
- Tạo chuỗi công việc
WorkRequest
WorkManager hỗ trợ nhiều công việc hơn chúng ta có thể thảo luận trong lớp học lập trình này, bao gồm cả công việc lặp lại, thư viện hỗ trợ kiểm thử, yêu cầu công việc song song và công cụ hợp nhất dữ liệu đầu vào.
Để tìm hiểu thêm, hãy chuyển đến tài liệu về Lên lịch cho tác vụ bằng WorkManager.