Android WorkManager 2026: Hướng Dẫn Toàn Diện Background Tasks, Constraints và Câu Hỏi Phỏng Vấn

Tìm hiểu Android WorkManager để chạy background tasks với constraints, chaining và CoroutineWorker. Bao gồm các câu hỏi phỏng vấn Android developer thường gặp.

Android WorkManager 2026: Hướng Dẫn Toàn Diện Background Tasks, Constraints và Câu Hỏi Phỏng Vấn

Android WorkManager xử lý các công việc background có thể trì hoãn nhưng được đảm bảo thực thi, ngay cả sau khi ứng dụng khởi động lại hoặc thiết bị reboot. Là một phần của Android Jetpack, WorkManager 2.11.2 (phiên bản ổn định, tháng 3 năm 2026) cung cấp API thống nhất hoạt động trên API 23+ bằng cách chọn implementation tốt nhất (JobScheduler, AlarmManager, hoặc Firebase JobDispatcher) dựa trên API level của thiết bị.

Khi nào nên sử dụng WorkManager

Sử dụng WorkManager cho các tác vụ cần đảm bảo thực thi: tải log lên server, đồng bộ dữ liệu, xử lý hình ảnh. Đối với công việc cần thực hiện ngay và không cần tồn tại khi process chết, hãy sử dụng Kotlin Coroutines.

Thiết Lập WorkManager 2.11 Trong Dự Án Kotlin

Trước khi viết bất kỳ worker nào, dependency cần được khai báo. WorkManager 2.11+ yêu cầu minSdk 23 và compileSdk 33.

build.gradle.kts (app module)gradle
dependencies {
    val workVersion = "2.11.2"
    implementation("androidx.work:work-runtime-ktx:$workVersion")
    // Optional: for testing
    androidTestImplementation("androidx.work:work-testing:$workVersion")
}

Artifact work-runtime-ktx bao gồm Kotlin extensions và hỗ trợ Coroutine thông qua CoroutineWorker. Không cần cấu hình bổ sung cho việc sử dụng cơ bản: WorkManager tự khởi tạo thông qua ContentProvider.

Tạo Worker Đơn Giản Với doWork()

Lớp Worker override doWork() và trả về Result. Hệ thống chạy method này trên background thread.

SyncWorker.ktkotlin
class SyncWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    override fun doWork(): Result {
        // Read input data
        val userId = inputData.getString("user_id") ?: return Result.failure()
        
        return try {
            // Perform sync operation
            val syncService = SyncService.getInstance(applicationContext)
            syncService.syncUserData(userId)
            Result.success()
        } catch (e: IOException) {
            // Retry on network errors
            Result.retry()
        } catch (e: Exception) {
            // Permanent failure
            Result.failure()
        }
    }
}

Có ba kết quả có thể xảy ra: Result.success() đánh dấu hoàn thành, Result.failure() dừng việc retry, và Result.retry() lên lịch lại theo chính sách backoff.

Sử Dụng CoroutineWorker Cho Suspend Functions

Khi công việc liên quan đến suspend functions (gọi Retrofit, truy vấn Room), CoroutineWorker loại bỏ callback lồng nhau.

UploadWorker.ktkotlin
class UploadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val imageUri = inputData.getString("image_uri")
            ?: return Result.failure()
        
        // Progress reporting (visible in WorkInfo observers)
        setProgress(workDataOf("status" to "compressing"))
        
        val compressed = ImageCompressor.compress(imageUri)
        
        setProgress(workDataOf("status" to "uploading"))
        
        return try {
            val response = ApiClient.imageService.upload(compressed)
            val outputData = workDataOf("url" to response.imageUrl)
            Result.success(outputData)
        } catch (e: HttpException) {
            if (e.code() in 500..599) Result.retry()
            else Result.failure()
        }
    }
}

CoroutineWorker.doWork() chạy trên Dispatchers.Default. Để chuyển dispatcher, sử dụng withContext() bên trong hàm.

Định Nghĩa Constraints Cho Thực Thi Có Điều Kiện

Constraints ngăn công việc chạy cho đến khi các điều kiện được đáp ứng. Điều này tiết kiệm pin và tránh các lần thử thất bại.

