WorkManager には、長時間実行ワーカーに対するサポートが組み込まれています。これにより、このワーカーが実行されている間は可能な限りプロセスを維持するよう WorkManager から OS に通知できます。これらのワーカーは、10 分以上実行できます。この新機能のユースケースの例には、一括アップロードまたはダウンロード(非チャンク型のみ)、ML モデルのローカル処理、アプリユーザーにとって重要なタスクなどが含まれます。
内部的には、WorkManager がフォアグラウンド サービスを管理、実行して WorkRequest
を実行し、同時に設定可能な通知を表示します。
現在、ListenableWorker
は setForegroundAsync()
API をサポートしており、CoroutineWorker
は、サスペンドする setForeground()
API をサポートしています。デベロッパーはこれらの API を使用して、WorkRequest
が重要なタスクか(ユーザーの観点から)、長時間実行ワーカーかを指定できます。
2.3.0-alpha03
以降の WorkManager では、PendingIntent
の作成も可能になりました。createCancelPendingIntent()
API を使用すれば、新しい Android コンポーネントを登録せずにワーカーをキャンセルできます。これを setForegroundAsync()
または setForeground()
API と組み合わせて、Worker
をキャンセルする通知アクションを追加するために使用すれば特に便利です。
長時間実行ワーカーの作成と管理
Kotlin あるいは Java のどちらでコーディングするかによって、アプローチが多少異なります。
Kotlin
Kotlin を使用している場合、CoroutineWorker
を使用する必要があります。setForegroundAsync()
を使用する代わりに、そのメソッドのサスペンド バージョン setForeground()
を使用できます。
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL)
?: return Result.failure()
val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
?: return Result.failure()
// Mark the Worker as important
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputFile)
return Result.success()
}
private fun download(inputUrl: String, outputFile: String) {
// Downloads a file and updates bytes read
// Calls setForeground() periodically when it needs to update
// the ongoing Notification
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = applicationContext.getString(R.string.notification_channel_id)
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(notificationId, notification)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
// Create a Notification channel
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
}
}
Java
ListenableWorker
または Worker
を使用している場合、ListenableFuture<Void>
を返す setForegroundAsync()
API を呼び出すことができます。setForegroundAsync()
を呼び出すと、進行中の Notification
も更新できます。
以下に、ファイルをダウンロードする長時間実行ワーカーの簡単な例を示します。このワーカーはダウンロードの進行状況を追跡し、その状況を示す Notification
を継続的に更新します。
public class DownloadWorker extends Worker {
private static final String KEY_INPUT_URL = "KEY_INPUT_URL";
private static final String KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME";
private NotificationManager notificationManager;
public DownloadWorker(
@NonNull Context context,
@NonNull WorkerParameters parameters) {
super(context, parameters);
notificationManager = (NotificationManager)
context.getSystemService(NOTIFICATION_SERVICE);
}
@NonNull
@Override
public Result doWork() {
Data inputData = getInputData();
String inputUrl = inputData.getString(KEY_INPUT_URL);
String outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME);
// Mark the Worker as important
String progress = "Starting Download";
setForegroundAsync(createForegroundInfo(progress));
download(inputUrl, outputFile);
return Result.success();
}
private void download(String inputUrl, String outputFile) {
// Downloads a file and updates bytes read
// Calls setForegroundAsync(createForegroundInfo(myProgress))
// periodically when it needs to update the ongoing Notification.
}
@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String progress) {
// Build a notification using bytesRead and contentLength
Context context = getApplicationContext();
String id = context.getString(R.string.notification_channel_id);
String title = context.getString(R.string.notification_title);
String cancel = context.getString(R.string.cancel_download);
// This PendingIntent can be used to cancel the worker
PendingIntent intent = WorkManager.getInstance(context)
.createCancelPendingIntent(getId());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel();
}
Notification notification = new NotificationCompat.Builder(context, id)
.setContentTitle(title)
.setTicker(title)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build();
return new ForegroundInfo(notificationId, notification);
}
@RequiresApi(Build.VERSION_CODES.O)
private void createChannel() {
// Create a Notification channel
}
}
長時間実行ワーカーにフォアグラウンド サービス タイプを追加する
Android 14(API レベル 34)以降をターゲットとするアプリの場合、
長時間実行されるすべてのワーカーのフォアグラウンド サービス タイプ。
アプリが Android 10(API レベル 29)以降をターゲットとしており、
位置情報へのアクセスを必要とする長時間実行ワーカーの場合、ワーカーが
フォアグラウンド サービスのタイプ location
を使用している。
アプリが Android 11(API レベル 30)以降をターゲットとしている場合
カメラやマイクへのアクセスを必要とする長時間実行ワーカーが含まれている場合
camera
または microphone
のフォアグラウンドを宣言する
サービスタイプを指定します。
これらのフォアグラウンド サービス タイプを追加するには、以下のセクションで説明する手順を実行します。
アプリ マニフェストでフォアグラウンド サービス タイプを宣言する
アプリのマニフェストで、ワーカーのフォアグラウンド サービス タイプを宣言します。次の例では、ワーカーは位置情報とマイクへのアクセスを必要とします。
<service android:name="androidx.work.impl.foreground.SystemForegroundService" android:foregroundServiceType="location|microphone" tools:node="merge" />
フォアグラウンド サービス タイプをランタイムに指定する
setForeground()
または setForegroundAsync()
を呼び出すときは、必ず
フォアグラウンド サービスのタイプ。
Kotlin
private fun createForegroundInfo(progress: String): ForegroundInfo { // ... return ForegroundInfo(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_LOCATION or FOREGROUND_SERVICE_TYPE_MICROPHONE) }
Java
@NonNull private ForegroundInfo createForegroundInfo(@NonNull String progress) { // Build a notification... Notification notification = ...; return new ForegroundInfo(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MICROPHONE); }