Firebase JobDispatcher から WorkManager への移行

WorkManager は、Android で遅延可能なバックグラウンド処理をスケジューリングし実行するためのライブラリです。Firebase JobDispatcher の代わりに使用することが推奨されています。以下のガイドでは、Firebase JobDispatcher による実装を WorkManager に移行する手順を説明します。

Gradle のセットアップ

WorkManager ライブラリを Android プロジェクトにインポートするには、WorkManager のスタートガイドに記載されている依存関係を追加します。

JobService から Worker へ

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 を使用している場合、@JobResult int 型を返す onRunJob() をオーバーライドします。

主な違いは、JobService を直接使用している場合、onStartJob() はメインスレッドから呼び出され、処理をバックグラウンド スレッドにオフロードするのはアプリの責任になるという点です。これに対して、SimpleJobService を使用している場合、バックグラウンド スレッドで処理を実行するのはこのサービスの責任になります。

WorkManager にも同様のコンセプトがあります。WorkManager の基本の処理単位は ListenableWorker です。ワーカーには、ほかにも WorkerRxWorkerCoroutineWorker(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() はメインスレッドで呼び出されます。MyWorkerListenableWorker を実装して、ListenableFuture のインスタンスを返します。このインスタンスは処理の完了を非同期に通知するために使用されます。ここで独自のスレッド戦略を選択する必要があります。

この場合、ListenableFuture は最終的に ListenableWorker.Result 型を返します。これは Result.success()Result.success(Data outputData)Result.retry()Result.failure()Result.failure(Data outputData) のいずれかです。詳細については、ListenableWorker.Result のリファレンス ページをご覧ください。

onStopped() が呼び出されて、制約が満たされなくなった(ネットワークを利用できなくなったなど)か、WorkManager.cancel…() メソッドが呼び出されたために、ListenableWorker を停止する必要があることを通知します。onStopped() は、なんらかの理由で OS が処理をシャットダウンする場合にも呼び出されることがあります。

SimpleJobService を Worker に変換する

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 を使用します。

WorkManager には、OneTimeWorkRequestPeriodicWorkRequest の 2 種類の WorkRequest があります。

現在 Job.Builder.setRecurring(true) を使用している場合は、新しい PeriodicWorkRequest を作成します。それ以外の場合は、OneTimeWorkRequest を使用してください。

FirebaseJobDispatcher を使用した複雑な Job のスケジューリングは次のようになります。

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 の入力に使用できる入力データを作成する。
  • 上の FirebaseJobDispatcher で定義したものと同様の入力データと制約を使用して WorkRequest を作成する。
  • WorkRequest をキューに加える。

ワーカーの入力をセットアップする

FirebaseJobDispatcherBundle を使用して入力データを JobService に送信します。 WorkManager は代わりに 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();

ワーカーに Constraints をセットアップする

FirebaseJobDispatcherJob.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 または Periodic)の作成

OneTimeWorkRequestPeriodicWorkRequest を作成するには、OneTimeWorkRequest.BuilderPeriodicWorkRequest.Builder を使用します。

上記の Job に似た OneTimeWorkRequest を作成するには、次のようにします。

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

処理のスケジューリング

WorkerWorkRequest を定義したら、処理をスケジューリングできます。

FirebaseJobDispatcher で定義されたすべての Job には、Job の一意な識別に使用される tag があります。また、アプリが setReplaceCurrent を呼び出して、Job のこのインスタンスが Job の既存のコピーを置き換えるかどうかをスケジューラに通知する方法を提供しています。

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() の API(それぞれ OneTimeWorkRequestPeriodicWorkRequest を使用する場合)を使用して同じ結果を得られます。詳細については、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 の構成と初期化をカスタマイズできます。