آموزش ورک منیجر WorkManager در اندروید: اجرای تسک پس زمینه

برنامه نویسی اندروید
آموزش ورک منیجر WorkManager در اندروید: اجرای تسک پس زمینه

یکی از پیچیده ترین چالش های مهندسی در اکوسیستم برنامه نویسی اندروید، مدیریت و اجرای مطمئن پردازش های پس زمینه (Background Tasks) است.

از زمان معرفی اندروید ۸.۰ (Oreo) و سخت تر شدن سیاست های مدیریت انرژی مانند Doze Mode و App Standby Buckets، سیستم عامل اندروید به منظور بهینه سازی مصرف باتری کاربر، اجرای پروسه های پس زمینه را به شدت محدود کرده است.

در این شرایط، بهره گیری از رویکردهای قدیمی نظیر Service معمولی، IntentService یا استفاده غیر اصولی از برودکست رسیور در اندروید به طور مکرر به بسته شدن ناگهانی پروسه، عدم اجرای وظایف و تجربه کاربری نامطلوب می انجامد.

شرکت گوگل برای پایان دادن به این سردرگمی، کتابخانه قدرتمند ورک منیجر (WorkManager) را به عنوان هسته اصلی معماری مدرن اندروید در مجموعه Android Jetpack معرفی کرد.

ورک منیجر پایداری و تضمین اجرای ۱۰۰ درصدی وظایف تعویق پذیر را فراهم می سازد و بیش از ۹۵ درصد دستگاه های فعال اندروید در سال ۲۰۲۶ را بدون کوچک ترین ناسازگاری پوشش می دهد.

در این راهنمای جامع مرجع با تکیه بر زبان برنامه نویسی کاتلین، معماری داخلی، پیاده سازی CoroutineWorker، زنجیره سازی تسک ها، تزریق وابستگی با Hilt و الگو های پیشرفته را به صورت تخصصی بررسی خواهیم کرد.

مفهوم تضمین اجرا (Guaranteed Execution): ورک منیجر برای پردازش های ماندگار (Persistent Work) طراحی شده است. در صورت کشته شدن پروسس یا ریبوت دستگاه، متادیتای تسک در دیتابیس داخلی حفظ شده و با مهیا شدن شرایط اجرا می شود. برای محاسبات آنی صرفاً از Coroutine استفاده کنید.

۱. ماتریس تصمیم گیری مهندسی: چه زمانی از WorkManager استفاده کنیم؟

در مهندسی نرم افزار اندروید، انتخاب اشتباه ابزار پردازش پس زمینه منجر به مصرف بی رویه منابع سخت افزاری یا حذف تسک توسط سیستم عامل می شود. برای انتخاب ابزار دقیق، باید به دو پرسش کلیدی پاسخ دهید:

  1. آیا اجرای تسک باید دقیقاً در یک ثانیه یا دقیقه مشخص آغاز شود؟ (Exact Timing)
  2. آیا در صورت کشته شدن پروسه اپلیکیشن، کار باید حتماً ادامه یابد؟ (Persistent Work)

ماتریس تحلیلی زیر چارچوب تصمیم گیری استانداردی را در اختیار مهندسان قرار می دهد:

  • کارهای فوری درون پروسه ای (Immediate In-process): وظایفی که صرفاً در حضور کاربر در یک صفحه خاص اهمیت دارند (مانند ارسال درخواست جستجوی لحظه ای به سرور یا انیمیشن). راهکار مناسب: Kotlin Coroutines همراه با Scope های متصل به لایف سایکل نظیر viewModelScope.
  • کارهای نیازمند اجرای سر ثانیه دقیق (Exact Timing): وظایفی که باید در یک میلی ثانیه یا ثانیه مشخص بدون درنگ اجرا شوند (مانند آلارم بیدارباش، یادآور مصرف دارو یا رویدادهای تقویم). راهکار مناسب: AlarmManager سیستم عامل. ورک منیجر برای زمان بندی سر ثانیه ای طراحی نشده است.
  • کارهای مداوم همراه با اطلاع کاربر (Long-running User Aware): وظایفی که طولانی مدت هستند اما کاربر مستقیماً با آنها تعامل دارد (مانند پخش موسیقی، ضبط صدا یا ناوبری صوتی نقشه). راهکار مناسب: Foreground Service با نوتیفیکیشن ماندگار.
  • کارهای تعویق پذیر با اجرای تضمینی (Deferrable Guaranteed Work): وظایفی که زمان دقیق شروع آنها حیاتی نیست، اما باید حتماً انجام شوند و به شرایطی مانند اتصال به اینترنت نامحدود یا اتصال به شارژر وابسته هستند (مانند آپلود لاگ های تحلیلی، پشتیبان گیری از پایگاه داده و همگام سازی دیتای محلی). راهکار مناسب و استاندارد: WorkManager.