ScheduleUpload.ktkotlin
fun scheduleUpload(context: Context, imageUri: String) {
    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.UNMETERED) // WiFi only
        .setRequiresBatteryNotLow(true)
        .setRequiresStorageNotLow(true)
        .build()

    val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
        .setConstraints(constraints)
        .setInputData(workDataOf("image_uri" to imageUri))
        .setBackoffCriteria(
            BackoffPolicy.EXPONENTIAL,
            Duration.ofMinutes(1)
        )
        .addTag("upload")
        .build()

    WorkManager.getInstance(context)
        .enqueueUniqueWork(
            "upload_$imageUri",
            ExistingWorkPolicy.KEEP,
            uploadRequest
        )
}

Các constraints có sẵn bao gồm loại mạng (CONNECTED, UNMETERED, METERED, NOT_ROAMING), mức pin, trạng thái sạc, dung lượng lưu trữ, và trạng thái idle của thiết bị. WorkManager 2.10+ cũng chấp nhận NetworkRequest thô để kiểm soát mạng chi tiết hơn.

Thông tin phỏng vấn

Một câu hỏi phỏng vấn phổ biến là khi nào sử dụng KEEP so với REPLACE trong enqueueUniqueWork. KEEP bỏ qua yêu cầu mới nếu công việc cùng tên đã tồn tại, REPLACE hủy công việc hiện có và bắt đầu lại. Sử dụng KEEP cho upload khi bản sao lãng phí băng thông, REPLACE cho đồng bộ khi chỉ dữ liệu mới nhất quan trọng.

Chuỗi Work Requests Với then() và combine()

Các workflow phức tạp yêu cầu thực thi tuần tự và song song. WorkManager chuỗi work requests sử dụng beginWith()then().

WorkChain.ktkotlin
fun processAndUploadImages(context: Context, imageUris: List<String>) {
    val workManager = WorkManager.getInstance(context)

    // Parallel compression workers
    val compressRequests = imageUris.map { uri ->
        OneTimeWorkRequestBuilder<CompressWorker>()
            .setInputData(workDataOf("uri" to uri))
            .build()
    }

    // Single upload worker runs after all compressions complete
    val uploadRequest = OneTimeWorkRequestBuilder<BatchUploadWorker>()
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        )
        .build()

    // Cleanup runs after upload, regardless of success
    val cleanupRequest = OneTimeWorkRequestBuilder<CleanupWorker>()
        .build()

    workManager
        .beginWith(compressRequests) // Parallel
        .then(uploadRequest)          // Sequential
        .then(cleanupRequest)          // Sequential
        .enqueue()
}

Output từ parallel workers được hợp nhất vào ArrayCreatingInputMerger theo mặc định. Worker tiếp theo nhận tất cả các cặp key-value, với array được tạo cho các key trùng lặp.

Sẵn sàng chinh phục phỏng vấn Android?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Lên Lịch Periodic Work Với PeriodicWorkRequest

PeriodicWorkRequest thực thi lặp lại với khoảng cách tối thiểu 15 phút (Android bắt buộc giới hạn này).

PeriodicSync.ktkotlin
fun scheduleDailySync(context: Context) {
    val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
        repeatInterval = 6, 
        repeatIntervalTimeUnit = TimeUnit.HOURS,
        flexTimeWindow = 30,
        flexTimeUnit = TimeUnit.MINUTES
    )
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        )
        .addTag("periodic_sync")
        .build()

    WorkManager.getInstance(context)
        .enqueueUniquePeriodicWork(
            "daily_sync",
            ExistingPeriodicWorkPolicy.UPDATE,
            syncRequest
        )
}

Flex window cho phép WorkManager gộp công việc với các job khác, cải thiện hiệu quả pin. Khoảng cách 6 giờ với flex 30 phút có nghĩa là thực thi xảy ra trong khoảng 5:30 đến 6:00 sau lần chạy trước.

Theo Dõi Trạng Thái Công Việc Với LiveData và Flow

WorkManager expose trạng thái công việc thông qua WorkInfo. Theo dõi bằng LiveData hoặc Kotlin Flow.

