バックグラウンドでの最適化

バックグラウンド プロセスは、メモリと電池を大量に消費することがあります。たとえば、非明示的ブロードキャストにより、それをリッスンするように登録されているバックグラウンド プロセスが、作業量が少ないにもかかわらず多数開始される可能性があります。これは、デバイスのパフォーマンスとユーザー エクスペリエンスの両方に大きな影響を与える可能性があります。

この問題を軽減するため、Android 7.0(API レベル 24)では次の制限が適用されます。

  • Android 7.0(API レベル 24)以降を対象とするアプリは、マニフェストでブロードキャスト レシーバを宣言している場合は、CONNECTIVITY_ACTION ブロードキャストを受信しません。アプリが Context.registerReceiver() で BroadcastReceiver を登録し、コンテキストがまだ有効である場合、アプリは引き続き CONNECTIVITY_ACTION ブロードキャストを受信します。
  • アプリは ACTION_NEW_PICTURE ブロードキャストと ACTION_NEW_VIDEO ブロードキャストの送受信ができません。この最適化は、Android 7.0(API レベル 24)を対象とするアプリだけでなく、すべてのアプリに影響します。

アプリでこれらのインテントを使用している場合は、Android 7.0 以降を搭載するデバイスを対象にする設定を適切に行えるように、できるだけ早くそれらの依存関係を削除してください。Android フレームワークでは、非明示的ブロードキャストの必要性を緩和する方法がいくつかあります。たとえば、JobScheduler と新しい WorkManager は、指定された条件(定額制ネットワークへの接続など)が満たされた場合のネットワーク操作をスケジューリングする堅牢なメカニズムを備えています。JobScheduler を使用して、コンテンツ プロバイダの変更に応答できるようにもなりました。JobInfo オブジェクトは、JobScheduler がジョブのスケジューリングに使用するパラメータをカプセル化します。ジョブの条件が満たされると、システムがアプリの JobService でこのジョブを実行します。

このページでは、JobScheduler などの代替の方法を使用して、これらの新しい制限にアプリを適合させる方法を説明します。

ユーザーが開始する制限

システム設定の [バッテリー使用量] ページで、ユーザーは次のオプションのいずれかを選択できます。

  • 制限なし: すべてのバックグラウンド処理を許可し、バックグラウンドのバッテリー消費量の増加を許容します。
  • 最適化(デフォルト): ユーザーによるアプリの操作方法に基づいて、アプリがバックグラウンド処理を行う能力を最適化します。
  • 制限付き: バックグラウンドでのアプリの実行が完全にできなくなります。アプリが想定通り機能しない場合があります。

アプリが Android Vitals に記述されている好ましくない動作を行った場合、そのアプリによるシステム リソースへのアクセスを制限するように、システムがユーザーに求めることがあります。

アプリが過度にリソースを消費していることをシステムが検知すると、ユーザーに通知され、ユーザーにアプリのアクションを制限するかどうかの確認が求められます。 このような通知のきっかけとなる動作には、次のようなものがあります。

  • 過度な wake lock: 画面がオフのときに、1 つの部分的な wake lock が 1 時間保持されている
  • 過剰なバックグラウンド サービス: API レベルが 26 未満で、バックグラウンド サービスが過剰である場合

詳細な制限はデバイス メーカーが決定します。たとえば、Android 9(API レベル 28)以降を搭載している AOSP ビルドの場合、「制限付き」状態でバックグラウンドで動作しているアプリには以下の制限があります。

  • フォアグラウンド サービスを起動できない
  • 既存のフォアグラウンド サービスがフォアグラウンドから削除される
  • アラームがトリガーされない
  • ジョブが実行されない

また、アプリが Android 13(API レベル 33)以降をターゲットにしていて、「制限付き」状態である場合、その他の理由でアプリが起動されるまで、BOOT_COMPLETED ブロードキャストと LOCKED_BOOT_COMPLETED ブロードキャストは実行されません。

個別の制限事項は電源管理に関する制限に記載されています。

ネットワーク アクティビティのブロードキャストの受信に関する制限

