Android WorkManager 2026: คู่มือฉบับสมบูรณ์ Background Tasks, Constraints และคำถามสัมภาษณ์งาน

เรียนรู้ Android WorkManager สำหรับการรัน background tasks พร้อม constraints, chaining และ CoroutineWorker รวมถึงคำถามสัมภาษณ์งานสำหรับ Android developer

Android WorkManager 2026: คู่มือฉบับสมบูรณ์ Background Tasks, Constraints และคำถามสัมภาษณ์งาน

Android WorkManager จัดการงาน background ที่สามารถเลื่อนเวลาได้และรับประกันการทำงาน แม้หลังจากแอปพลิเคชันรีสตาร์ทหรืออุปกรณ์รีบูต ในฐานะส่วนหนึ่งของ Android Jetpack นั้น WorkManager 2.11.2 (เวอร์ชันเสถียร, มีนาคม 2026) มี API แบบรวมที่ทำงานบน API 23+ โดยเลือก implementation ที่ดีที่สุด (JobScheduler, AlarmManager หรือ Firebase JobDispatcher) ตาม API level ของอุปกรณ์

เมื่อใดควรใช้ WorkManager

ใช้ WorkManager สำหรับงานที่ต้องการการรับประกันการทำงาน: อัปโหลด log, ซิงค์ข้อมูล, ประมวลผลรูปภาพ สำหรับงานที่ต้องทำทันทีและไม่จำเป็นต้องคงอยู่เมื่อ process ตาย ให้ใช้ Kotlin Coroutines แทน

การตั้งค่า WorkManager 2.11 ในโปรเจกต์ Kotlin

ก่อนเขียน worker ใดๆ ต้องประกาศ dependency ก่อน WorkManager 2.11+ ต้องการ minSdk 23 และ 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 รวม Kotlin extensions และการสนับสนุน Coroutine ผ่าน CoroutineWorker ไม่จำเป็นต้องกำหนดค่าเพิ่มเติมสำหรับการใช้งานพื้นฐาน: WorkManager จะเริ่มต้นตัวเองผ่าน ContentProvider

การสร้าง Worker อย่างง่ายด้วย doWork()

คลาส Worker override doWork() และคืนค่า Result ระบบรันเมธอดนี้บน 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()
        }
    }
}

มีผลลัพธ์ที่เป็นไปได้สามแบบ: Result.success() หมายถึงเสร็จสิ้น, Result.failure() หยุดการลองใหม่, และ Result.retry() จัดกำหนดการใหม่ตามนโยบาย backoff

การใช้ CoroutineWorker สำหรับ Suspend Functions

เมื่องานเกี่ยวข้องกับ suspend functions (การเรียก Retrofit, การ query Room) CoroutineWorker ช่วยกำจัด callback ที่ซ้อนกัน

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() รันบน Dispatchers.Default หากต้องการเปลี่ยน dispatcher ให้ใช้ withContext() ภายในฟังก์ชัน

การกำหนด Constraints สำหรับการทำงานแบบมีเงื่อนไข

Constraints ป้องกันไม่ให้งานรันจนกว่าเงื่อนไขจะเป็นไปตามที่กำหนด ซึ่งช่วยประหยัดแบตเตอรี่และหลีกเลี่ยงการพยายามที่ล้มเหลว

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
        )
}

Constraints ที่มีให้ใช้งานรวมถึงประเภทเครือข่าย (CONNECTED, UNMETERED, METERED, NOT_ROAMING), ระดับแบตเตอรี่, สถานะการชาร์จ, พื้นที่จัดเก็บ และสถานะ idle ของอุปกรณ์ WorkManager 2.10+ ยังยอมรับ NetworkRequest แบบ raw สำหรับการควบคุมเครือข่ายที่ละเอียดยิ่งขึ้น

ข้อมูลสำหรับการสัมภาษณ์

