مدیریت کار

پس از تعریف Worker و WorkRequest ، آخرین مرحله، نوبت‌دهی به کار است. ساده‌ترین راه برای نوبت‌دهی کار، فراخوانی متد enqueue() از WorkManager و ارسال WorkRequest مورد نظر برای اجرا است.

کاتلین

val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)

جاوا

WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);

هنگام صف‌بندی کارها برای جلوگیری از تکرار، احتیاط کنید. به عنوان مثال، یک برنامه ممکن است سعی کند هر 24 ساعت گزارش‌های خود را در یک سرویس backend بارگذاری کند. اگر مراقب نباشید، ممکن است در نهایت یک کار را بارها در صف قرار دهید، حتی اگر آن کار فقط یک بار نیاز به اجرا داشته باشد. برای رسیدن به این هدف، می‌توانید کار را به عنوان یک کار منحصر به فرد زمان‌بندی کنید.

کار منحصر به فرد

کار منحصر به فرد یک مفهوم قدرتمند است که تضمین می‌کند شما فقط یک نمونه کار با یک نام خاص در یک زمان دارید. برخلاف شناسه‌ها، نام‌های منحصر به فرد توسط انسان قابل خواندن هستند و توسط توسعه‌دهنده مشخص می‌شوند، به جای اینکه به طور خودکار توسط WorkManager تولید شوند. برخلاف برچسب‌ها ، نام‌های منحصر به فرد فقط با یک نمونه کار مرتبط هستند.

کار منحصر به فرد را می‌توان هم برای کارهای یکباره و هم برای کارهای دوره‌ای اعمال کرد. می‌توانید با فراخوانی یکی از این متدها، بسته به اینکه آیا کار تکراری را زمان‌بندی می‌کنید یا کار یکباره، یک توالی کاری منحصر به فرد ایجاد کنید.

هر دوی این متدها ۳ آرگومان می‌پذیرند:

  • uniqueWorkName - String که برای شناسایی منحصر به فرد درخواست کار استفاده می‌شود.
  • existingWorkPolicy - یک enum که به WorkManager می‌گوید اگر زنجیره‌ای از کارها با آن نام منحصر به فرد ناتمام وجود دارد، چه کاری باید انجام دهد. برای اطلاعات بیشتر به سیاست حل تعارض مراجعه کنید.
  • work - WorkRequest برای برنامه‌ریزی.

با استفاده از کار منحصر به فرد، می‌توانیم مشکل زمان‌بندی تکراری که قبلاً به آن اشاره کردیم را برطرف کنیم.

کاتلین

val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

جاوا

PeriodicWorkRequest sendLogsWorkRequest = new
      PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
              .setConstraints(new Constraints.Builder()
              .setRequiresCharging(true)
          .build()
      )
     .build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

حال، اگر کد در حالی اجرا شود که یک کار sendLogs از قبل در صف باشد، کار موجود نگه داشته می‌شود و کار جدیدی اضافه نمی‌شود.

توالی‌های کاری منحصر به فرد همچنین می‌توانند مفید باشند اگر نیاز دارید که به تدریج یک زنجیره طولانی از وظایف ایجاد کنید. به عنوان مثال، یک برنامه ویرایش عکس ممکن است به کاربران اجازه دهد یک زنجیره طولانی از اقدامات را لغو کنند. هر یک از این عملیات لغو ممکن است مدتی طول بکشد، اما باید به ترتیب صحیح انجام شوند. در این حالت، برنامه می‌تواند یک زنجیره "لغو" ایجاد کند و هر عملیات لغو را در صورت نیاز به زنجیره اضافه کند. برای جزئیات بیشتر به بخش "زنجیره‌بندی کارها" مراجعه کنید.

سیاست حل اختلاف

هنگام زمان‌بندی کارهای منحصر به فرد، باید به WorkManager بگویید که در صورت بروز تداخل چه اقدامی انجام دهد. این کار را با ارسال یک enum هنگام قرار دادن کار در صف انجام می‌دهید.

