انتقال از Firebase JobDispatcher به WorkManager

WorkManager کتابخانه‌ای برای زمان‌بندی و اجرای کارهای پس‌زمینه‌ای قابل‌تعویق در Android است. این ابزار جایگزین توصیه‌شده برای Firebase JobDispatcher است. راهنمای زیر شما را در فرایند انتقال پیاده‌سازی Firebase JobDispatcher به WorkManager همراهی می‌کند.

راه‌اندازی Gradle

برای وارد کردن کتابخانه WorkManager به پروژه Android خود، وابستگی‌های فهرست‌شده در شروع به کار با WorkManager را اضافه کنید.

از JobService به عاملان

FirebaseJobDispatcher از زیرکلاس JobService به‌عنوان نقطه ورودی برای تعریف کاری که باید انجام شود استفاده می‌کند. ممکن است مستقیماً از JobService استفاده کنید یا از SimpleJobService استفاده کنید.

JobService چیزی شبیه این خواهد بود:

کاتلین

import com.firebase.jobdispatcher.JobParameters
import com.firebase.jobdispatcher.JobService

class MyJobService : JobService() {
    override fun onStartJob(job: JobParameters): Boolean {
        // Do some work here
        return false // Answers the question: "Is there still work going on?"
    }
    override fun onStopJob(job: JobParameters): Boolean {
        return false // Answers the question: "Should this job be retried?"
    }
}

جاوا

import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;

public class MyJobService extends JobService {
    @Override
    public boolean onStartJob(JobParameters job) {
        // Do some work here

        return false; // Answers the question: "Is there still work going on?"
    }

    @Override
    public boolean onStopJob(JobParameters job) {
        return false; // Answers the question: "Should this job be retried?"
    }
}

اگر از SimpleJobService استفاده می‌کنید، onRunJob() را لغو کرده‌اید، که نوع @JobResult int را برمی‌گرداند.

تفاوت اصلی این است که وقتی مستقیماً از JobService استفاده می‌کنید، onStartJob() در رشته اصلی فراخوانی می‌شود و مسئولیت انتقال کار به رشته پس‌زمینه برعهده برنامه است. ازسوی دیگر، اگر از SimpleJobService استفاده می‌کنید، آن سرویس مسئول اجرای کار شما در رشته پس‌زمینه است.

WorkManager مفاهیم مشابهی دارد. واحد اصلی کار در WorkManager ListenableWorker است. زیرنوع‌های مفید دیگری نیز برای Worker وجود دارد، مثل Worker، RxWorker، و CoroutineWorker (هنگام استفاده از روال‌های هم‌زمان Kotlin).

JobService به ListenableWorker نگاشت می‌شود

اگر مستقیماً از JobService استفاده می‌کنید، کارگری که به آن نگاشت می‌شود ListenableWorker است. اگر از SimpleJobService استفاده می‌کنید، باید درعوض از Worker استفاده کنید.

بیایید از مثال بالا (MyJobService) استفاده کنیم و ببینیم چگونه می‌توانیم آن را به ListenableWorker تبدیل کنیم.

کاتلین

import android.content.Context
import androidx.work.ListenableWorker
import androidx.work.ListenableWorker.Result
import androidx.work.WorkerParameters
import com.google.common.util.concurrent.ListenableFuture

class MyWorker(appContext: Context, params: WorkerParameters) :
    ListenableWorker(appContext, params) {

    override fun startWork(): ListenableFuture<ListenableWorker.Result> {
        // Do your work here.
        TODO("Return a ListenableFuture<Result>")
    }

    override fun onStopped() {
        // Cleanup because you are being stopped.
    }
}

جاوا

import android.content.Context;
import androidx.work.ListenableWorker;
import androidx.work.ListenableWorker.Result;
import androidx.work.WorkerParameters;
import com.google.common.util.concurrent.ListenableFuture;

class MyWorker extends ListenableWorker {

  public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) {
    super(appContext, params);
  }

  @Override
  public ListenableFuture<ListenableWorker.Result> startWork() {
    // Do your work here.
    Data input = getInputData();

    // Return a ListenableFuture<>
  }

  @Override
  public void onStopped() {
    // Cleanup because you are being stopped.
  }
}