ابزار پردازش پس زمینه تضمین بقا پس از مرگ پروسه پشتیبانی از محدودیت های باتری و شبکه زمان بندی دقیق بر ثانیه موارد کاربرد طلایی
Kotlin Coroutines خیر (با نابودی ویومدل یا پروسه لغو می شود) خیر (نیازمند بررسی دستی کد) خیر فراخوانی وب سرویس در UI و محاسبات رم
AlarmManager بله (توسط سیستم عامل بیدار می شود) خیر (مستقیماً در زمان مشخص شلیک می کند) بله (دقت بالا با setExactAndAllowWhileIdle) ساعت زنگ دار و یادآورهای تقویم
Foreground Service بله (دارای اولویت بالا با نوتیفیکیشن) خیر (مدیریت بر عهده توسعه دهنده) خیر پخش موزیک آنلاین و ضبط موقعیت مکانی لحظه ای
WorkManager بله (ذخیره سازی متادیتا در دیتابیس پایدار) بله (پشتیبانی کامل از شروط باتری، شبکه و حافظه) خیر (حداقل بازه دوره ای ۱۵ دقیقه) سینک دیتای محلی با سرور، آپلود مدیا و ارسال لاگ ها

۲. کالبدشکافی معماری داخلی و نحوه کارکرد WorkManager

معماری داخلی ورک منیجر و سیستم زمانبندی هوشمند
دیاگرام معماری لایه ای ورک منیجر: هدایت درخواست به دیتابیس Room و زمانبندهای سیستمی

ورک منیجر یک سرویس جادویی نیست؛ بلکه یک لایه انتزاعی بسیار هوشمند (Abstraction Layer) است که متناسب با نسخه اندروید دستگاه کاربر، کارآمدترین API مدیریت کار سیستم عامل را انتخاب می کند:

  • اندروید ۶.۰ به بالا (API ۲۳+): از هسته قدرتمند JobScheduler سیستم عامل اندروید استفاده می کند که مستقیماً با هسته لینوکس و سیاست های Doze Mode در تعامل است.
  • اندروید های قدیمی تر: از تلفیق BroadcastReceiver و AlarmManager برای شبیه سازی صف وظایف استفاده می نماید.

جریان پردازش یک درخواست در ورک منیجر به شرح زیر است:

  1. ثبت در پایگاه داده (Room SQLite): زمانی که متد enqueue() را فراخوانی می کنید، جزئیات کامل Worker، شناسه تسک، وضعیت اولیه (ENQUEUED) و محدودیت ها بلافاصله درون یک دیتابیس محلی ذخیره می شوند. این مرحله در کسری از میلی ثانیه انجام شده و تضمین می کند اگر بلافاصله اپلیکیشن کرش کند یا بسته شود، کار پاک نخواهد شد.
  2. پایشگر محدودیت ها (Constraint Trackers): سرویس داخلی ورک منیجر به برودکست های وضعیت باتری، میزان شارژ و وضعیت اتصال به اینترنت Wi-Fi گوش می دهد. تا زمانی که این شروط فراهم نشوند، تسک معلق باقی می ماند.
  3. تحویل به پروسه اجرا (Worker Execution): به محض برقراری کلیه شروط، سیستم عامل پروسه برنامه را (در صورت بسته بودن) در پس زمینه اجرا کرده و متد doWork() را روی یک ترد پس زمینه (Background Thread) اجرا می کند.
  4. به روزرسانی وضعیت و پاک سازی: پس از اتمام متد و بازگشت یکی از نتایج Result.success()، Result.retry() یا Result.failure()، وضعیت رکورد در دیتابیس به روز شده و منابع سیستمی آزاد می گردند.

۳. انواع کلاس های Worker در WorkManager

بسته به الگوی برنامه نویسی و زبان استفاده شده در پروژه، ورک منیجر چهار کلاس پایه مختلف را در اختیارتان می گذارد:

