フォアグラウンド サービスをユーザーが開始するデータ転送ジョブに移行する

Android 14 applies strict rules on when apps are allowed to use foreground services.

Also in Android 14 we are introducing a new API to specify that a job must be a user-initiated data transfer job. This API is helpful for use cases that require longer-duration, user-initiated transferring of data, such as downloading a file from a remote server. These types of tasks should use a user-initiated data transfer job.

User-initiated data transfer jobs are started by the user. These jobs require a notification, start immediately, and may be able to run for an extended period of time as system conditions allow. You can run several user-initiated data transfer jobs concurrently.

User initiated jobs must be scheduled while the application is visible to the user (or in one of the allowed conditions). After all constraints are met, user initiated jobs can be executed by the OS, subject to system health restrictions. The system may also use the provided estimated payload size to determine how long the job executes.

ユーザーが開始するデータ転送ジョブの権限

ユーザーが開始するデータ転送ジョブを実行するには、新しい権限 RUN_USER_INITIATED_JOBS が必要です。この権限は自動的に付与されます。 アプリ マニフェストで権限を宣言しないと、SecurityException がスローされます。

ユーザーが開始するデータ転送ジョブをスケジュールするプロセス

ユーザーが開始するジョブを実行するには、次の手順を行います。

  1. JobScheduler を使用して API を初めて宣言する場合は、マニフェストで JobService と関連する権限を宣言します。また、データ転送用に JobService の具象サブクラスを定義します。

    <service android:name="com.example.app.CustomTransferService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false">
            ...
    </service>
    
    class CustomTransferService : JobService() {
      ...
    }
    
  2. マニフェストで RUN_USER_INITIATED_JOBS 権限を宣言します。

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. JobInfo オブジェクトを作成するときに、新しい setUserInitiated() メソッドを呼び出します。また、ジョブの作成中に setEstimatedNetworkBytes() を呼び出して、ペイロード サイズの見積もりを提示することをおすすめします。

    val networkRequestBuilder = NetworkRequest.Builder()
            .addCapability(NET_CAPABILITY_INTERNET)
            .addCapability(NET_CAPABILITY_NOT_METERED)
            // Add or remove capabilities based on your requirements
            .build()
    
    val jobInfo = JobInfo.Builder()
            // ...
            .setUserInitiated(true)
            .setRequiredNetwork(networkRequestBuilder.build())
            .setEstimatedNetworkBytes(1024 * 1024 * 1024)
            // ...
            .build()
    
  4. 転送の開始前に、アプリケーションが表示されている間、または許可された条件のリストでジョブをスケジュールします。

    val jobScheduler: JobScheduler =
        context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    jobScheduler.schedule(jobInfo)
    
  5. ジョブを実行するときは、JobService オブジェクトで setNotification() を呼び出します。この値は、タスク マネージャーとステータスバーの通知領域の両方で、ジョブが実行されていることをユーザーに知らせるために使用されます。

    class CustomTransferService : JobService() {
      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)
          // Do the job execution.
      }
    }
    
  6. 通知を定期的に更新して、ジョブのステータスと進行状況をユーザーに知らせます。ジョブのスケジュール前に転送サイズを決定できない場合や、推定転送サイズを更新する必要がある場合は、新しい API の updateEstimatedNetworkBytes() を使用して、判明した転送サイズを更新します。

  7. 実行が完了したら、jobFinished() を呼び出して、ジョブが完了したこと、またはジョブのスケジュールを再設定する必要があることをシステムに通知します。

ユーザーが開始するデータ転送ジョブは停止可能

ユーザーとシステムの両方が、ユーザーが開始した転送ジョブを停止できます。

ユーザーがタスク マネージャーから停止

ユーザーは、タスク マネージャーに表示されるユーザーが開始したデータ転送ジョブを停止できます。

ユーザーが [停止] を押すと、以下の処理が行われます。

  • アプリのプロセス(実行中の他のすべてのジョブまたはフォアグラウンド サービスを含む)をすぐに終了します。
  • 実行中のジョブについて onStopJob() を呼び出しません。
  • ユーザーに表示されるジョブのスケジュール変更が行われないようにします。

そのため、ジョブを正常に停止してスケジュールを変更できるように、ジョブに関して送信された通知でコントロールを提供することをおすすめします。

なお、特別な状況では、タスク マネージャーのジョブの横に [停止] ボタンが表示されないか、タスク マネージャーにジョブがまったく表示されません。

システムによる停止

Unlike regular jobs, user-initiated data transfer jobs are unaffected by App Standby Buckets quotas. However, the system still stops the job if any of the following conditions occur:

  • A developer-defined constraint is no longer met.
  • The system determines that the job has run for longer than necessary to complete the data transfer task.
  • The system needs to prioritize system health and stop jobs due to increased thermal state.
  • The app process is killed due to low device memory.

When the job is stopped by the system (not by the low-memory case), the system calls onStopJob(), and the system retries the job at a time that the system deems to be optimal. Check that your app can persist data transfer state, even if onStopJob() isn't called, and that your app can restore this state when onStartJob() is called again.

ユーザーが開始するデータ転送ジョブのスケジュールを設定できる条件

アプリがユーザー開始型データ転送ジョブを開始できるのは、アプリが可視ウィンドウ内にある場合、または特定の条件を満たしている場合のみです。システムは、ユーザー開始型データ転送ジョブのスケジュールを設定できるタイミングを判断するため、一定の条件(特殊なケースでアプリがバックグラウンドからアクティビティを開始できる場合と同じ条件)のリストを適用します。この条件のリストは、バックグラウンドで開始されるフォアグラウンド サービス制限の除外のセットとは異なります

上記の例外は次のとおりです。

  • アプリがバックグラウンドからアクティビティを開始できるのであれば、ユーザー開始型データ転送ジョブもバックグラウンドから開始できます。
  • アプリのアクティビティが [最近] 画面の既存タスクのバックスタックにあるだけでは、ユーザー開始型データ転送ジョブを実行することはできません。

許可された条件のリストにないタイミングでジョブのスケジュールが設定された場合、ジョブは失敗し、RESULT_FAILURE エラーコードが返されます。

ユーザーが開始するデータ転送ジョブで許可される制約

To support jobs running at optimal points, Android offers the ability to assign constraints to each job type. These constraints are already 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