واحد اصلی کار در WorkManager عبارت است از ListenableWorker. درست مثل JobService.onStartJob()، startWork() در رشته اصلی فراخوانی می‌شود. در اینجا MyWorker ListenableWorker را پیاده‌سازی می‌کند و نمونه‌ای از ListenableFuture را برمی‌گرداند، که برای علامت‌دهی تکمیل کار به‌صورت ناهمگام استفاده می‌شود. باید استراتژی رشته‌بندی خودتان را در اینجا انتخاب کنید.

ListenableFuture در اینجا درنهایت نوع ListenableWorker.Result را برمی‌گرداند که می‌تواند یکی از Result.success()،‏ Result.success(Data outputData)، Result.retry()،‏ Result.failure()، یا Result.failure(Data outputData) باشد. برای اطلاعات بیشتر، صفحه مرجع ListenableWorker.Result را ببینید.

onStopped() فراخوانده می‌شود تا نشان دهد که ListenableWorker باید متوقف شود، یا به این دلیل که محدودیت‌ها دیگر برآورده نمی‌شوند (برای مثال، به این دلیل که شبکه دیگر دردسترس نیست)، یا به این دلیل که روش WorkManager.cancel…() فراخوانده شده است. اگر سیستم‌عامل تصمیم بگیرد کار شما را به دلیلی متوقف کند، ممکن است onStopped() نیز فراخوانی شود.

SimpleJobService به Worker نگاشت می‌شود

هنگام استفاده از SimpleJobService، کارگر بالا به‌صورت زیر خواهد بود:

کاتلین

import android.content.Context;
import androidx.work.Data;
import androidx.work.ListenableWorker.Result;
import androidx.work.Worker;
import androidx.work.WorkerParameters;


class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        TODO("Return a Result")
    }

    override fun onStopped() {
        super.onStopped()
        TODO("Cleanup, because you are being stopped")
    }
}

جاوا

import android.content.Context;
import androidx.work.Data;
import androidx.work.ListenableWorker.Result;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

class MyWorker extends Worker {

  public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) {
    super(appContext, params);
  }

  @Override
  public Result doWork() {
    // Do your work here.
    Data input = getInputData();

    // Return a ListenableWorker.Result
    Data outputData = new Data.Builder()
        .putString(“Key”, “value”)
        .build();
    return Result.success(outputData);
  }

  @Override
  public void onStopped() {
    // Cleanup because you are being stopped.
  }
}

در اینجا doWork() نمونه‌ای از ListenableWorker.Result را برمی‌گرداند تا تکمیل کار را به‌صورت همگام سیگنال دهد. این شبیه به SimpleJobService است که کارها را در رشته پس‌زمینه زمان‌بندی می‌کند.

JobBuilder به WorkRequest نگاشت می‌شود

‫FirebaseJobBuilder از Job.Builder برای نمایش فراداده Job استفاده می‌کند. WorkManager از WorkRequest برای پر کردن این نقش استفاده می‌کند.

‫WorkManager دو نوع WorkRequest دارد: OneTimeWorkRequest و PeriodicWorkRequest.

اگر درحال‌حاضر از Job.Builder.setRecurring(true) استفاده می‌کنید، باید PeriodicWorkRequest جدیدی ایجاد کنید. درغیراین‌صورت، باید از OneTimeWorkRequest استفاده کنید.

بیایید ببینیم زمان‌بندی کردن Job پیچیده با FirebaseJobDispatcher چگونه است:

کاتلین

val input: Bundle = Bundle().apply {
    putString("some_key", "some_value")
}

val job = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyService::class.java)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // one-off job
    .setRecurring(false)
    // don't persist past a device reboot
    .setLifetime(Lifetime.UNTIL_NEXT_BOOT)
    // start between 0 and 60 seconds from now
    .setTrigger(Trigger.executionWindow(0, 60))
    // don't overwrite an existing job with the same tag
    .setReplaceCurrent(false)
    // retry with exponential backoff
    .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)

    .setConstraints(
        // only run on an unmetered network
        Constraint.ON_UNMETERED_NETWORK,
        // // only run when the device is charging
        Constraint.DEVICE_CHARGING
    )
    .setExtras(input)
    .build()

dispatcher.mustSchedule(job)

جاوا

Bundle input = new Bundle();
input.putString("some_key", "some_value");

Job myJob = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyJobService.class)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // one-off job
    .setRecurring(false)
    // don't persist past a device reboot
    .setLifetime(Lifetime.UNTIL_NEXT_BOOT)
    // start between 0 and 60 seconds from now
    .setTrigger(Trigger.executionWindow(0, 60))
    // don't overwrite an existing job with the same tag
    .setReplaceCurrent(false)
    // retry with exponential backoff
    .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
    // constraints that need to be satisfied for the job to run
    .setConstraints(
        // only run on an unmetered network
        Constraint.ON_UNMETERED_NETWORK,
        // only run when the device is charging
        Constraint.DEVICE_CHARGING
    )
    .setExtras(input)
    .build();

dispatcher.mustSchedule(myJob);

برای دستیابی به همین نتیجه با WorkManager باید:

  • داده‌های ورودی را که می‌توانند به‌عنوان ورودی برای Worker استفاده شوند بسازید.
  • یک WorkRequest با داده‌های ورودی و محدودیت‌های مشابه با آنچه در بالا برای FirebaseJobDispatcher تعریف شده است بسازید.
  • WorkRequest را در صف قرار دهید.

درحال راه‌اندازی ورودی‌ها برای Worker

FirebaseJobDispatcher از Bundle برای ارسال داده‌های ورودی به JobService استفاده می‌کند. WorkManager درعوض از Data استفاده می‌کند. بنابراین این‌گونه می‌شود:

کاتلین

import androidx.work.workDataOf
val data = workDataOf("some_key" to "some_val")

جاوا

import androidx.work.Data;
Data input = new Data.Builder()
    .putString("some_key", "some_value")
    .build();

تنظیم محدودیت‌ها برای Worker

FirebaseJobDispatcher از Job.Builder.setConstaints(...) برای تنظیم محدودیت‌ها در مشاغل استفاده می‌کند. WorkManager درعوض از Constraints استفاده می‌کند.

کاتلین

import androidx.work.*

val constraints: Constraints = Constraints.Builder().apply {
    setRequiredNetworkType(NetworkType.CONNECTED)
    setRequiresCharging(true)
}.build()

جاوا

import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;

Constraints constraints = new Constraints.Builder()
    // The Worker needs Network connectivity
    .setRequiredNetworkType(NetworkType.CONNECTED)
    // Needs the device to be charging
    .setRequiresCharging(true)
    .build();

ایجاد WorkRequest (یک‌باره یا دوره‌ای)

برای ایجاد OneTimeWorkRequest و PeriodicWorkRequest باید از OneTimeWorkRequest.Builder و PeriodicWorkRequest.Builder استفاده کنید.

برای ایجاد OneTimeWorkRequest که مشابه Job بالا است، باید کارهای زیر را انجام دهید:

کاتلین

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

val constraints: Constraints = TODO("Define constraints as above")
val request: OneTimeWorkRequest =
     // Tell which work to execute
     OneTimeWorkRequestBuilder<MyWorker>()
         // Sets the input data for the ListenableWorker
        .setInputData(input)
        // If you want to delay the start of work by 60 seconds
        .setInitialDelay(60, TimeUnit.SECONDS)
        // Set a backoff criteria to be used when retry-ing
        .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS)
        // Set additional constraints
        .setConstraints(constraints)
        .build()

جاوا

import androidx.work.BackoffCriteria;
import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OneTimeWorkRequest.Builder;
import androidx.work.Data;

