مدیریت کار

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

کاتلین

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

جاوا

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

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

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

کار یکتا مفهوم قدرتمندی است که تضمین می‌کند در هر زمان فقط یک نمونه کار با نام خاصی داشته باشید. برخلاف شناسه‌ها، نام‌های یکتا قابل‌خواندن برای انسان هستند و به‌جای اینکه به‌طور خودکار توسط 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 ارائه می‌دهید که از ۴ گزینه برای رسیدگی به تداخل پشتیبانی می‌کند.

  • 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 نسخه ۲.۴.۰ و بالاتر از پُرسمان پیچیده برای کارهای در صف بااستفاده از اشیاء 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 درحال اجرا

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

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

در این شرایط، Worker شما متوقف می‌شود.

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

فراخوانی()onStopped

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

خصوصیت isStopped()‎

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

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