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

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

この問題を軽減するため、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 を使用して、コンテンツ プロバイダの変更に応答できるようになりました。JobScheduler がジョブのスケジューリングに使用するパラメータは、JobInfo オブジェクトがカプセル化します。ジョブの条件が満たされると、システムがアプリの JobService でこのジョブを実行します。

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

ユーザーが開始する制限

Android 9(API レベル 28)から、アプリが Android Vitals に記述された好ましくない動作を行った場合、そのアプリによるシステム リソースへのアクセスを制限するように、システムがユーザーに求めます。

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

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

詳細な制限はデバイス メーカーが決定します。たとえば、AOSP ビルドでは、制限されたアプリは、アプリがフォアグラウンドにある場合を除き、ジョブの実行、アラームのトリガー、ネットワークの使用ができません(アプリがフォアグラウンドかどうかを判断する基準については、バックグラウンド サービスの制限をご覧ください)。電源管理に関する制限に個別の制限事項が記載されています。

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

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.classonStartJob() メソッドを実行します。JobScheduler 実装のその他の例については、JobScheduler のサンプルアプリをご覧ください。

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

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

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

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

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

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

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

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

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

JobInfo.TriggerContentUri()
コンテンツ URI の変更でジョブをトリガーするために必要なパラメータをカプセル化しています。
JobInfo.Builder.addTriggerContentUri()
TriggerContentUri オブジェクトを JobInfo に渡します。ContentObserver はカプセル化されたコンテンツ URI を監視します。ジョブに複数の TriggerContentUri オブジェクトが関連付けられている場合、いずれかのコンテンツ 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.classonStartJob() メソッドに渡されます。

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

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 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