کلاس پایه Worker زبان و پارادایم مورد استفاده نحوه مدیریت نخ (Threading) توصیه در سال ۲۰۲۶
Worker جاوا یا کاتلین سنتی اجرای متد doWork به صورت همگام (Synchronous) روی Executor پیش فرض تنها در پروژه های قدیمی و کدهای لگاسی
CoroutineWorker کاتلین (Kotlin Coroutines) متد doWork از نوع suspend است و روی Dispatchers.Default اجرا می شود استاندارد طلایی و الزامی برای کلیه پروژه های مدرن اندروید
RxWorker جاوا / کاتلین با RxJava۲ یا RxJava۳ بازگرداندن شیء Single<Result> برای مدیریت جریان های واکنشی مناسب پروژه های مبتنی بر معماری واکنشی Rx
ListenableWorker جاوا پیشرفته با Guava بازگرداندن شیء ListenableFuture برای پیاده سازی دستی تردینگ سطح پایین و پیچیده؛ صرفاً برای سناریوهای خاص کتابخانه نویسی

۴. راه اندازی و افزودن وابستگی های WorkManager در گریدل

برای افزودن ورک منیجر به پروژه های مدرن اندروید، قطعه کدهای زیر را در فایل build.gradle.kts ماژول اپلیکیشن یا ماژول core قرار دهید:

// build.gradle.kts
dependencies {
    val workVersion = "2.9.1"

    // هسته اصلی ورک منیجر برای کاتلین و کروتین
    implementation("androidx.work:work-runtime-ktx:$workVersion")

    // پشتیبانی از تزریق وابستگی Hilt در Worker ها
    implementation("androidx.hilt:hilt-work:1.2.0")
    kapt("androidx.hilt:hilt-compiler:1.2.0")

    // ابزار های تست واحد و شبیه سازی تست درایور
    testImplementation("androidx.work:work-testing:$workVersion")
}

۵. پیاده سازی گام به گام CoroutineWorker

در این بخش یک سناریوی پروداکشن واقعی را پیاده سازی می کنیم: همگام سازی دیتای محلی با وب سرویس و ذخیره سازی آن بر اساس اصول معماری تمیز. یک کلاس به نام SyncDataWorker ایجاد می کنیم که از CoroutineWorker ارث بری دارد:

// تزریق مستقیم ریپازیتوری به CoroutineWorker با استفاده از Hilt
@HiltWorker
class SyncDataWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val syncRepository: SyncRepository
) : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
        try {
            val category = inputData.getString("KEY_CATEGORY") ?: "GENERAL"
            val syncResult = syncRepository.synchronizeData(category, limit = 50)

            if (syncResult.isSuccess) {
                val output = workDataOf("SYNCED_COUNT" to syncResult.count)
                Result.success(output)
            } else if (runAttemptCount < 3) {
                Result.retry() // تلاش مجدد با الگوریتم Exponential Backoff
            } else {
                Result.failure()
            }
        } catch (e: Exception) {
            Result.failure(workDataOf("KEY_ERROR" to (e.localizedMessage ?: "Unknown")))
        }
    }
}
نکته بسیار مهم در تزریق وابستگی با Hilt: به دلیل اینکه فرآیند نمونه سازی (Instantiation) کلاس های Worker توسط سیستم عامل اندروید مدیریت می شود و نه به طور پیش فرض توسط کانتینر Hilt، استفاده از حاشیه نویسی های @HiltWorker و @AssistedInject اجباری است. همچنین باید Initializer پیش فرض ورک منیجر را در فایل مانیفست غیرفعال کرده و Configuration.Provider را در کلاس Application سفارشی سازی نمایید.

۶. پیکربندی Configuration.Provider برای اتصال کامل Hilt و WorkManager

برای اینکه تزریق وابستگی به درستی در ورکرها کار کند، فایل مانیفست AndroidManifest.xml را باز کرده و پرووایدر پیش فرض را غیرفعال کنید:

<!-- AndroidManifest.xml -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- حذف Initializer پیش فرض ورک منیجر -->
    <meta-data
        android:name="androidx.work.WorkManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
</provider>

سپس کلاس اصلی اپلیکیشن را به صورت زیر ویرایش نمایید:

import android.app.Application
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject

@HiltAndroidApp
class MyApp : Application(), Configuration.Provider {

    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    override val workManagerConfiguration: Configuration
        get() = Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .setMinimumLoggingLevel(android.util.Log.INFO)
            .build()
}

۷. تنظیم محدودیت های هوشمند سیستمی (Constraints)