Android 7.0(API レベル 24)を対象とするアプリは、CONNECTIVITY_ACTION ブロードキャストを受信するようにマニフェストに登録しても受信せず、このブロードキャストに依存するプロセスは開始されません。これは、ネットワークの変更をリッスンする、またはデバイスが定額制のネットワークに接続したときに、まとめてネットワーク アクティビティを実行するアプリに問題を引き起こす可能性があります。この制限を回避する方法は、すでに Android フレームワークにいくつか存在しますが、どれを選択すべきかはアプリの目的によって異なります。

注: Context.registerReceiver() で登録された BroadcastReceiver は、これらのブロードキャストをアプリの実行中も引き続き受信します。

定額制の接続で実行するようにネットワーク ジョブをスケジューリングする

JobInfo.Builder クラスを使用して JobInfo オブジェクトを作成する場合は、setRequiredNetworkType() メソッドを適用し、JobInfo.NETWORK_TYPE_UNMETERED をジョブ パラメータとして渡します。次のコードサンプルでは、デバイスが充電中で、定額制のネットワークに接続しているときに動作するサービスをスケジューリングしています。

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MyJobService::class.java)
    )
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
      (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo job = new JobInfo.Builder(
    MY_BACKGROUND_JOB,
    new ComponentName(context, MyJobService.class))
      .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
      .setRequiresCharging(true)
      .build();
  js.schedule(job);
}

ジョブの条件が満たされると、アプリはコールバックを受信して、指定された JobService.class の onStartJob() メソッドを実行します。JobScheduler の実装に関するその他の例については、JobScheduler のサンプルアプリをご覧ください。

JobScheduler に代わる新しい WorkManager は、アプリのプロセスの有無にかかわらず、完了する保証が必要なバックグラウンド タスクのスケジューリングができる API です。WorkManager は、デバイス API レベルなどの要素に基づいて、処理を実行する適切な方法を選択します(アプリのプロセスのスレッドで直接実行するか、JobScheduler、FirebaseJobDispatcher、あるいは AlarmManager を使用します)。さらに、WorkManager は Play 開発者サービスを必要とせず、タスクのチェーン化やタスクのステータスの確認などの高度な機能を備えています。詳細については、WorkManager をご覧ください。

アプリの実行中にネットワーク接続を監視する

実行中のアプリは、登録済みの BroadcastReceiver で CONNECTIVITY_CHANGE を引き続きリッスンできます。しかし、ConnectivityManager API は、指定されたネットワーク条件が満たされた場合にのみコールバックを要求する、より堅牢な手段を提供します。

NetworkRequest オブジェクトは、NetworkCapabilities に関するネットワーク コールバックのパラメータを定義します。NetworkRequest.Builder クラスを使用して NetworkRequest オブジェクトを作成します。作成後、registerNetworkCallback() は NetworkRequest オブジェクトをシステムに渡します。ネットワーク条件が満たされると、アプリはコールバックを受信し、ConnectivityManager.NetworkCallback クラスで定義された onAvailable() メソッドを実行します。

アプリは、終了するか unregisterNetworkCallback() を呼び出すまで、コールバックを受信し続けます。

画像や動画のブロードキャストの受信に関する制限

Android 7.0(API レベル 24)では、アプリは ACTION_NEW_PICTURE ブロードキャストと ACTION_NEW_VIDEO ブロードキャストの送受信ができません。この制限は、新しい画像や動画を処理するために複数のアプリを復帰させる必要がある場合に、パフォーマンスとユーザー エクスペリエンスが低下するのを防ぎます。Android 7.0(API レベル 24)では、JobInfo と JobParameters が拡張され、代替の解決策が用意されています。

コンテンツ URI の変更でジョブをトリガーする

コンテンツ URI の変更でジョブをトリガーするために、Android 7.0(API レベル 24)では次のメソッドで JobInfo API を拡張しています。

JobInfo.TriggerContentUri()
コンテンツ URI の変更でジョブをトリガーするために必要なパラメータをカプセル化しています。
JobInfo.Builder.addTriggerContentUri()
TriggerContentUri オブジェクトを JobInfo に渡します。ContentObserver はカプセル化されたコンテンツ URI を監視します。ジョブに複数の TriggerContentUri オブジェクトが関連付けられている場合、いずれか 1 つのコンテンツ URI のみが変更されたことを報告する場合でも、システムはコールバックを行います。
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS フラグを追加して、指定された URI の配下で変更があった場合にジョブをトリガーします。このフラグは、registerContentObserver() に渡される notifyForDescendants パラメータに対応します。