WorkObserver.ktkotlin
class UploadViewModel(application: Application) : AndroidViewModel(application) {

    private val workManager = WorkManager.getInstance(application)

    // Flow-based observation
    fun observeUpload(workId: UUID): Flow<WorkInfo?> {
        return workManager.getWorkInfoByIdFlow(workId)
    }

    // Check if any upload is running
    val activeUploads: Flow<List<WorkInfo>> = 
        workManager.getWorkInfosByTagFlow("upload")
            .map { workInfos ->
                workInfos.filter { it.state == WorkInfo.State.RUNNING }
            }
}

// In Compose UI
@Composable
fun UploadProgress(workId: UUID, viewModel: UploadViewModel) {
    val workInfo by viewModel.observeUpload(workId)
        .collectAsState(initial = null)

    when (workInfo?.state) {
        WorkInfo.State.RUNNING -> {
            val status = workInfo?.progress?.getString("status") ?: "working"
            CircularProgressIndicator()
            Text(status)
        }
        WorkInfo.State.SUCCEEDED -> {
            val url = workInfo?.outputData?.getString("url")
            Text("Uploaded: $url")
        }
        WorkInfo.State.FAILED -> Text("Upload failed")
        else -> {}
    }
}

Các trạng thái công việc tuân theo lifecycle: ENQUEUED, RUNNING, SUCCEEDED/FAILED/CANCELLED, BLOCKED (đang chờ dependencies).

API Work Metrics của WorkManager 2.12

WorkManager 2.12.0-rc01 (tháng 8 năm 2026) giới thiệu WorkMetricsInfo để theo dõi lịch sử thực thi.

MetricsExample.ktkotlin
class MetricsRepository(private val context: Context) {
    
    private val workManager = WorkManager.getInstance(context)

    suspend fun getWorkerMetrics(workerName: String): List<WorkMetricsInfo> {
        val query = WorkMetricsQuery.Builder()
            .setWorkerClassName(workerName)
            .setLimit(100)
            .build()
        
        return workManager.getWorkMetrics(query)
    }

    fun analyzeFailures(metrics: List<WorkMetricsInfo>): Map<Int, Int> {
        // Count stop reasons
        return metrics
            .flatMap { it.stopReasonCounts.entries }
            .groupBy { it.key }
            .mapValues { entry -> entry.value.sumOf { it.value } }
    }
}

Metrics bao gồm thời gian thực thi, số lần retry, lý do dừng, và timestamps. API này cho phép debug các lỗi không liên tục trong production mà không cần hạ tầng logging tùy chỉnh.

Lưu giữ Metrics

Dữ liệu WorkMetricsInfo được xóa sau 7 ngày theo mặc định. Cấu hình thời gian lưu giữ với Configuration.Builder().setWorkMetricsRetentionDuration().

Kiểm Thử Workers Với WorkManagerTestInitHelper

Unit testing workers yêu cầu artifact work-testing. TestListenableWorkerBuilder tạo workers mà không enqueue chúng.

SyncWorkerTest.ktkotlin
@RunWith(AndroidJUnit4::class)
class SyncWorkerTest {

    private lateinit var context: Context

    @Before
    fun setup() {
        context = ApplicationProvider.getApplicationContext()
        val config = Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .setExecutor(SynchronousExecutor())
            .build()
        WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
    }

    @Test
    fun syncWorker_withValidUserId_succeeds() = runTest {
        val inputData = workDataOf("user_id" to "123")
        
        val worker = TestListenableWorkerBuilder<SyncWorker>(context)
            .setInputData(inputData)
            .build()

        val result = worker.doWork()
        
        assertThat(result).isEqualTo(ListenableWorker.Result.success())
    }

    @Test
    fun syncWorker_withoutUserId_fails() = runTest {
        val worker = TestListenableWorkerBuilder<SyncWorker>(context)
            .build()

        val result = worker.doWork()
        
        assertThat(result).isEqualTo(ListenableWorker.Result.failure())
    }
}

Đối với integration tests xác minh constraints và chaining, sử dụng TestDriver để mô phỏng việc đáp ứng constraint và thời gian trôi qua.