یکی از شگفت انگیزترین ویژگی های ورک منیجر، قابلیت تعیین شروط محیطی است. تسک شما می تواند تا برقراری کلیه شرایط مورد نیاز در حالت معلق بماند:

  • وضعیت اتصال شبکه (NetworkType): تعیین اتصال به اینترنت آزاد، اینترنت Wi-Fi نامحدود (UNMETERED) یا هر نوع اتصالی (CONNECTED).
  • وضعیت شارژ باتری (RequiresCharging): الزام به اتصال شارژر برای کارهای به شدت سنگین دیتابیسی یا انکود ویدیو.
  • حداقل درصد باتری (RequiresBatteryNotLow): جلوگیری از اجرای تسک زمانی که باتری کمتر از ۱۵ درصد شارژ دارد.
  • وضعیت بیکاری دستگاه (RequiresDeviceIdle): اجرای کار فقط زمانی که کاربر حداقل چند دقیقه با گوشی کار نکرده باشد (ایده آل برای ایندکس دیتای محلی).
  • فضای ذخیره سازی کافی (RequiresStorageNotLow): تضمین اینکه دیسک ذخیره سازی پر نخواهد شد.
import androidx.work.Constraints
import androidx.work.NetworkType

val smartConstraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.UNMETERED) // فقط با اینترنت وای فای بدون هزینه
    .setRequiresCharging(true)                    // فقط وقتی گوشی روی شارژر قرار دارد
    .setRequiresBatteryNotLow(true)               // باتری ضعیف نباشد
    .setRequiresDeviceIdle(false)                 // نیاز به بیکاری دستگاه نیست
    .build()

۸. الگو های زمان بندی: کارهای یک باره در برابر کارهای دوره ای

ورک منیجر از دو الگوی اصلی ثبت تسک پشتیبانی می کند:

ماشین وضعیت چرخه حیات کارهای پس زمینه در ورک منیجر
دیاگرام ماشین وضعیت کارهای پس زمینه و الگوی تلاش مجدد تصاعدی در ورک منیجر

۱. تسک های یک باره (OneTimeWorkRequest)

این تسک ها یک بار اجرا شده و پس از اتمام موفقیت آمیز، به کار خود پایان می دهند. برای این تسک ها می توانید تاخیر اولیه (Initial Delay) و سیاست جبران شکست (Backoff Criteria) تعیین کنید:

import androidx.work.BackoffPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.workDataOf
import java.util.concurrent.TimeUnit

val uploadWorkRequest = OneTimeWorkRequestBuilder<SyncDataWorker>()
    .setConstraints(smartConstraints)
    .setInitialDelay(5, TimeUnit.MINUTES) // ۵ دقیقه پس از ثبت اجرا شود
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL, // افزایش تصاعدی تاخیر در صورت شکست تسک
        30,
        TimeUnit.SECONDS
    )
    .setInputData(workDataOf("KEY_CATEGORY" to "USER_DOCUMENTS"))
    .addTag("TAG_UPLOAD_WORK")
    .build()

۲. تسک های دوره ای تکرارشونده (PeriodicWorkRequest)

برای وظایفی که باید در بازه های زمانی منظم تکرار شوند (مانند بروزرسانی کش هر ۶ ساعت یک بار). به منظور جلوگیری از مصرف باتری، حداقل بازه تکرار مجاز در سیستم عامل اندروید ۱۵ دقیقه (۱۵ Minutes) است:

import androidx.work.PeriodicWorkRequestBuilder
import java.util.concurrent.TimeUnit

val periodicSyncRequest = PeriodicWorkRequestBuilder<SyncDataWorker>(
    repeatInterval = 6,
    repeatIntervalTimeUnit = TimeUnit.HOURS,
    flexTimeInterval = 30, // بازه انعطاف ۳۰ دقیقه ای در انتهای هر ۶ ساعت
    flexTimeIntervalUnit = TimeUnit.MINUTES
)
.setConstraints(smartConstraints)
.build()
قاعده کلیدی Unique Work: برای جلوگیری از ثبت تکراری کارهای دوره ای هنگام هر بار اجرای اپلیکیشن، همواره از متد enqueueUniquePeriodicWork استفاده کنید. این متد تضمین می کند که تنها یک نسخه فعال از تسک با شناسه یکتا در صف باقی می ماند و درخواست های مضاعف با سیاست ExistingPeriodicWorkPolicy.KEEP نادیده گرفته می شوند.

۹. زنجیره سازی پیشرفته وظایف (Work Chaining)

یکی از خیره کننده ترین قابلیت های ورک منیجر، توانایی ایجاد گراف های جهت دار غیرمدور (DAG) برای اجرای هماهنگ چندین تسک متوالی یا موازی است:

زنجیره سازی و اجرای موازی کارهای وابسته در ورک منیجر
دیاگرام جریان موازی و همگرای تسک های وابسته با متدهای beginWith و then در ورک منیجر
import androidx.work.WorkManager

val workManager = WorkManager.getInstance(context)

