從 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。另外還有其他實用的工作站子類型,例如 Worker、RxWorker 和 CoroutineWorker (使用 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 類型:OneTimeWorkRequest 和 PeriodicWorkRequest。

如果您目前使用的是 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 (「一次性」或「定期」)

如要建立 OneTimeWorkRequest 和 PeriodicWorkRequest,請使用 OneTimeWorkRequest.Builder 和 PeriodicWorkRequest.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();

安排工作時間

定義 Worker 和 WorkRequest 後,您現在已可安排工作時間。

每個使用 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 達到相同的結果 (分別使用 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 設定和初始化。