คำถามสัมภาษณ์ที่พบบ่อยคือเมื่อใดควรใช้ KEEP เทียบกับ REPLACE ใน enqueueUniqueWork ตัวเลือก KEEP จะละเว้นคำขอใหม่หากงานที่มีชื่อเดียวกันมีอยู่แล้ว ในขณะที่ REPLACE จะยกเลิกงานที่มีอยู่และเริ่มต้นใหม่ ใช้ KEEP สำหรับการอัปโหลดที่การทำซ้ำสิ้นเปลือง bandwidth และใช้ REPLACE สำหรับการซิงค์ที่ข้อมูลล่าสุดเท่านั้นที่สำคัญ

การเชื่อมต่อ Work Requests ด้วย then() และ combine()

Workflow ที่ซับซ้อนต้องการการทำงานแบบลำดับและแบบขนาน WorkManager เชื่อมต่อ work requests โดยใช้ 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 จาก parallel workers จะรวมเข้ากับ ArrayCreatingInputMerger โดยค่าเริ่มต้น Worker ถัดไปจะได้รับคู่ key-value ทั้งหมด โดยจะสร้าง array สำหรับ key ที่ซ้ำกัน

พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

การกำหนดเวลา Periodic Work ด้วย PeriodicWorkRequest

PeriodicWorkRequest ทำงานซ้ำๆ โดยมีช่วงเวลาขั้นต่ำ 15 นาที (Android บังคับใช้ข้อจำกัดนี้)

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 อนุญาตให้ WorkManager รวมงานกับ job อื่นๆ เพื่อปรับปรุงประสิทธิภาพแบตเตอรี่ ช่วงเวลา 6 ชั่วโมงพร้อม flex 30 นาทีหมายความว่าการทำงานจะเกิดขึ้นระหว่าง 5:30 ถึง 6:00 หลังจากการรันครั้งก่อน

การสังเกตสถานะงานด้วย LiveData และ Flow

WorkManager เปิดเผยสถานะงานผ่าน WorkInfo สามารถสังเกตได้โดยใช้ LiveData หรือ 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 -> {}
    }
}

สถานะงานเป็นไปตาม lifecycle: ENQUEUED, RUNNING, SUCCEEDED/FAILED/CANCELLED, BLOCKED (รอ dependencies)

API Work Metrics ของ WorkManager 2.12

WorkManager 2.12.0-rc01 (สิงหาคม 2026) แนะนำ WorkMetricsInfo สำหรับการติดตามประวัติการทำงาน

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 รวมถึงระยะเวลาการทำงาน, จำนวนการลองใหม่, เหตุผลที่หยุด และ timestamps API นี้ช่วยให้ debug ความล้มเหลวที่ไม่ต่อเนื่องใน production ได้โดยไม่ต้องมีโครงสร้าง logging แบบกำหนดเอง

การเก็บรักษา Metrics

ข้อมูล WorkMetricsInfo จะถูกลบหลังจาก 7 วันโดยค่าเริ่มต้น กำหนดค่าการเก็บรักษาด้วย Configuration.Builder().setWorkMetricsRetentionDuration()

การทดสอบ Workers ด้วย WorkManagerTestInitHelper

Unit testing workers ต้องการ artifact work-testing TestListenableWorkerBuilder สร้าง workers โดยไม่ต้อง enqueue

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())
    }
}

สำหรับ integration tests ที่ตรวจสอบ constraints และ chaining ให้ใช้ TestDriver เพื่อจำลองการตอบสนอง constraint และการผ่านไปของเวลา

คำถามสัมภาษณ์ที่พบบ่อยเกี่ยวกับ WorkManager

การสัมภาษณ์ทางเทคนิคมักทดสอบความเข้าใจเกี่ยวกับการรับประกันและ trade-off ของ WorkManager คำถามเหล่านี้ปรากฏในการสัมภาษณ์ Android ระดับกลางและอาวุโส

ถ: จะเกิดอะไรขึ้นกับ WorkRequest เมื่อแอปถูก kill?