// سه تسک فیلتر تصویر که به طور کاملاً موازی اجرا می شوند
val filterTask1 = OneTimeWorkRequestBuilder<ImageFilterWorker>().build()
val filterTask2 = OneTimeWorkRequestBuilder<ImageFilterWorker>().build()
val filterTask3 = OneTimeWorkRequestBuilder<ImageFilterWorker>().build()

// تسک فشرده سازی که باید پس از اتمام هر سه تسک موازی اجرا شود
val compressTask = OneTimeWorkRequestBuilder<CompressImageWorker>().build()

// تسک نهایی آپلود به سرور ابری
val uploadTask = OneTimeWorkRequestBuilder<CloudUploadWorker>().build()

// ایجاد زنجیره پردازشی هماهنگ
workManager
    .beginWith(listOf(filterTask1, filterTask2, filterTask3)) // اجرای موازی ۳ تسک
    .then(compressTask)                                       // سپس فشرده سازی
    .then(uploadTask)                                         // سپس آپلود نهایی
    .enqueue()

۱۰. تسک های فوری و شتاب یافته (Expedited Work) در اندروید ۱۲ به بعد

از اندروید ۱۲ (API ۳۱)، گوگل محدودیت های سختی بر سرویس های پس زمینه اعمال کرد و قابلیت Expedited Work را در ورک منیجر گنجاند.

تسک های Expedited برای کارهای فوری با اولویت بالا هستند که باید بلافاصله حتی اگر سیستم عامل در حالت Doze Mode باشد اجرا شوند و تاخیر پردازش کمتر از ۳۵۰ میلی ثانیه در تسک های شتاب یافته را تضمین کنند. این قابلیت با متد setExpedited() فعال شده و نیازمند پیاده سازی getForegroundInfo() است:

import android.app.Notification
import androidx.core.app.NotificationCompat
import androidx.work.ForegroundInfo

override suspend fun getForegroundInfo(): ForegroundInfo {
    val notification: Notification = NotificationCompat.Builder(applicationContext, "SYNC_CHANNEL_ID")
        .setSmallIcon(android.R.drawable.stat_sys_upload)
        .setContentTitle("همگام سازی فوری اطلاعات")
        .setContentText("در حال آپلود فایل های حساس پس زمینه...")
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .build()

    return ForegroundInfo(NOTIFICATION_ID, notification)
}

۱۱. پایش وضعیت و مشاهده پیشرفت زنده تسک ها در لایه UI با Flow

ورک منیجر قابلیت رصد لحظه ای وضعیت کارها را از طریق Kotlin Flow و LiveData مهیا می سازد. می توان درصد پیشرفت را در درون Worker محاسبه کرد و در کامپوز یا ویومدل نمایش داد:

// درون متد doWork در Worker
for (progress in 1..100) {
    setProgress(workDataOf("KEY_PROGRESS" to progress))
    // شبیه سازی پردازش داده ها
    kotlinx.coroutines.delay(100)
}

// درون ویومدل (ViewModel) برای اتصال به Jetpack Compose
val syncWorkInfoFlow = workManager
    .getWorkInfosForUniqueWorkFlow("UNIQUE_SYNC_JOB")
    .map { workInfoList ->
        val info = workInfoList.firstOrNull()
        val progress = info?.progress?.getInt("KEY_PROGRESS", 0) ?: 0
        val isRunning = info?.state == WorkInfo.State.RUNNING
        SyncUiState(progress = progress, isRunning = isRunning)
    }

۱۲. استراتژی های تست واحد و تست یکپارچه با TestDriver

برای راستی آزمایی و تضمین کارکرد بی نقص ورکرها بدون نیاز به انتظار چندین ساعته، آرتیفکت work-testing امکان شبیه سازی هوشمند تغییرات شبکه و زمان را در کنار اصول معماری تمیز فراهم می آورد:

// تست زمانبندی و تایید برقراری شروط سیستمی با TestDriver
class SyncWorkerUnitTest {
    @Before
    fun setup() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        WorkManagerTestInitHelper.initializeTestWorkManager(context)
    }

    @Test
    fun testPeriodicWorkerConstraints() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        val testDriver = WorkManagerTestInitHelper.getTestDriver(context)!!
        val workManager = WorkManager.getInstance(context)

        val request = PeriodicWorkRequestBuilder<SyncDataWorker>(6, TimeUnit.HOURS)
            .setConstraints(smartConstraints).build()
        workManager.enqueue(request).result.get()

        // شبیه سازی برقراری اینترنت و جهش زمانی بدون انتظار ۶ ساعته
        testDriver.setAllConstraintsMet(request.id)
        testDriver.setPeriodDelayMet(request.id)

        val workInfo = workManager.getWorkInfoById(request.id).get()
        assertEquals(WorkInfo.State.ENQUEUED, workInfo.state)
    }
}

۱۳. اشتباهات مهلک و خطاهای رایج پروداکشن در استفاده از WorkManager

بررسی لاگ های کرش در اپلیکیشن های مقیاس بزرگ، چندین ضدالگوی تکراری را در استفاده از ورک منیجر آشکار می کند:

  1. انفجار صف با خطای Too Many Pending Jobs: سیستم عامل اندروید دارای سقف حداکثری (معمولاً ۱۰۰ تسک همزمان در صف JobScheduler) است. اگر در حلقه های تکرار بدون کنترل صدها تسک ثبت کنید، سیستم عامل کل برنامه را کرش می دهد. همواره تسک های متوالی را با زنجیره سازی (Chaining) در یک جریان واحد ادغام کنید.
  2. افتادن در تله Throttling با Result.retry(): اگر یک تسک به دلیل خطای سرور مرتباً ریترای شود، اندروید زمان اجرای آن را تا چندین ساعت جریمه می کند. همواره با فیلد runAttemptCount سقف تلاش (مثلاً حداکثر ۳ بار) تعیین کرده و پس از آن وضعیت Result.failure() بازگردانید.
  3. نشت حافظه در Custom WorkerFactory: در صورت پیکربندی دستی HiltWorker، مطمئن شوید که HiltWorkerFactory را در کلاس Application به درستی ست کرده اید و فکتوری های قدیمی را آنبایند نموده اید.
  4. انتقال داده های بسیار حجیم درون Data: آبجکت Data در ورک منیجر برای داده های حداکثر ۱۰ کیلوبایت طراحی شده است. قرار دادن تصاویر یا آرایه های بزرگ جیسون در Data خطای فاحش بوده و منجر به کرش TransactionTooLargeException می شود. همواره فقط شناسه های عددی یا مسیر فایل را به عنوان پارامتر پاس دهید.
قاعده طلایی همگام سازی دیتای محلی (Offline-First): در معماری های مدرن، کارهای پس زمینه نباید مستقیماً UI را تغییر دهند. تسک پس زمینه داده ها را از وب سرویس دریافت کرده و در دیتابیس Room محلی درج می کند؛ سپس لایه UI از طریق Flow به دیتابیس گوش داده و تغییرات را به صورت خودکار رندر می نماید. این جداسازی تضمین می کند که تجربه کاربری همواره روان و بدون لکنت باقی بماند.

۱۴. بررسی عمیق مکانیزم های مقابله با Doze Mode و App Standby Buckets

برای درک چرایی رفتار ورک منیجر در محیط های واقعی تولیدی (Production)، باید با سازوکار درونی مدیریت انرژی اندروید آشنا شد. سیستم عامل اندروید از دو مکانیزم پیشرفته برای حفظ طول عمر باتری استفاده می کند:

  • حالت چرت زدن سیستم عامل (Doze Mode): زمانی که دستگاه از شارژر جدا شده، صفحه نمایش خاموش است و برای مدتی بدون حرکت روی میز باقی مانده است، سیستم عامل وارد حالت Doze می شود. در این حالت، دسترسی به شبکه قطع می شود، WakeLock ها نادیده گرفته شده و اجرای تسک های عادی معلق می ماند. سیستم عامل تنها در پنجره های زمانی بسیار کوتاه به نام «پنجره های نگه داری» (Maintenance Windows) اجازه اجرای کارهای انباشته شده را می دهد. ورک منیجر به طور خودکار تسک های عادی شما را با این پنجره ها هماهنگ می کند تا کمترین بیداری پردازنده (CPU Wakeup) رخ دهد.
  • سطوح آماده به کار اپلیکیشن ها (App Standby Buckets): اندروید اپلیکیشن ها را بر اساس میزان استفاده کاربر در دسته های Active، Working Set، Frequent، Rare و Restricted طبقه بندی می کند. اگر کاربری به ندرت اپلیکیشن شما را باز کند، برنامه در دسته Rare قرار گرفته و سقف اجرای کارهای پس زمینه آن به حداکثر یک بار در ۲۴ ساعت کاهش می یابد. ورک منیجر به صورت درونی این محدودیت های سیستمی را مدیریت می کند و از بروز رفتارهای غیرقابل پیش بینی جلوگیری می نماید.

۱۵. معماری آفلاین محور (Offline-First) با تلفیق WorkManager، دیتابیس Room و کاتلین Flow

یکی از شاخص ترین معماری های نرم افزاری در شرکت های برتر فناوری دنیا، الگوی Offline-First است. در این الگو، تجربه کاربری هرگز معطل اینترنت باقی نمی ماند.

معماری همگام سازی دیتای محلی آفلاین با ورک منیجر و روم
خط لوله همگام سازی دیتای آفلاین با دیتابیس محلی Room، ورک منیجر و استریم داده های Flow

کاربر هر عملیاتی (مانند ارسال کامنت، لایک کردن یا ویرایش اطلاعات پروفایل) انجام دهد، بلافاصله در پایگاه داده محلی Room ذخیره شده و وضعیت آن به عنوان PENDING علامت گذاری می شود. سپس یک OneTimeWorkRequest با قید اتصال شبکه (NetworkType.CONNECTED) در صف ورک منیجر قرار می گیرد.

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow

@Dao
interface PendingActionDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertPendingAction(action: PendingActionEntity): Long

    @Query("SELECT * FROM pending_actions WHERE isSynced = 0 ORDER BY createdAt ASC")
    fun getPendingActions(): Flow<List<PendingActionEntity>>

    @Query("UPDATE pending_actions SET isSynced = 1 WHERE id = :actionId")
    suspend fun markAsSynced(actionId: Long)
}

هنگامی که اینترنت مجدداً وصل شد، ورک منیجر کلاس PendingActionsSyncWorker را فراخوانی می کند. این ورکر لیست وظایف معلق را از دیتابیس خوانده، یکی پس از دیگری به سرور ارسال کرده و وضعیت آنها را به SYNCED تغییر می دهد.

لایه رابط کاربری (Jetpack Compose) تنها به Flow دیتابیس Room گوش می دهد؛ به این ترتیب بدون اینکه UI نیازی به مدیریت پیچیدگی های قطعی اینترنت داشته باشد، واکنش گرایی برنامه تضمین می گردد.

۱۶. مهاجرت گام به گام از سرویس های قدیمی (IntentService و JobIntentService) به WorkManager

اگر در پروژه های قدیمی اندروید از IntentService یا JobIntentService استفاده می کنید، باید در نظر داشته باشید که گوگل این کلاس ها را در نسخه های جدید اندروید به طور کامل منسوخ (Deprecated) اعلام کرده است. برای مهاجرت اصولی:

  1. کد داخل متد onHandleWork(Intent) یا onHandleIntent(Intent) را استخراج کرده و به متد doWork() در یک CoroutineWorker منتقل کنید.
  2. پارامترهای Intent.getExtra() را با شیء inputData در ورک منیجر جایگزین نمایید.
  3. تگ های <service> اضافی مربوط به آن سرویس ها را از فایل مانیفست AndroidManifest.xml حذف کنید تا فضای مانیفست سبک تر شود.
  4. فراخوانی های JobIntentService.enqueueWork(...) را با WorkManager.getInstance(context).enqueue(...) جایگزین نمایید.

۱۷. چک لیست و جمع بندی استراتژی های دفاعی در محیط پروداکشن

برای تضمین بهترین امتیاز عملکردی در گوگل پلی (Android Vitals) و جلوگیری از قرار گرفتن اپلیکیشن در لیست برنامه های مصرف کننده بیش از حد باتری (Excessive Wakeups):

  • اجتناب از پردازش های طولانی در یک Worker منفرد: زمان اجرای مجاز برای تسک های عادی ورک منیجر حداکثر ۱۰ دقیقه است. اگر کاری بیش از این مدت طول بکشد، سیستم عامل پروسه را خاتمه می دهد (Stop Execution). کارهای سنگین طولانی باید به قطعات کوچک تر (Chunking) شکسته شده و زنجیره سازی شوند.
  • پاسخگویی سریع به رویداد لغو (Cooperative Cancellation): همواره در طول حلقه های سنگین پردازشی، متغیر بولین isStopped را چک کنید:
    for (chunk in fileChunks) {
        if (isStopped) {
            // آزادسازی بلافاصله فایل هندلرها و سوکت های باز شبکه
            return Result.failure()
        }
        uploadChunk(chunk)
    }
    
  • بهره گیری از قید شارژر برای کارهای به شدت سنگین: اگر نیاز به عملیات هایی نظیر پردازش تصاویر با هوش مصنوعی یا ایجاد ایندکس های متنی سنگین دارید، قید setRequiresCharging(true) را فعال کنید تا زمانی که باتری کاربر رو به اتمام است، پردازش های سنگین به او آسیب نزند.

پرسش های متداول و نکات کلیدی

۱. حداقل زمان تکرار مجاز برای PeriodicWorkRequest چقدر است؟