Các Câu Hỏi Phỏng Vấn Phổ Biến Về WorkManager

Các cuộc phỏng vấn kỹ thuật thường kiểm tra sự hiểu biết về các đảm bảo và đánh đổi của WorkManager. Những câu hỏi này xuất hiện trong các cuộc phỏng vấn Android cấp trung và cấp cao.

H: Điều gì xảy ra với WorkRequest khi ứng dụng bị kill?

WorkManager lưu trữ work requests trong database Room. Khi ứng dụng khởi động lại hoặc hệ thống báo hiệu rằng constraint được đáp ứng, công việc đang chờ được tiếp tục. Sự đảm bảo này phân biệt WorkManager với công việc CoroutineScope chết cùng với process.

H: WorkManager khác với AlarmManager như thế nào?

AlarmManager lên lịch alarm với thời gian chính xác và chạy code vào thời điểm đồng hồ cụ thể. WorkManager lên lịch công việc có thể trì hoãn chạy khi constraints được đáp ứng, không có đảm bảo thời gian chính xác. Sử dụng AlarmManager cho alarm hướng đến người dùng, WorkManager cho xử lý dữ liệu background.

H: WorkManager có thể chạy công việc ngay lập tức không?

Có, với setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST). Expedited work requests sử dụng slot foreground service trên API 31+ hoặc JobScheduler.setImportantWhileForeground() trên các API cũ hơn. Giới hạn quota áp dụng, vì vậy OutOfQuotaPolicy định nghĩa hành vi fallback.

H: Làm thế nào để hủy tất cả pending uploads?

kotlin
WorkManager.getInstance(context).cancelAllWorkByTag("upload")

Tags cho phép các thao tác hàng loạt. Ngoài ra, cancelUniqueWork("name") hủy một chain cụ thể.

Xử Lý System-Induced Stops Với Backoff Policies

Khi hệ thống dừng worker (tối ưu hóa pin, áp lực bộ nhớ), chính sách backoff xác định thời gian retry.

BackoffConfig.ktkotlin
val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL,
        Duration.ofSeconds(30) // Initial delay, minimum 10 seconds
    )
    .setBackoffOnSystemInterruption(true) // New in 2.11
    .build()

Exponential backoff nhân đôi delay sau mỗi lần retry: 30s, 60s, 120s, tối đa 5 giờ. Linear backoff thêm delay ban đầu mỗi lần: 30s, 60s, 90s. Flag setBackoffOnSystemInterruption() mới trong WorkManager 2.11 áp dụng backoff ngay cả khi hệ thống (không phải worker) gây ra việc dừng.

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Những Điểm Quan Trọng Cho Việc Sử Dụng WorkManager Trong Production

  • Ưu tiên CoroutineWorker hơn Worker khi gọi suspend functions. Điều này tránh blocking threads và tích hợp với code coroutine hiện có.
  • Sử dụng enqueueUniqueWork() để ngăn công việc trùng lặp. Chọn KEEP cho các thao tác idempotent, REPLACE khi chỉ yêu cầu mới nhất quan trọng.
  • Đặt tags có ý nghĩa cho mỗi request. Tags cho phép quan sát, hủy bỏ, và debug trên toàn bộ work chains.
  • Kiểm thử workers độc lập với TestListenableWorkerBuilder, sau đó kiểm thử chains với mô phỏng constraint của TestDriver.
  • API WorkMetricsInfo của WorkManager 2.12 cung cấp lịch sử thực thi. Sử dụng nó để debug các mẫu retry và lý do dừng trong production.
  • Constraints tiết kiệm pin. Worker thất bại do không có mạng lãng phí chu kỳ CPU và tiêu hao năng lượng trong các lần retry.
  • Tối thiểu 15 phút cho periodic work được Android bắt buộc, không phải WorkManager. Thiết kế các tác vụ định kỳ để chịu được khoảng cách này.
Thử thách hôm nay

Bạn có tìm ra lỗi trong Android không?

Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Anthony Fillion-Maillet

Viết bởi

Anthony Fillion-Maillet

Người sáng lập SharpSkill

Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.

Cập nhật ngày 22 tháng 8, 2026

Chia sẻ

Bài viết liên quan