長時間かかる可能性のあるデータ転送を実行する必要がある場合は、JobScheduler ジョブを作成し、ユーザーが開始したデータ転送(UIDT)ジョブとして識別できます。UIDT ジョブは、デバイス ユーザーが開始するデータ転送(リモート サーバーからのファイルのダウンロードなど)で、転送に時間がかかることが予想される場合に便利です。UIDT ジョブは Android 14(API レベル 34)で導入されました。
ユーザーが開始するデータ転送ジョブは、ユーザーによって開始されます。これらのジョブは通知を必要とし、直ちに開始されるとともに、システム条件が許せば実行時間が延長される可能性があります。ユーザーが開始するデータ転送ジョブは、同時に複数実行できます。
ユーザーが開始するジョブのスケジュールは、アプリがユーザーに表示されている状態(または許可された条件のいずれかで)設定される必要があります。システムの健全性に関わる制約にもよりますが、すべての制約が満たされていれば、ユーザーが開始するジョブは OS で実行できます。また、システムは提供された推定ペイロード サイズを使用して、ジョブの実行にかかる時間を推定することもできます。
ユーザーが開始するデータ転送ジョブのスケジュールを設定する
ユーザーが開始するデータ転送ジョブを実行するには、次の操作を行います。
アプリのマニフェストで
JobService
と関連する権限が宣言されていることを確認します。<service android:name="com.example.app.CustomTransferService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> ... </service>
また、データ転送用に
JobService
の具体的なサブクラスを定義します。Kotlin
class CustomTransferService : JobService() { ... }
Java
class CustomTransferService extends JobService() { .... }
マニフェストで
RUN_USER_INITIATED_JOBS
権限を宣言します。<manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest>
JobInfo
オブジェクトを作成するときに、setUserInitiated()
メソッドを呼び出します。(このメソッドは Android 14 以降で使用できます)。また、ジョブの作成時にsetEstimatedNetworkBytes()
を呼び出して、ペイロード サイズを見積もることもおすすめします。Kotlin
val networkRequestBuilder = NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build() val jobInfo = JobInfo.Builder(jobId, ComponentName(mContext, CustomTransferService::class.java)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequestBuilder) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024) // ... .build()
Java
NetworkRequest networkRequest = new NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build(); JobInfo jobInfo = JobInfo.Builder(jobId, new ComponentName(mContext, CustomTransferService.class)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequest) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024) // ... .build();
ジョブの実行中は、
JobService
オブジェクトでsetNotification()
を呼び出します。setNotification()
を呼び出すと、タスク マネージャーとステータスバーの通知領域の両方で、ジョブが実行されていることをユーザーに知らせます。実行が完了したら、
jobFinished()
を呼び出して、ジョブが完了したこと、またはジョブのスケジュールを再設定する必要があることをシステムに通知します。Kotlin
class CustomTransferService: JobService() { private val scope = CoroutineScope(Dispatchers.IO) @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) override fun onStartJob(params: JobParameters): Boolean { val notification = Notification.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build() setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. scope.launch { doDownload(params) } return true } private suspend fun doDownload(params: JobParameters) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false) } // Called when the system stops the job. override fun onStopJob(params: JobParameters?): Boolean { // Asynchronously record job-related data, such as the // stop reason. return true // or return false if job should end entirely } }
Java
class CustomTransferService extends JobService{ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Override public boolean onStartJob(JobParameters params) { Notification notification = Notification.Builder(getBaseContext(), NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build(); setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. new Thread(() -> doDownload(params)).start(); return true; } private void doDownload(JobParameters params) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false); } // Called when the system stops the job. @Override public boolean onStopJob(JobParameters params) { // Asynchronously record job-related data, such as the // stop reason. return true; // or return false if job should end entirely } }
通知を定期的に更新して、ジョブのステータスと進行状況をユーザーに知らせます。ジョブのスケジュールを設定する前に転送サイズを特定できない場合や、推定転送サイズを更新する必要がある場合は、新しい API である
updateEstimatedNetworkBytes()
を使用して、判明した転送サイズを更新します。
推奨事項
UIDT ジョブを効果的に実行するには、次の操作を行います。
ネットワーク制約とジョブ実行制約を明確に定義して、ジョブの実行タイミングを指定します。
onStartJob()
でタスクを非同期に実行します。たとえば、コルーチンを使用して実行できます。タスクを非同期で実行しないと、メインスレッドで処理が実行され、メインスレッドがブロックされる可能性があります。これにより、ANR が発生する可能性があります。ジョブが不要に長く実行されないように、転送が成功したか失敗したかにかかわらず、転送が完了したら
jobFinished()
を呼び出します。これにより、ジョブが不必要に長く実行されるのを防ぐことができます。ジョブが停止した理由を検出するには、onStopJob()
コールバック メソッドを実装し、JobParameters.getStopReason()
を呼び出します。
下位互換性
現在、UIDT ジョブをサポートする Jetpack ライブラリはありません。そのため、Android 14 以降で実行されていることを確認するコードで変更をゲートすることをおすすめします。Android の古いバージョンでは、フォールバック アプローチとして WorkManager のフォアグラウンド サービス実装を使用できます。
適切なシステム バージョンを確認するコードの例を次に示します。
Kotlin
fun beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context) } else { scheduleDownloadUIDTJob(context) } } private fun scheduleDownloadUIDTJob(context: Context) { // build jobInfo val jobScheduler: JobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(jobInfo) } private fun scheduleDownloadFGSWorker(context: Context) { val myWorkRequest = OneTimeWorkRequest.from(DownloadWorker::class.java) WorkManager.getInstance(context).enqueue(myWorkRequest) }
Java
public void beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context); } else { scheduleDownloadUIDTJob(context); } } private void scheduleDownloadUIDTJob(Context context) { // build jobInfo JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(jobInfo); } private void scheduleDownloadFGSWorker(Context context) { OneTimeWorkRequest myWorkRequest = OneTimeWorkRequest.from(DownloadWorker.class); WorkManager.getInstance(context).enqueue(myWorkRequest) }
UIDT ジョブを停止する
ユーザーとシステムの両方が、ユーザーが開始した転送ジョブを停止できます。
ユーザーがタスク マネージャーから停止
ユーザーは、タスク マネージャーに表示されるユーザーが開始したデータ転送ジョブを停止できます。
ユーザーが [停止] を押すと、以下の処理が行われます。
- アプリのプロセス(実行中の他のすべてのジョブまたはフォアグラウンド サービスを含む)をすぐに終了します。
- 実行中のジョブについて
onStopJob()
を呼び出しません。 - ユーザーに表示されるジョブのスケジュール変更が行われないようにします。
そのため、ジョブを正常に停止してスケジュールを変更できるように、ジョブに関して送信された通知でコントロールを提供することをおすすめします。
なお、特別な状況では、タスク マネージャーのジョブの横に [停止] ボタンが表示されないか、タスク マネージャーにジョブがまったく表示されません。
システムによる停止
与常规作业不同,用户发起的数据传输作业不受应用待机模式存储分区配额的影响。但是,如果出现以下任一情况,系统仍会停止作业:
- 不再满足开发者定义的约束条件。
- 系统确定该作业的运行时间超出了完成数据传输任务所需的时间。
- 系统需要优先考虑系统运行状况,并因发热程度上升而停止作业。
- 应用进程因设备内存不足而被终止。
如果系统因设备内存不足以外的原因停止作业,系统会调用 onStopJob()
,并在系统认为最佳的时间重试作业。确保您的应用可以保留数据传输状态(即使未调用 onStopJob()
),并且您的应用可以在再次调用 onStartJob()
时恢复此状态。
ユーザーが開始するデータ転送ジョブのスケジュールを設定できる条件
アプリがユーザー開始型データ転送ジョブを開始できるのは、アプリが可視ウィンドウ内にある場合、または次の特定の条件を満たしている場合のみです。
- アプリがバックグラウンドからアクティビティを開始できる場合、ユーザー開始型データ転送ジョブもバックグラウンドから開始できます。
- アプリのアクティビティが [最近] 画面の既存タスクのバックスタックにあるだけでは、ユーザー開始型データ転送ジョブを実行することはできません。
必要な条件が満たされていないタイミングでジョブの実行がスケジュールされている場合、ジョブは失敗し、RESULT_FAILURE
エラーコードが返されます。
ユーザーが開始するデータ転送ジョブで許可される制約
To support jobs running at optimal points, Android offers the ability to assign constraints to each job type. These constraints are available as of Android 13.
Note: The following table only compares the constraints that vary between each job type. See JobScheduler developer page or work constraints for all constraints.
The following table shows the different job types that support a given job constraint, as well as the set of job constraints that WorkManager supports. Use the search bar before the table to filter the table by the name of a job constraint method.
These are the constraints allowed with user-initiated data transfer jobs:
setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)
setClipData()
setEstimatedNetworkBytes()
setMinimumNetworkChunkBytes()
setPersisted()
setNamespace()
setRequiredNetwork()
setRequiredNetworkType()
setRequiresBatteryNotLow()
setRequiresCharging()
setRequiresStorageNotLow()
テスト
アプリのジョブを手動でテストする方法のリストを次に示します。
- ジョブ ID を取得するには、ビルドされるジョブで定義されている値を取得します。
ジョブをすぐに実行する場合、または停止したジョブを再試行する場合は、ターミナル ウィンドウで次のコマンドを実行します。
adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
システムの健全性や割り当て不足の状態により、ジョブが強制的に停止される場合をシミュレートするには、ターミナル ウィンドウで次のコマンドを実行します。
adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID
関連ドキュメント
参考情報
ユーザーが開始したデータ転送の詳細については、次のリソースをご覧ください。
- UIDT 統合に関する事例紹介: Google マップはユーザーが開始したデータ転送 API を使用してダウンロードの信頼性を 10% 向上させました