注: TriggerContentUri() は setPeriodic() または setPersisted() と組み合わせて使用することはできません。継続的にコンテンツの変更を監視するには、アプリの JobService が最新のコールバックの処理を終える前に、新しい JobInfo をスケジューリングしてください。

次のサンプルコードは、システムがコンテンツ URI(MEDIA_URI)の変更を報告したときにジョブをトリガーするようにスケジューリングします。

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MediaContentJob::class.java)
    )
            .addTriggerContentUri(
                    JobInfo.TriggerContentUri(
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                            JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
                    )
            )
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
          (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo.Builder builder = new JobInfo.Builder(
          MY_BACKGROUND_JOB,
          new ComponentName(context, MediaContentJob.class));
  builder.addTriggerContentUri(
          new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
          JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
  js.schedule(builder.build());
}

システムが指定されたコンテンツ URI の変更を報告すると、アプリはコールバックを受信し、JobParameters オブジェクトが MediaContentJob.class の onStartJob() メソッドに渡されます。

ジョブをトリガーしたコンテンツ認証局を調べる

Android 7.0(API レベル 24)では、JobParameters も拡張され、どのコンテンツ認証局と URI がジョブをトリガーしたかに関する有用な情報を、アプリが受信できるようになります。

Uri[] getTriggeredContentUris()
ジョブをトリガーした URI の配列を返します。ジョブをトリガーした URI がない場合(たとえば、デッドラインを超えたなどの理由でジョブがトリガーされた場合)、または変更された URI の数が 50 より多い場合は、null になります。
String[] getTriggeredContentAuthorities()
ジョブをトリガーしたコンテンツ認証局を文字列の配列で返します。 返された配列が null でない場合は、getTriggeredContentUris() を使用して変更された URI の詳細を取得します。

次のサンプルコードは、JobService.onStartJob() メソッドをオーバーライドし、ジョブをトリガーしたコンテンツ認証局と URI を記録します。

Kotlin

override fun onStartJob(params: JobParameters): Boolean {
    StringBuilder().apply {
        append("Media content has changed:\n")
        params.triggeredContentAuthorities?.also { authorities ->
            append("Authorities: ${authorities.joinToString(", ")}\n")
            append(params.triggeredContentUris?.joinToString("\n"))
        } ?: append("(No content)")
        Log.i(TAG, toString())
    }
    return true
}

Java

@Override
public boolean onStartJob(JobParameters params) {
  StringBuilder sb = new StringBuilder();
  sb.append("Media content has changed:\n");
  if (params.getTriggeredContentAuthorities() != null) {
      sb.append("Authorities: ");
      boolean first = true;
      for (String auth :
          params.getTriggeredContentAuthorities()) {
          if (first) {
              first = false;
          } else {
             sb.append(", ");
          }
           sb.append(auth);
      }
      if (params.getTriggeredContentUris() != null) {
          for (Uri uri : params.getTriggeredContentUris()) {
              sb.append("\n");
              sb.append(uri);
          }
      }
  } else {
      sb.append("(No content)");
  }
  Log.i(TAG, sb.toString());
  return true;
}

アプリをさらに最適化する

アプリをメモリの少ないデバイスやメモリが少ない状態に最適化すると、パフォーマンスやユーザー エクスペリエンスが向上します。バックグラウンド サービスとマニフェストに登録された非明示的ブロードキャスト レシーバの依存関係を削除すると、そのようなデバイスでのアプリの動作が改善されます。Android 7.0(API レベル 24)では、これらの問題の一部を軽減する措置が講じられますが、これらのバックグラウンド サービスをまったく使用せずに動作するようにアプリを最適化することをおすすめします。

次の Android Debug Bridge(ADB)コマンドを使用すると、バックグラウンド プロセスを無効にした状態でアプリの動作をテストできます。

  • 非明示的ブロードキャストとバックグラウンド サービスが使用できない状況をシミュレートするには、次のコマンドを入力します。
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • 非明示的ブロードキャストとバックグラウンド サービスを再度有効にするには、次のコマンドを入力します。
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • バックグラウンドのバッテリー使用量についてアプリを「制限付き」状態に設定するユーザーをシミュレートできます。この設定では、アプリがバックグラウンドで実行できなくなります。この処理を行うには、ターミナル ウィンドウで次のコマンドを実行します。
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny