백그라운드 최적화

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

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

  • Android 7.0(API 레벨 24) 이상을 타겟팅하는 앱은 manifest에서 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 9(API 레벨 28)부터 앱이 Android vitals에 설명된 잘못된 동작을 보이면 시스템은 사용자에게 시스템 리소스에 앱의 액세스를 제한하라는 메시지를 표시합니다.

시스템에서 앱이 과도한 리소스를 사용하는 것을 알면 사용자에게 알리고 앱의 작업을 제한하는 옵션을 사용자에게 제공합니다. 알림을 트리거할 수 있는 동작은 다음과 같습니다.

  • 과도한 wake lock: 화면이 꺼져 있을 때 한 시간 동안 유지되는 한 번의 부분적 wake lock
  • 과도한 백그라운드 서비스: 앱이 API 레벨 26 이하를 타겟팅하고 과도한 백그라운드 서비스가 있는 경우

적용되는 정확한 제한사항은 기기 제조업체에서 확인합니다. 예를 들어 AOSP 빌드에서는 앱이 포그라운드에 있을 때를 제외하고 제한된 앱은 작업을 실행하거나 알람을 트리거하거나 네트워크를 사용할 수 없습니다. 앱이 포그라운드에 있는지 확인하는 데 사용되는 기준은 백그라운드 서비스 제한을 참조하세요. 특정 제한사항은 전원 관리 제한사항에 나열되어 있습니다.

네트워크 활동 브로드캐스트 수신에 관한 제한사항

Android 7.0(API 레벨 24)을 타겟팅하는 앱은 manifest에서 수신하도록 등록한 경우 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)
    }
    

자바

    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를 참조하세요.

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

실행 중인 앱은 등록된 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 중 하나에서만 변경을 보고하더라도 시스템은 콜백을 제공합니다.
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)
    }
    

자바

    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의 변경을 보고하면 앱은 콜백을 수신하고 MediaContentJob.classonStartJob() 메서드에 JobParameters 개체가 전달됩니다.

어떤 콘텐츠 권한이 작업을 트리거했는지 확인

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
    }
    

자바

    @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;
    }
    

앱의 추가 최적화

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

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