[번역] [Android] Background Optimizations

Background Optimizations

백그라운드 프로세스는 메모리 혹은 배터리를 많이 소모할 수 있다. 예를들어 Implicit Broadcast는 해당 브로드캐스트를 확인하기 위해 등록된 많은 백그라운드 프로세스를 시작하게 될 것이다. 비록 그 프로세스들이 많은 작업을 수행하지 않을지라도... 이것은 디바이스의 성능과 사용자 경험에 상당한 충격을 줄 수 있다. 
이 이슈를 완화하기 위해 Android 7.0에서는 아래 제약사항들이 적용되었다.
  • Android 7.0을 Target으로 하는 앱들 혹은 그 이상의 앱들은 manifest에 브로드캐스트 리시버를 등록하더라도 CONNECTIVITY_ACTION 를 받을 수 없다. 하지만 앱들은 브로드캐스트 리시버를 context가 유효한 상태에서 Context.registerReceiver() 를 사용할 경우 CONNECTIVITY_ACTION 를 받을 수 있다.
  • Apps cannot send or receive ACTION_NEW_PICTURE or ACTION_NEW_VIDEO broadcasts.
    앱들은 ACTION_NEW_PICTURE 또는 ACTION_NEW_VIDEO 브로드캐스트를 보내거나 받을 수 없다. 이 최적화는 모든 앱들에 적용된다.(Android 7.0 target인 앱들에만 적용되는 것이 아니다)
만약 앱이 사용한다 이 인텐트들을 사용한다면, 가능한 빨리 Android 7.0을 타겟팅 할 수 있도록 종속성을 제거해야 한다. 안드로이드 프레임워크는 이러한 implicit broadcast의 필요를 완화할 수 있는 몇가지 솔루션을 제공한다. 예를들어 JobScheduler GcmNetworkManager는 특별한 조건(ex. 측정되지 않은 네트워크에 대한 연결)일 때 네트워크 오퍼레이션을 스케쥴 하는 강력한 메커니즘을 제공한다. 이제 JobScheduler 를 사용해서 Content Provider에 대한 변경사항에 대해 대응할 수 있다. JobInfo 객체는 JobScheduler가 작업 스케줄링에 사용하는 Parameter를 캡슐화 한다. 작업의 조건이 달성되었을 때, 시스템은 앱의 JobService에서 작업을 실행한다. 
이 문서에서는 이런 새로운 제약들에 대해 앱을 조정하도록 JobScheduler와 같이 대안이 되는 방법들에 대해 알아본다. 

Restrictions on CONNECTIVITY_ACTION


Android 7.0 Target인 앱들은 manifest에 리시버를 등록해놨다더라도 CONNECTIVITY_ACTION  브로드캐스트를 받지 못한다. 그리고 이 브로드캐스트에 의존되는 프로세스들도 시작되지 않는다. 이는 디바이스가 측정되지 않는 네트워크(unmetered network)에 연결될 때, 네트워크 변경을 수신하거나 대량의 네트워크 활동을 하려는 앱에 문제를 일으킬 수 있다. 이미 이러한 제한들을 해결하는 몇몇의 솔루션들은 안드로이드 프레임워크에 있다. 하지만 적합한 것을 선택하는 것은 앱이 성취하고자하는 것이 무엇인지에 달려있다. 
Note: 앱이 실행되는 동안 Context.registerReceiver()를 통해 등록된 BroadcastReceiver는 이전과 같이 브로드캐스트를 받을 수 있다.

Scheduling Network Jobs on Unmetered Connections

JobInfo 객체를 만들기 위해 JobInfo.Builder 클래스를 사용할 때 setRequiredNetworkType() Method를 적용하고, JobInfo.NETWORK_TYPE_UNMETERED를 작업 parameter로 전달한다. 아래 샘플코드는 디바이스가 측정할 수 없는 네트워크에 연결되어있고, 충전중일때 서비스를 실행하도록 스케쥴링한다.
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);
}
When the conditions for your job are met, your app receives a callback to run the onStartJob() method in the specified JobService.class.
작업을 위한 조건이 충족되었을 때, 앱은 콜백을 수신하여 지정된 JobService.class에서 onStartJob() Method를 실행한다. 좀더 많은 JobScheduler 의 구현 예제는 JobScheduler sample app에서 볼 수 있다.
GMSCore services를 사용하고 Target 이 Android 5.0이하인 어플리케이션은 GcmNetworkManager 를 사용할 수 있고 Task.NETWORK_STATE_UNMETERED를 지정할 수 있다.

Monitoring Network Connectivity While the App is Running

Apps that are running can still listen for CONNECTIVITY_CHANGE with a registered BroadcastReceiver.
실행되고 있는 앱은 BroadcastReceiver를 등록해서 CONNECTIVITY_CHANGE를 수신할 수 있다. 하지만 ConnectivityManager API는 특정 네트워크 컨디션이 되었을 때 만 콜백을 요청할 수 있는 좀더 강력한 method를 제공한다 
NetworkRequest 객체는 NetworkCapabilities와 관련해서 네트워크 콜백 parameter를 정의한다. NetworkRequest.Builder를 통해 NetworkRequest를 생성할 수 있다. 그런다음 registerNetworkCallback()은 NetworkRequest 객체를 시스템에 전달한다. 네트워크 컨디션이 충족되었을 때, 앱은 ConnectivityManager.NetworkCallback 클래스에 정의되어있는 onAvailable() method를 실행하기 위해 콜백을 수신한다. 
앱이 종료되거나 unregisterNetworkCallback()를 호출될 때까지 앱은 계속 콜백을 받을 수 있다.

Restrictions on NEW_PICTURE and NEW_VIDEO


Android 7.0에서 앱들은 ACTION_NEW_PICTURE 혹은 ACTION_NEW_VIDEO 브로드캐스트에 대한 송수신을 사용할 수 없다. 이런 제약은 몇몇 앱들이 새로운 이미지나 비디오의 처리를 위해 반드시 깨어날 때, 성능과 사용자 경험으로 오는 충격을 완화하는데 도움을 준다. Android 7.0에서는 대안이 될 수 있는 솔루션을 제공하기 위해 JobInfo 와 JobParameters를 확대(extends)했다.

New JobInfo methods

컨텐츠 URI 변경을 트리거하기 위해 Android 7.0은 JobInfo 를 확대해서 아래 Method들을 추가했다.
JobInfo.TriggerContentUri()
Content URI 변경을 트리거 할 때 요구되는 parameter를 캡슐화 한다.
JobInfo.Builder.addTriggerContentUri()
 JobInfo에 TriggerContentUri 객체를 전달한다. ContentObserver는 content URL의 캡슐화를 모니터 한다.
만약 어떤 작업과 관련이 있는 여러 TriggerContentUri 객체가 있다면 시스템은 비록 그것이 content URI들 중 오직 하나의 변경일 지라도 콜백을 제공한다 
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS 플래그를 추가하여 주어진 URI중 하위 항목이 변경된 경우 트리거한다. 이 플래그는 registerContentObserver() 전달된 notifyForDescendants 파라미터에 해당된다.
Note: TriggerContentUri() 는 setPeriodic() 혹은 setPersisted()와 함께 사용될 수는 없다. 컨텐츠 변경을 계속 감시하기 위해, 앱의 Jobservice가 가장 마지막 콜백의 처리를 끝내기 전에 새로운 JobInfo를 스케쥴 해라. 
The following sample code schedules a job to trigger when the system reports a change to the content URI, MEDIA_URI: 아래 샘플 코드는 시스템이 content URI, MEDIA_URI의 변경을 리포트 하는것을 트리거 하기 위해 작업을 스케줄 하는 것이다. 
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());
}
When the system reports a change in the specified content URI(s), your app receives a callback and a JobParameters object is passed to the onStartJob() method in MediaContentJob.class.
시스템이 특정 컨텐츠 URI의 변경을 리포트 할 때, 앱은 콜백과 MediaContentJob.class의 onStartJob() method로 전달된 JobParameters 객체를 수신한다.

New JobParameter Methods

Android 7.0은 또한 JobParameters를 확장해서, 앱이 컨텐츠 권한 및 URI가 작업을 트리거 한 이유에 대해 유용한 정보를 수신하는 것을 허용했다.
Uri[] getTriggeredContentUris()
Returns an array of URIs that have triggered the job. 해당 작업을 트리거한 URI들의 array를 리턴한다. 만약 해당 작업을 트리거한 URI가 없거나(예를들면 해당 작업이 데드라인이나 혹은 다른 이유에 의해 트리거 되었다면) 50개보다 더 크다면 이 값은 null이다.
String[] getTriggeredContentAuthorities()
Returns a string array of content authorities that have triggered the job. 해당 작업을 트리거 한 컨텐츠 권한의 String Array를 리턴한다. 만약 리턴이 null이 아니라면, URI 변경의 세부사항을 확인하기 위해 getTriggeredContentUris()를 사용해라. 
아래 샘플코드는 JobService.onStartJob() method를 oerride하고, 해당 작업이 트리거 된 content 권한과 URI를 기록한다.
@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;
}

Further Optimizing Your App


low-memory 디바이스 혹은 low-memory 조건에서 실행하기 위해 앱을 최적화 하는 것은 성능과 사용자 경험을 개선할 수 있다. 백그라운드 서비스와 manifest에 등록되는 implicit broadcast receiver들에 대한 종속성을 제거하는 것은 위와같은 디바이스들에서 앱이 좀 더 잘 실행 될 수 있게 한다. 비록 Android 7.0에서 이러한 이슈를 줄이고자 조치가 취해졌지만, 이러한 백그라운드 프로세스를 전혀 사용하지 않고 앱을 실행할 수 있도록 최적화 하는 것을 권장한다.

Android 7.0은 그러한 백그라운드 프로세스의 disabled된 상태에서 앱 동작을 테스트하는데 사용할 수 있는 몇개의 추가적인 Android Debug Bridge (ADB) 커맨드를 소개한다. 
  • implicit broadcast와 백그라운드 서비스를 사용할 수 없는 조건을 시뮬레이션 하기위해  아래 커맨드를 입력하라.
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • 다시 사용하게 만드려면 아래 커맨드를 입력하라.
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow

댓글

이 블로그의 인기 게시물

[Android] DataBinding의 동작방식 - 4. include Tag 혹은 ViewStub 사용시의 Binding

[Android] DataBinding의 동작방식 - 5. Listener, Callback (CustomView의 Callback을 람다식으로 Binding하기)

[Android] DataBinding의 동작방식 - 2. BindingAdapter의 기본 및 사용 시점