// Define constraints (as above)
Constraints constraints = ...
OneTimeWorkRequest request =
    // Tell which work to execute
    new OneTimeWorkRequest.Builder(MyWorker.class)
        // Sets the input data for the ListenableWorker
        .setInputData(inputData)
        // If you want to delay the start of work by 60 seconds
        .setInitialDelay(60, TimeUnit.SECONDS)
        // Set a backoff criteria to be used when retry-ing
        .setBackoffCriteria(BackoffCriteria.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS)
        // Set additional constraints
        .setConstraints(constraints)
        .build();

تفاوت کلیدی در اینجا این است که کارهای WorkManager همیشه به‌طور خودکار در سراسر راه‌اندازی مجدد دستگاه حفظ می‌شوند.

اگر می‌خواهید PeriodicWorkRequest ایجاد کنید، باید کاری شبیه به این انجام دهید:

کاتلین

val constraints: Constraints = TODO("Define constraints as above")
val request: PeriodicWorkRequest =
PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
    // Sets the input data for the ListenableWorker
    .setInputData(input)
    // Other setters
    .build()

جاوا

import androidx.work.BackoffCriteria;
import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.PeriodicWorkRequest.Builder;
import androidx.work.Data;

// Define constraints (as above)
Constraints constraints = ...

PeriodicWorkRequest request =
    // Executes MyWorker every 15 minutes
    new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.MINUTES)
        // Sets the input data for the ListenableWorker
        .setInputData(input)
        . // other setters (as above)
        .build();

زمان‌بندی کردن کار

اکنون که Worker و WorkRequest را تعریف کرده‌اید، آماده‌اید تا کار را زمان‌بندی کنید.

هر Job تعریف‌شده با FirebaseJobDispatcher دارای یک tag بود که برای شناسایی منحصربه‌فرد یک Job استفاده می‌شد. همچنین راهی برای برنامه فراهم می‌کند تا به زمان‌بند بگوید که آیا این نمونه از Job قرار است جایگزین نسخه موجود Job شود یا نه، با فراخوانی setReplaceCurrent.

کاتلین

val job = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyService::class.java)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // don't overwrite an existing job with the same tag
    .setRecurring(false)
    // Other setters...
    .build()

جاوا

Job myJob = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyJobService.class)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // don't overwrite an existing job with the same tag
    .setReplaceCurrent(false)
    // other setters
    // ...

dispatcher.mustSchedule(myJob);

هنگام استفاده از WorkManager، می‌توانید بااستفاده از میاناهای برنامه‌سازی کاربردی enqueueUniqueWork() و enqueueUniquePeriodicWork() (به‌ترتیب هنگام استفاده از OneTimeWorkRequest و PeriodicWorkRequest) به همان نتیجه برسید. برای اطلاعات بیشتر، صفحه‌های مرجع WorkManager.enqueueUniqueWork() و WorkManager.enqueueUniquePeriodicWork() را ببینید.

این مورد چیزی شبیه به این خواهد بود:

کاتلین

import androidx.work.*

val request: OneTimeWorkRequest = TODO("A WorkRequest")
WorkManager.getInstance(myContext)
    .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, request)

جاوا

import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

OneTimeWorkRequest workRequest = // a WorkRequest;
WorkManager.getInstance(myContext)
    // Use ExistingWorkPolicy.REPLACE to cancel and delete any existing pending
    // (uncompleted) work with the same unique name. Then, insert the newly-specified
    // work.
    .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, workRequest);

درحال لغو کردن کار

با FirebaseJobDispatcher می‌توانید کار را بااستفاده از موارد زیر لغو کنید:

کاتلین

dispatcher.cancel("my-unique-tag")

جاوا

dispatcher.cancel("my-unique-tag");

هنگام استفاده از WorkManager می‌توانید از این موارد استفاده کنید:

کاتلین

import androidx.work.WorkManager
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name")

جاوا

import androidx.work.WorkManager;
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name");

درحال مقداردهی اولیه WorkManager

WorkManager معمولاً بااستفاده از ContentProvider خودش را مقداردهی اولیه می‌کند. اگر به کنترل بیشتری بر نحوه سازمان‌دهی و زمان‌بندی کار توسط WorkManager نیاز دارید، می‌توانید پیکربندی و مقداردهی اولیه WorkManager را سفارشی‌سازی کنید.