برای کاری که فقط یک بار انجام می‌شود، شما یک ExistingWorkPolicy ارائه می‌دهید که از 4 گزینه برای مدیریت تداخل پشتیبانی می‌کند.

  • کار موجود را با کار جدید REPLACE . این گزینه کار موجود را لغو می‌کند.
  • کار فعلی را KEEP و کار جدید را نادیده بگیرید.
  • کار جدید را به انتهای کار موجود APPEND . این سیاست باعث می‌شود کار جدید شما به کار موجود زنجیر شود و پس از اتمام کار موجود اجرا شود.

کار موجود پیش‌نیاز کار جدید می‌شود. اگر کار موجود CANCELLED یا FAILED شود، کار جدید نیز CANCELLED یا FAILED می‌شود. اگر می‌خواهید کار جدید صرف نظر از وضعیت کار موجود اجرا شود، به جای آن APPEND_OR_REPLACE استفاده کنید.

  • APPEND_OR_REPLACE عملکردی مشابه APPEND دارد، با این تفاوت که به وضعیت کار پیش‌نیاز وابسته نیست. اگر کار موجود CANCELLED یا FAILED باشد، کار جدید همچنان اجرا می‌شود.

برای کار دوره‌ای، شما یک ExistingPeriodicWorkPolicy ارائه می‌دهید که از دو گزینه REPLACE و KEEP پشتیبانی می‌کند. این گزینه‌ها مانند همتایان ExistingWorkPolicy خود عمل می‌کنند.

مشاهده کار شما

در هر مرحله پس از قرار دادن کار در صف، می‌توانید وضعیت آن را با پرس‌وجو از WorkManager با name ، id یا tag مرتبط با آن بررسی کنید.

کاتلین

// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

جاوا

// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>

این کوئری یک ListenableFuture از یک شیء WorkInfo را برمی‌گرداند که شامل id کار، تگ‌های آن، State فعلی آن و هر مجموعه داده‌ی خروجی با استفاده از Result.success(outputData) است.

انواع LiveData و Flow هر یک از این متدها به شما امکان می‌دهند با ثبت یک شنونده، تغییرات WorkInfo را مشاهده کنید . برای مثال، اگر می‌خواهید وقتی کاری با موفقیت تمام شد، پیامی به کاربر نمایش داده شود، می‌توانید آن را به صورت زیر تنظیم کنید:

کاتلین

workManager.getWorkInfoByIdFlow(syncWorker.id)
          .collect{ workInfo ->
              if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
                  Snackbar.make(requireView(),
                      R.string.work_completed, Snackbar.LENGTH_SHORT)
                      .show()
              }
          }

جاوا

workManager.getWorkInfoByIdLiveData(syncWorker.id)
        .observe(getViewLifecycleOwner(), workInfo -> {
    if (workInfo.getState() != null &&
            workInfo.getState() == WorkInfo.State.SUCCEEDED) {
        Snackbar.make(requireView(),
                    R.string.work_completed, Snackbar.LENGTH_SHORT)
                .show();
   }
});

سوالات کاری پیچیده

WorkManager 2.4.0 و بالاتر از پرس‌وجوهای پیچیده برای کارهای در صف با استفاده از اشیاء WorkQuery پشتیبانی می‌کند. WorkQuery از پرس‌وجو برای کار با ترکیبی از برچسب(های) آن، وضعیت و نام کار منحصر به فرد پشتیبانی می‌کند.

مثال زیر نشان می‌دهد که چگونه می‌توانید تمام کارهایی را که با برچسب "syncTag" انجام می‌شوند و در حالت FAILED یا CANCELLED هستند و نام کار منحصر به فردی از نوع " preprocess " یا " sync " دارند، پیدا کنید.

کاتلین

val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

جاوا

WorkQuery workQuery = WorkQuery.Builder
       .fromTags(Arrays.asList("syncTag"))
       .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(Arrays.asList("preProcess", "sync")
     )
    .build();

ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);

