نقل البيانات من Firebase JobDispatcher إلى WorkManager

WorkManager هي مكتبة لجدولة المهام وتنفيذها في الخلفية في نظام Android. وهذا هو البديل المُقترَح لمنصة Firebase JobDispatcher. سيرشدك الدليل التالي خلال عملية نقل بيانات تنفيذ JobDispatcher في Firebase إلى WorkManager.

إعداد Gradle

لاستيراد مكتبة WorkManager إلى مشروع Android الخاص بك، أضِف التبعيات المُدرجة في بدء استخدام WorkManager.

من JobService إلى العاملين

تستخدم السمة FirebaseJobDispatcher فئة فرعية من JobService كنقطة دخول لتحديد العمل الذي يجب إنجازه. من المحتمل أنّك تستخدم JobService مباشرةً، أو تستخدم SimpleJobService.

سيبدو JobService على النحو التالي:

Kotlin

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?"
    }
}

Java

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 وRxWorker وCoroutineWorker (عند استخدام الكوروتينات بلغة Kotlin).

تعيين JobService إلى ListenableWorker

إذا كنت تستخدم JobService مباشرةً، يكون العامل الذي يتم الربط إليه هو ListenableWorker. في حال استخدام SimpleJobService، يجب استخدام Worker بدلاً منها.

لنستخدم المثال أعلاه (MyJobService) وننظر في كيفية تحويله إلى ListenableWorker.

Kotlin

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

Java

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 بتعيين عامل

عند استخدام SimpleJobService، سيظهر العامل أعلاه على النحو التالي:

Kotlin

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

Java

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 لشغل هذا الدور.

هناك نوعان من WorkRequest في WorkManager: OneTimeWorkRequest و PeriodicWorkRequest.

إذا كنت تستخدم Job.Builder.setRecurring(true) حاليًا، عليك إنشاء PeriodicWorkRequest جديد. وبخلاف ذلك، يجب استخدام OneTimeWorkRequest.

لنلقِ نظرة على الشكل المحتمل لجدولة Job معقّدة باستخدام FirebaseJobDispatcher:

Kotlin

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)

Java

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 إلى قائمة الانتظار.

إعداد الإدخالات للعامل

FirebaseJobDispatcher يستخدم Bundle لإرسال بيانات الإدخال إلى JobService. يستخدم "مدير العمل" Data بدلاً من ذلك. وبالتالي يصبح ذلك:

Kotlin

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

Java

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

وضع القيود على العامل

يستخدم FirebaseJobDispatcher Job.Builder.setConstaints(...) لفرض قيود على الوظائف. يستخدم WorkManager Constraints بدلاً من ذلك.

Kotlin

import androidx.work.*

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

Java

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 (OneTime أو بشكل دوري)

لإنشاء OneTimeWorkRequest وPeriodicWorkRequest، عليك استخدام OneTimeWorkRequest.Builder وPeriodicWorkRequest.Builder.

لإنشاء OneTimeWorkRequest بطريقة مشابهة لما يلي Job أعلاه، عليك اتّباع الخطوات التالية:

Kotlin

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

Java

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، يمكنك تنفيذ ما يلي:

Kotlin

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

Java

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.

Kotlin

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

Java

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

سيبدو هذا على النحو التالي:

Kotlin

import androidx.work.*

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

Java

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"، يمكنك إلغاء العمل باستخدام:

Kotlin

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

Java

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

عند استخدام WorkManager، يمكنك استخدام ما يلي:

Kotlin

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

Java

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

إعداد WorkManager

يضبط تطبيق WorkManager عادةً نفسه باستخدام ContentProvider. إذا كنت بحاجة إلى مزيد من التحكّم في طريقة تنظيم WorkManager وجدولته الزمنية، يمكنك تخصيص إعدادات WorkManager وإعدادها.