WorkManager จัดเก็บ work requests ในฐานข้อมูล Room เมื่อแอปรีสตาร์ทหรือระบบส่งสัญญาณว่า constraint ได้รับการตอบสนอง งานที่รอดำเนินการจะกลับมาทำงานต่อ การรับประกันนี้ทำให้ WorkManager แตกต่างจากงาน CoroutineScope ที่ตายไปพร้อมกับ process

ถ: WorkManager แตกต่างจาก AlarmManager อย่างไร?

AlarmManager กำหนดเวลา alarm ที่แม่นยำและรันโค้ดในเวลานาฬิกาที่เจาะจง WorkManager กำหนดเวลางานที่สามารถเลื่อนได้ซึ่งรันเมื่อ constraints ตรงตามเงื่อนไข โดยไม่มีการรับประกันเวลาที่แน่นอน ใช้ AlarmManager สำหรับ alarm ที่ผู้ใช้เห็น และ WorkManager สำหรับการประมวลผลข้อมูล background

ถ: WorkManager สามารถรันงานได้ทันทีหรือไม่?

ได้ ด้วย setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) Expedited work requests ใช้ slot foreground service บน API 31+ หรือ JobScheduler.setImportantWhileForeground() บน API ที่เก่ากว่า มีการใช้ข้อจำกัดโควตา ดังนั้น OutOfQuotaPolicy จึงกำหนดพฤติกรรม fallback

ถ: จะยกเลิก pending uploads ทั้งหมดได้อย่างไร?

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

Tags เปิดใช้งานการดำเนินการแบบกลุ่ม หรือ cancelUniqueWork("name") ยกเลิก chain ที่เจาะจง

การจัดการ System-Induced Stops ด้วย Backoff Policies

เมื่อระบบหยุด worker (การเพิ่มประสิทธิภาพแบตเตอรี่, แรงกดดันหน่วยความจำ) นโยบาย backoff จะกำหนดเวลา 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 เพิ่มความล่าช้าเป็นสองเท่าในแต่ละการลองใหม่: 30s, 60s, 120s สูงสุด 5 ชั่วโมง Linear backoff เพิ่มความล่าช้าเริ่มต้นทุกครั้ง: 30s, 60s, 90s Flag setBackoffOnSystemInterruption() ใหม่ใน WorkManager 2.11 ใช้ backoff แม้ว่าระบบ (ไม่ใช่ worker) เป็นสาเหตุของการหยุด

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ประเด็นสำคัญสำหรับการใช้ WorkManager ใน Production

  • เลือกใช้ CoroutineWorker มากกว่า Worker เมื่อเรียก suspend functions ซึ่งหลีกเลี่ยงการบล็อก threads และรวมเข้ากับโค้ด coroutine ที่มีอยู่
  • ใช้ enqueueUniqueWork() เพื่อป้องกันงานซ้ำ เลือก KEEP สำหรับการดำเนินการ idempotent และ REPLACE เมื่อคำขอล่าสุดเท่านั้นที่สำคัญ
  • ตั้ง tags ที่มีความหมายในทุก request Tags ช่วยให้การสังเกต, การยกเลิก และการ debug ข้าม work chains
  • ทดสอบ workers แยกด้วย TestListenableWorkerBuilder จากนั้นทดสอบ chains ด้วยการจำลอง constraint ของ TestDriver
  • API WorkMetricsInfo ของ WorkManager 2.12 ให้ประวัติการทำงาน ใช้เพื่อ debug รูปแบบ retry และเหตุผลการหยุดใน production
  • Constraints ช่วยประหยัดแบตเตอรี่ Worker ที่ล้มเหลวเนื่องจากไม่มีเครือข่ายสิ้นเปลือง CPU cycles และใช้พลังงานในการลองใหม่
  • ขั้นต่ำ 15 นาทีสำหรับ periodic work ถูกบังคับโดย Android ไม่ใช่ WorkManager ออกแบบงานเป็นระยะให้ทนต่อช่วงเวลานี้
ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Android เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 22 สิงหาคม 2569

แชร์

บทความที่เกี่ยวข้อง