백그라운드 최적화

백그라운드 프로세스는 메모리와 배터리를 많이 사용할 수 있습니다. 예를 들어 암시적 브로드캐스트는 프로세스가 많은 작업을 하지는 않더라도 수신 대기하도록 등록된 많은 백그라운드 프로세스를 시작할 수 있습니다. 이렇게 되면 기기 성능과 사용자 환경에 모두 상당한 영향을 미치게 됩니다.

이 문제를 완화하기 위해 Android 7.0 (API 수준 24)에서는 다음 제한사항을 적용합니다.

  • Android 7.0(API 수준 24) 이상을 타겟팅하는 앱은 매니페스트에서 broadcast receiver를 선언하면 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: 화면이 꺼져 있을 때 한 시간 동안 유지되는 한 번의 부분적 wake lock
  • 과도한 백그라운드 서비스: 앱이 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);
}

작업의 조건이 충족되면, 앱은 onStartJob() 메서드를 지정된 JobService.class에서 실행하기 위한 콜백을 수신합니다. JobScheduler 구현의 더 많은 예를 보려면 JobScheduler 샘플 앱을 참고하세요.

JobScheduler의 새로운 대안은 WorkManager로, 앱 프로세스 유무에 상관없이 완료가 보장되어야 하는 백그라운드 작업을 예약할 수 있는 API입니다. WorkManager는 기기 API 수준과 같은 요소에 기반해 작업을 실행할 적절한 방법(앱 프로세스의 스레드에서 직접 또는 JobScheduler, FirebaseJobDispatcher, AlarmManager를 사용)을 선택합니다. 또한 WorkManager에서는 Play 서비스가 필요하지 않으며 작업을 함께 체이닝하거나 작업 상태를 확인하는 등 여러 고급 기능을 제공합니다. 자세한 내용은 WorkManager를 참고하세요.

앱이 실행되는 동안 네트워크 연결 모니터링

실행 중인 앱은 등록된 BroadcastReceiverCONNECTIVITY_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)은 대체 솔루션을 제공하기 위해 JobInfoJobParameters를 확장합니다.

콘텐츠 URI 변경에 관한 작업 트리거

콘텐츠 URI 변경에 관한 작업을 트리거하기 위해 Android 7.0 (API 수준 24)은 다음 메서드를 사용하여 JobInfo API를 확장합니다.

JobInfo.TriggerContentUri()
콘텐츠 URI 변경에 관한 작업을 트리거하는 데 필요한 매개변수를 캡슐화합니다.
JobInfo.Builder.addTriggerContentUri()
TriggerContentUri 객체를 JobInfo에 전달합니다. ContentObserver는 캡슐화된 콘텐츠 URI를 모니터링합니다. 하나의 작업과 연결된 여러 TriggerContentUri 객체가 있는 경우, 콘텐츠 URI 중 하나에서만 변경이 보고되더라도 시스템은 콜백을 제공합니다.
지정된 URI의 하위 요소가 하나라도 변경되면, TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS 플래그를 추가하여 작업을 트리거합니다. 이 플래그는 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;
}

앱의 추가 최적화

메모리가 부족한 기기나 조건에서 실행되도록 앱을 최적화하면 성능과 사용자 환경을 개선할 수 있습니다. 백그라운드 서비스의 종속 항목과 매니페스트에 등록된 암시적 broadcast receiver의 종속 항목을 삭제하면 앱이 이러한 기기에서 더 잘 실행될 수 있습니다. Android 7.0(API 수준 24)에서 이 문제를 줄이기 위해 조치를 취하고 있지만 이러한 백그라운드 프로세스를 완전히 사용하지 않고 앱이 실행되도록 최적화하는 것이 좋습니다.

다음 Android 디버그 브리지(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