حداقل بازه زمانی مجاز برای تسک های دوره ای در اندروید ۱۵ دقیقه است. اگر مقداری کمتر از ۱۵ دقیقه (مثلاً ۵ دقیقه) تنظیم کنید، ورک منیجر به طور خودکار آن را به ۱۵ دقیقه گرد خواهد کرد.

۲. چگونه می توان در صورت بسته شدن برنامه مانع لغو تسک ورک منیجر شد؟

ماهیت اصلی ورک منیجر تضمین اجرای کار پس از بسته شدن برنامه است. نیازی به کار اضافه ای ندارید؛ ورک منیجر تسک ها را در پایگاه داده محلی ذخیره می کند و سیستم عامل حتی در صورت بسته بودن اپلیکیشن، در زمان مناسب تسک را فراخوانی می نماید.

۳. چه تفاوتی میان سیاست های KEEP و REPLACE در Unique Work وجود دارد؟

سیاست KEEP باعث می شود اگر تسکی با همین نام در صف در حال اجرا یا انتظار باشد، درخواست جدید نادیده گرفته شود. اما سیاست REPLACE تسک قبلی را لغو کرده و تسک جدید را به عنوان جایگزین آن در صف قرار می دهد.

۴. حداکثر حجم دیتایی که می توان از طریق Data به Worker ارسال کرد چقدر است؟

محدودیت شیء Data دقیقاً ۱۰ کیلوبایت (۱۰KB) است. این آبجکت فقط برای ارسال متادیتای سبک (نظیر آیدی رکورد دیتابیس یا پارامترهای عددی) طراحی شده است و نباید برای آرایه های بزرگ یا فایل استفاده شود.

۵. کاربرد متد getForegroundInfo در تسک های شتاب یافته چیست؟

در اندروید ۱۲ به بعد، تسک های با اولویت بالا برای اجرا به عنوان Expedited Work نیازمند نمایش یک نوتیفیکیشن ماندگار به کاربر هستند تا کاربر از پردازش در پس زمینه مطلع شود. متد getForegroundInfo() این نوتیفیکیشن را پیکربندی می کند.

۶. چگونه می توان ورک منیجر را در تست های واحد تست کرد؟

با استفاده از آرتیفکت رسمی androidx.work:work-testing و کلاس WorkManagerTestInitHelper و ابزار TestDriver می توان اجرای ایزوله ورکرها و عبور زمان را بدون نیاز به صف واقعی گریدل تست نمود.

۷. تفاوت میان CoroutineWorker و Worker معمولی چیست؟

کلاس Worker متد همگام doWork() را بر روی یک ترد پس زمینه پیش فرض اجرا می کند، در حالی که CoroutineWorker از توابع تعلیق پذیر (suspend) کاتلین بهره برده و به شما اجازه می دهد از کانتکست های غیرمسدودکننده نظیر Dispatchers.IO برای عملیات های شبکه و دیتابیس به صورت کاملاً غیرهمزمان استفاده کنید.

۸. در صورت قطع ارتباط شبکه وسط اجرای تسک، چه اتفاقی برای ورکر می افتد؟

چنانچه برای تسک خود قید NetworkType.CONNECTED تعیین کرده باشید و حین اجرا اتصال قطع شود، ورک منیجر به صورت خودکار سیگنال لغو را از طریق متد isStopped یا لغو Coroutine ارسال کرده و وضعیت کار را به ENQUEUED برمی گرداند تا پس از وصل مجدد اینترنت بازاجرا شود.

۹. چگونه خطای کشته شدن ورک منیجر در رام های سفارشی (شیائومی، هواوی) را مدیریت کنیم؟

برخی سیستم های مدیریت باتری تهاجمی (مانند MIUI یا EMUI) تسک های پس زمینه را مسدود می کنند. راهکار استاندارد نمایش راهنما به کاربر جهت غیرفعال کردن بهینه سازی باتری (Battery Optimization) و فعال سازی Auto-start برای اپلیکیشن از طریق تنظیمات سیستم است.

۱۰. حداکثر زمان مجاز اجرای یک تسک در WorkManager چقدر است؟

ورک منیجر برای کارهای پس زمینه معمول حداکثر تا ۱۰ دقیقه زمان اجرا اختصاص می دهد. اگر پردازش بیش از ۱۰ دقیقه به طول بینجامد، تسک به عنوان متوقف شده علامت گذاری می شود؛ برای پردازش های طولانی تر باید از setForegroundAsync استفاده نمایید.

امتیاز و نظرات

۰.۰ (۰ نظر)
برای ثبت دیدگاه باید وارد حساب کاربری شوید. ورود

هنوز نظری ثبت نشده است. اولین نفر باشید.