هر کامپوننت (برچسب، حالت یا نام) در یک WorkQuery با بقیه با AND ویرایش می‌شود. هر مقدار در یک کامپوننت OR ویرایش می‌شود. برای مثال: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...) .

WorkQuery همچنین با معادل LiveData، getWorkInfosLiveData() و معادل Flow، getWorkInfosFlow() کار می‌کند.

لغو و توقف کار

اگر دیگر نیازی به اجرای کار قبلی خود که در صف انتظار قرار داده‌اید ندارید، می‌توانید درخواست لغو آن را بدهید. کار را می‌توان با name ، id یا tag مرتبط با آن لغو کرد.

کاتلین

// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")

جاوا

// by id
workManager.cancelWorkById(syncWorker.id);

// by name
workManager.cancelUniqueWork("sync");

// by tag
workManager.cancelAllWorkByTag("syncTag");

در پشت صحنه، WorkManager State کار را بررسی می‌کند. اگر کار از قبل تمام شده باشد، هیچ اتفاقی نمی‌افتد. در غیر این صورت، وضعیت کار به CANCELLED تغییر می‌کند و کار در آینده اجرا نخواهد شد. هر کار WorkRequest که به این کار وابسته باشد نیز CANCELLED خواهد شد.

کار RUNNING فراخوانی ListenableWorker.onStopped() را دریافت می‌کند. این متد را برای مدیریت هرگونه پاکسازی احتمالی، بازنویسی کنید. برای اطلاعات بیشتر به بخش «متوقف کردن یک کارگر در حال اجرا» مراجعه کنید.

یک کارگر در حال دویدن را متوقف کنید

دلایل مختلفی وجود دارد که Worker در حال اجرا شما ممکن است توسط WorkManager متوقف شود:

  • شما صریحاً درخواست لغو آن را داده‌اید (برای مثال با فراخوانی WorkManager.cancelWorkById(UUID) .
  • در مورد کار منحصر به فرد ، شما به صراحت یک WorkRequest جدید را با ExistingWorkPolicy با مقدار REPLACE در صف قرار داده‌اید. WorkRequest قدیمی بلافاصله لغو شده تلقی می‌شود.
  • محدودیت‌های کاری شما دیگر برآورده نمی‌شوند.
  • سیستم به دلیلی به برنامه شما دستور داده است که کار شما را متوقف کند. این اتفاق می‌تواند در صورتی رخ دهد که از مهلت اجرای ۱۰ دقیقه‌ای عبور کنید. کار برای تلاش مجدد در زمان دیگری برنامه‌ریزی شده است.

تحت این شرایط، کارگر شما متوقف می‌شود.

شما باید با همکاری یکدیگر هر کاری را که در حال انجام بود، متوقف کنید و منابعی را که Worker شما در اختیار دارد، آزاد کنید. به عنوان مثال، باید در این مرحله، دستگیره‌های باز پایگاه‌های داده و فایل‌ها را ببندید. دو مکانیسم برای فهمیدن زمان توقف Worker شما وجود دارد.

فراخوانی onStopped()

WorkManager به محض اینکه Worker شما متوقف شود ListenableWorker.onStopped() را فراخوانی می‌کند. این متد را برای بستن هرگونه منبعی که ممکن است در اختیار داشته باشید، بازنویسی کنید.

ویژگی isStopped()‎

می‌توانید متد ListenableWorker.isStopped() را فراخوانی کنید تا بررسی کنید که آیا worker شما قبلاً متوقف شده است یا خیر. اگر در worker خود عملیات طولانی مدت یا تکراری انجام می‌دهید، باید مرتباً این ویژگی را بررسی کنید و از آن به عنوان سیگنالی برای توقف کار در اسرع وقت استفاده کنید.

نکته: WorkManager Result که توسط Worker ای که سیگنال onStop را دریافت کرده است، تعیین می‌شود را نادیده می‌گیرد، زیرا Worker از قبل متوقف شده در نظر گرفته می‌شود.