從 Firebase JobDispatcher 遷移至 WorkManager

WorkManager 程式庫是用於在 Android 中排程及執行可延後的背景作業。建議您針對 Firebase JobDispatcher 進行取代。下列指南將逐步說明將 Firebase JobDispatcher 執行遷移至 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。另外還有其他實用的工作站子類型,例如 WorkerRxWorkerCoroutineWorker (使用 Kotlin 協同程式時)。

JobService 對應至 SenseableWorker

如果您直接使用 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 的參考頁面。

因為無法符合限制條件 (例如網路已無法使用) 或是因為呼叫 WorkManager.cancel…() 方法,則呼叫 onStopped() 以表示 ListenableWorker 必須停止。如果作業系統因故決定關閉您的作業,則也可能呼叫 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 來填入此角色。

WorkManager 提供兩種 WorkRequest 類型:OneTimeWorkRequestPeriodicWorkRequest

如果您目前使用的是 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 加入佇列。

設定工作站的輸入來源

FirebaseJobDispatcher 會使用 Bundle 將輸入資料傳送至 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();

設定工作站的限制條件

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 (「一次性」或「定期」)

如要建立 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 都有一個 tag,用於「識別」 Job。此 API 還可讓應用程式告知排程器,如果 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() 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 設定和初始化