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

Android 14 では、アプリがフォアグラウンド サービスを使用できるタイミングに厳格なルールが適用されます

また、Android 14 では、ユーザーが開始するデータ転送ジョブを必須とすることを指定する新しい API が導入されています。この API は、ユーザーが開始するデータ転送(リモート サーバーからのファイルのダウンロードなど)で、転送に時間がかかることが予想される場合に便利です。こうしたタイプのタスクでは、ユーザーが開始するデータ転送ジョブを使用する必要があります。

ユーザーが開始するデータ転送ジョブは、ユーザーによって開始されます。これらのジョブは通知を必要とし、直ちに開始されるとともに、システム条件が許せば実行時間が延長される可能性があります。ユーザーが開始するデータ転送ジョブは、同時に複数実行できます。

ユーザーが開始するジョブのスケジュールは、アプリがユーザーに表示されている状態(または許可された条件のいずれかで)設定される必要があります。システムの健全性に関わる制約にもよりますが、すべての制約が満たされていれば、ユーザーが開始するジョブは OS で実行できます。また、システムは提供された推定ペイロード サイズを使用して、ジョブの実行にかかる時間を推定することもできます。

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

ユーザーが開始するデータ転送ジョブを実行するには、新しい権限 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() を呼び出しません。
  • ユーザーに表示されるジョブのスケジュール変更が行われないようにします。

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

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

システムによる停止

通常のジョブとは異なり、ユーザーが開始するデータ転送ジョブはアプリ スタンバイ バケットの割り当ての影響を受けません。ただし、次のいずれかの条件が発生すると、システムはジョブを停止します。

  • デベロッパー定義の制約が満たされなくなった。
  • システムにより、ジョブがデータ転送タスクを完了するために必要な時間よりも長く実行されていると判断された。
  • 温度が上昇しているため、システムの健全性を優先してジョブを停止する必要がある。
  • デバイスのメモリが少ないため、アプリプロセスが強制終了された。

メモリ不足ではなくシステムによってジョブが停止される場合、システムは onStopJob() を呼び出し、システムが最適だと判断したタイミングでジョブを再試行します。onStopJob() が呼び出されていなくてもアプリがデータ転送状態を保持できることと、onStartJob() が再度呼び出されたときにアプリがこの状態を復元できることを確認してください。

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

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

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

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

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

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

Android には、最適なポイントで実行されるジョブをサポートするために、ジョブタイプごとに制約を割り当てる機能があります。これらの制約は、Android 13 ですでに利用可能となっていました。

: 次の表では、ジョブタイプによって異なる制約のみを比較しています。すべての制約については、JobScheduler のデベロッパー ページまたは処理の制約をご覧ください。

次の表に、特定のジョブ制約をサポートするさまざまなジョブタイプと、WorkManager がサポートするジョブ制約のセットを示します。表の前の検索バーを使用して、ジョブ制約メソッドの名前で表をフィルタできます。

ユーザーが開始するデータ転送ジョブで許可される制約は次のとおりです。

  • 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