ART 서비스 구성

시작하기 전에 대략적인 ART 서비스 개요를 확인하세요.

Android 14부터 앱의 기기 내 AOT 컴파일(일명 dexopt)이 ART 서비스에서 처리됩니다. ART 서비스는 ART 모듈의 일부이며 시스템 속성 및 API를 통해 맞춤설정할 수 있습니다.

시스템 속성

ART 서비스는 관련 모든 dex2oat 옵션을 지원합니다.

또한 ART 서비스는 다음 시스템 속성을 지원합니다.

pm.dexopt.<reason>

이는 Dexopt 시나리오에 설명된 사전 정의된 모든 컴파일 이유에 관한 기본 컴파일러 필터를 결정하는 시스템 속성의 집합입니다.

자세한 내용은 컴파일러 필터를 참고하세요.

표준 기본값은 다음과 같습니다.

pm.dexopt.first-boot=verify
pm.dexopt.boot-after-ota=verify
pm.dexopt.boot-after-mainline-update=verify
pm.dexopt.bg-dexopt=speed-profile
pm.dexopt.inactive=verify
pm.dexopt.cmdline=verify

pm.dexopt.shared(기본값: speed)

다른 앱에서 사용하는 앱의 대체 컴파일러 필터입니다.

원칙적으로 ART 서비스는 일반적으로 백그라운드 dexopt 중에 가능한 경우 모든 앱의 프로필 기반 컴파일(speed-profile)을 실행합니다. 그러나 일부 앱은 다른 앱에서 사용합니다(<uses-library>를 통해 또는 CONTEXT_INCLUDE_CODE와 함께 Context#createPackageContext를 사용하여 동적으로 로드됨). 이러한 앱은 개인 정보 보호상의 이유로 로컬 프로필을 사용할 수 없습니다.

이러한 앱의 경우 프로필 기반 컴파일이 요청되면 ART 서비스는 먼저 클라우드 프로필을 사용하려고 합니다. 클라우드 프로필이 없으면 ART 서비스는 pm.dexopt.shared에서 지정된 컴파일러 필터를 사용하도록 대체합니다.

요청된 컴파일이 프로필 기반이 아니면 이 속성은 아무런 영향을 미치지 않습니다.

pm.dexopt.<reason>.concurrency(기본값: 1)

사전 정의된 특정 컴파일 이유(first-boot, boot-after-ota, boot-after-mainline-update, bg-dexopt)의 dex2oat 호출 수입니다.

이 옵션의 효과는 dex2oat 리소스 사용 옵션(dalvik.vm.*dex2oat-threads, dalvik.vm.*dex2oat-cpu-set, 작업 프로필)과 결합되어 있습니다.

  • dalvik.vm.*dex2oat-threads는 각 dex2oat 호출의 스레드 수를 제어하고 pm.dexopt.<reason>.concurrency는 dex2oat 호출 수를 제어합니다. 즉, 최대 동시 스레드 수는 두 시스템 속성의 곱입니다.
  • dalvik.vm.*dex2oat-cpu-set와 작업 프로필은 위에 설명한 최대 동시 스레드 수와 관계없이 항상 CPU 코어 사용량을 제한합니다.

단일 dex2oat 호출은 dalvik.vm.*dex2oat-threads와 상관없이 모든 CPU 코어를 완전히 활용하지 못할 수 있습니다. 따라서 dex2oat 호출(pm.dexopt.<reason>.concurrency) 수를 늘리면 CPU 코어를 더 잘 활용하여 전반적인 dexopt 진행 속도를 높일 수 있습니다. 이는 부팅 중에 특히 유용합니다.

하지만 dex2oat 호출이 너무 많으면 스왑 파일을 사용하도록 dalvik.vm.dex2oat-swaptrue로 설정하여 완화할 수 있다 해도 기기의 메모리가 부족해질 수 있습니다. 너무 많은 호출은 불필요한 컨텍스트 전환도 야기할 수 있습니다. 따라서 이 수치는 제품별로 신중하게 조정해야 합니다.

pm.dexopt.downgrade_after_inactive_days(기본값: 설정되지 않음)

이 옵션이 설정되면 ART 서비스는 지난 특정 일수 내에 사용된 앱만 dexopt를 실행합니다.

또한 저장소가 거의 부족하면 백그라운드 dexopt 중에 ART 서비스는 지난 특정 일수 내에 사용되지 않은 앱의 컴파일러 필터를 다운그레이드하여 공간을 확보합니다. 이에 관한 컴파일러 이유는 inactive이고 컴파일러 필터는 pm.dexopt.inactive에서 결정됩니다. 이 기능을 트리거하는 공간 기준점은 저장소 관리자의 공간 부족 기준점(전역 설정 sys_storage_threshold_percentagesys_storage_threshold_max_bytes를 통해 구성 가능, 기본값은 500MB)에 500MB를 합한 값입니다.

ArtManagerLocal#setBatchDexoptStartCallback을 통해 패키지 목록을 맞춤설정하면 bg-dexoptBatchDexoptStartCallback에서 제공된 목록의 패키지가 다운그레이드되지 않습니다.

pm.dexopt.disable_bg_dexopt(기본값: false)

테스트 전용입니다. ART 서비스가 백그라운드 dexopt 작업을 예약하는 것을 방지합니다.

백그라운드 dexopt 작업이 이미 예약되었지만 아직 실행되지 않았다면 이 옵션은 아무런 효과가 없습니다. 즉, 작업은 여전히 실행됩니다.

백그라운드 dexopt 작업이 실행되지 않도록 하려면 다음과 같은 명령어 시퀀스를 사용하는 것이 좋습니다.

setprop pm.dexopt.disable_bg_dexopt true
pm bg-dexopt-job --disable

첫 번째 줄은 백그라운드 dexopt 작업의 예약을 방지합니다(아직 예약되지 않은 경우). 두 번째 줄은 백그라운드 dexopt 작업의 예약을 해제하고(이미 예약된 경우) 백그라운드 dexopt 작업을 즉시 취소합니다(실행되는 경우).

ART 서비스 API

ART 서비스는 맞춤설정을 위해 Java API를 노출합니다. API는 ArtManagerLocal에 정의되어 있습니다. 사용법은 art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java에서 Javadoc을 참고하세요(Android 14 소스, 미출시 개발 소스).

ArtManagerLocalLocalManagerRegistry에서 보유한 싱글톤입니다. 도우미 함수 com.android.server.pm.DexOptHelper#getArtManagerLocal을 사용하여 가져올 수 있습니다.

import static com.android.server.pm.DexOptHelper.getArtManagerLocal;

대부분의 API에는 모든 앱의 정보를 보유하는 PackageManagerLocal.FilteredSnapshot의 인스턴스가 필요합니다. 이는 PackageManagerLocal#withFilteredSnapshot을 호출하면 되며 여기서 PackageManagerLocalLocalManagerRegistry에서 보유하는 싱글톤이며 com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal에서 가져올 수 있습니다.

import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;

다음은 API의 일반적인 사용 사례입니다.

앱 dexopt 트리거

언제든지 ArtManagerLocal#dexoptPackage를 호출하여 앱의 dexopt를 트리거할 수 있습니다.

try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  getArtManagerLocal().dexoptPackage(
      snapshot,
      "com.google.android.calculator",
      new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build());
}

자체 dexopt 이유를 전달할 수도 있습니다. 이렇게 하려면 우선순위 클래스와 컴파일러 필터를 명시적으로 설정해야 합니다.

try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  getArtManagerLocal().dexoptPackage(
      snapshot,
      "com.google.android.calculator",
      new DexoptParams.Builder("my-reason")
          .setCompilerFilter("speed-profile")
          .setPriorityClass(ArtFlags.PRIORITY_BACKGROUND)
          .build());
}

dexopt 취소

dexoptPackage 호출로 작업이 시작되는 경우 특정 시점에 작업을 취소할 수 있는 취소 신호를 전달할 수 있습니다. 이는 비동기식으로 dexopt를 실행할 때 유용할 수 있습니다.

Executor executor = ...;  // Your asynchronous executor here.
var cancellationSignal = new CancellationSignal();
executor.execute(() -> {
  try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
    getArtManagerLocal().dexoptPackage(
        snapshot,
        "com.google.android.calculator",
        new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build(),
        cancellationSignal);
  }
});

// When you want to cancel the operation.
cancellationSignal.cancel();

ART 서비스에서 시작된 백그라운드 dexopt도 취소할 수 있습니다.

getArtManagerLocal().cancelBackgroundDexoptJob();

dexopt 결과 가져오기

dexoptPackage 호출로 작업이 시작되는 경우 반환값에서 결과를 가져올 수 있습니다.

DexoptResult result;
try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  result = getArtManagerLocal().dexoptPackage(...);
}

// Process the result here.
...

ART 서비스도 백그라운드 dexopt 등 여러 시나리오에서 직접 dexopt 작업을 시작합니다. 작업이 dexoptPackage 호출로 시작되든 ART 서비스로 시작되든 상관없이 모든 dexopt 결과를 수신 대기하려면 ArtManagerLocal#addDexoptDoneCallback을 사용하세요.

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */,
    Runnable::run,
    (result) -> {
      // Process the result here.
      ...
    });

첫 번째 인수는 결과에 업데이트만 포함할지 결정합니다. dexopt로 업데이트된 패키지만 수신 대기하려면 이를 true로 설정하세요.

두 번째 인수는 콜백의 실행자입니다. dexopt를 실행하는 동일한 스레드에서 콜백을 실행하려면 Runnable::run을 사용하세요. 콜백으로 dexopt가 차단되지 않도록 하려면 비동기 실행자를 사용합니다.

콜백은 여러 개 추가할 수 있으며 ART 서비스에서는 모든 콜백을 순차적으로 실행합니다. 모든 콜백은 삭제되지 않는 한 모든 향후 호출을 위해 활성 상태로 유지됩니다.

콜백을 삭제하려면 추가할 때 콜백 참조를 유지하고 ArtManagerLocal#removeDexoptDoneCallback을 사용하세요.

DexoptDoneCallback callback = (result) -> {
  // Process the result here.
  ...
};

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */, Runnable::run, callback);

// When you want to remove it.
getArtManagerLocal().removeDexoptDoneCallback(callback);

패키지 목록 및 dexopt 매개변수 맞춤설정

ART 서비스는 부팅 및 백그라운드 dexopt 중에 dexopt 작업을 직접 시작합니다. 이러한 작업의 패키지 목록 또는 dexopt 매개변수를 맞춤설정하려면 ArtManagerLocal#setBatchDexoptStartCallback을 사용하세요.

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      switch (reason) {
        case ReasonMapping.REASON_BG_DEXOPT:
          var myPackages = new ArrayList<String>(defaultPackages);
          myPackages.add(...);
          myPackages.remove(...);
          myPackages.sort(...);
          builder.setPackages(myPackages);
          break;
        default:
          // Ignore unknown reasons.
      }
    });

패키지 목록에 항목을 추가하거나 목록에서 항목을 삭제하거나 항목을 정렬하거나 완전히 다른 목록을 사용할 수도 있습니다.

콜백은 알 수 없는 이유를 무시해야 합니다. 향후 더 많은 이유가 추가될 수 있기 때문입니다.

최대 1개의 BatchDexoptStartCallback을 설정할 수 있습니다. 이 콜백은 삭제되지 않는 한 향후 모든 호출을 위해 활성 상태로 유지됩니다.

콜백을 삭제하려면 ArtManagerLocal#clearBatchDexoptStartCallback을 사용하세요.

getArtManagerLocal().clearBatchDexoptStartCallback();

백그라운드 dexopt 작업의 매개변수 맞춤설정

기본적으로 백그라운드 dexopt 작업은 기기가 유휴 상태이고 충전 중일 때 하루에 한 번 실행됩니다. 이는 ArtManagerLocal#setScheduleBackgroundDexoptJobCallback을 사용하여 변경할 수 있습니다.

getArtManagerLocal().setScheduleBackgroundDexoptJobCallback(
    Runnable::run,
    builder -> {
      builder.setPeriodic(TimeUnit.DAYS.toMillis(2));
    });

최대 1개의 ScheduleBackgroundDexoptJobCallback을 설정할 수 있습니다. 이 콜백은 삭제되지 않는 한 향후 모든 호출을 위해 활성 상태로 유지됩니다.

콜백을 삭제하려면 ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback을 사용하세요.

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

dexopt 일시적으로 사용 중지

ART 서비스에서 시작되는 dexopt 작업은 BatchDexoptStartCallback을 트리거합니다. 작업을 계속 취소하여 dexopt를 효과적으로 사용 중지할 수 있습니다.

취소한 작업이 백그라운드 dexopt이면 기본 재시도 정책(30초, 지수, 5시간 제한)을 따릅니다.

// Good example.

var shouldDisableDexopt = new AtomicBoolean(false);

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      if (shouldDisableDexopt.get()) {
        cancellationSignal.cancel();
      }
    });

// Disable dexopt.
shouldDisableDexopt.set(true);
getArtManagerLocal().cancelBackgroundDexoptJob();

// Re-enable dexopt.
shouldDisableDexopt.set(false);

최대 1개의 BatchDexoptStartCallback을 보유할 수 있습니다. 또한 BatchDexoptStartCallback을 사용하여 패키지 목록이나 dexopt 매개변수를 맞춤설정하려면 코드를 콜백 하나로 결합해야 합니다.

// Bad example.

// Disable dexopt.
getArtManagerLocal().unscheduleBackgroundDexoptJob();

// Re-enable dexopt.
getArtManagerLocal().scheduleBackgroundDexoptJob();

앱 설치 시 실행된 dexopt 작업은 ART 서비스에서 시작되지 않습니다. 대신 dexoptPackage 호출을 통해 패키지 관리자에서 시작됩니다. 따라서 BatchDexoptStartCallback을 트리거하지 않습니다. 앱 설치 시 dexopt를 사용 중지하려면 패키지 관리자가 dexoptPackage를 호출하지 못하도록 하세요.

특정 패키지의 컴파일러 필터 재정의(Android 15(AOSP 실험용) 이상)

setAdjustCompilerFilterCallback을 통해 콜백을 등록하여 특정 패키지의 컴파일러 필터를 재정의할 수 있습니다. 콜백은 dexopt가 부팅 및 백그라운드 dexopt 중에 ART 서비스에서 시작되든 또는 dexoptPackage API 호출로 시작되든 상관없이 패키지가 dexopt 처리될 때마다 호출됩니다.

패키지에 조정이 필요하지 않으면 콜백은 originalCompilerFilter를 반환해야 합니다.

getArtManagerLocal().setAdjustCompilerFilterCallback(
    Runnable::run,
    (packageName, originalCompilerFilter, reason) -> {
      if (isVeryImportantPackage(packageName)) {
        return "speed-profile";
      }
      return originalCompilerFilter;
    });

AdjustCompilerFilterCallback은 하나만 설정할 수 있습니다. AdjustCompilerFilterCallback을 사용하여 여러 패키지의 컴파일러 필터를 재정의하려는 경우 코드를 하나의 콜백으로 결합해야 합니다. 이 콜백은 삭제되지 않는 한 향후 모든 호출을 위해 활성 상태로 유지됩니다.

콜백을 삭제하려면 ArtManagerLocal#clearAdjustCompilerFilterCallback을 사용하세요.

getArtManagerLocal().clearAdjustCompilerFilterCallback();

기타 맞춤설정

ART 서비스는 다른 맞춤설정도 지원합니다.

백그라운드 dexopt의 열 기준점 설정

백그라운드 dexopt 작업의 열 제어는 작업 스케줄러에서 실행됩니다. 작업은 온도가 THERMAL_STATUS_MODERATE에 도달하면 즉시 취소됩니다. THERMAL_STATUS_MODERATE의 기준점은 조정할 수 있습니다.

백그라운드 dexopt가 실행 중인지 확인

백그라운드 dexopt 작업은 작업 스케줄러에서 관리하며 작업 ID는 27873780입니다. 작업이 실행 중인지 확인하려면 작업 스케줄러 API를 사용하세요.

// Good example.

var jobScheduler =
    Objects.requireNonNull(mContext.getSystemService(JobScheduler.class));
int reason = jobScheduler.getPendingJobReason(27873780);

if (reason == PENDING_JOB_REASON_EXECUTING) {
  // Do something when the job is running.
  ...
}
// Bad example.

var backgroundDexoptRunning = new AtomicBoolean(false);

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) {
        backgroundDexoptRunning.set(true);
      }
    });

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */,
    Runnable::run,
    (result) -> {
      if (result.getReason().equals(ReasonMapping.REASON_BG_DEXOPT)) {
        backgroundDexoptRunning.set(false);
      }
    });

if (backgroundDexoptRunning.get()) {
  // Do something when the job is running.
  ...
}

dexopt 프로필 제공

프로필을 사용하여 dexopt를 안내하려면 APK 옆에 .prof 파일이나 .dm 파일을 배치하세요.

.prof 파일은 바이너리 형식 프로필 파일이어야 하며 파일 이름은 APK + .prof 파일 이름이어야 합니다. 예를 들면 다음과 같습니다.

base.apk.prof

.dm 파일의 파일 이름은 확장자가 .dm으로 대체된 APK의 파일 이름이어야 합니다. 예를 들면 다음과 같습니다.

base.dm

프로필이 dexopt에 사용되는지 확인하려면 speed-profile로 dexopt를 실행하고 결과를 확인합니다.

pm art clear-app-profiles <package-name>
pm compile -m speed-profile -f -v <package-name>

첫 번째 줄은 런타임에서 생성된 모든 프로필(즉, /data/misc/profiles의 프로필)을 삭제하여 APK 옆의 프로필이 ART 서비스에서 사용할 수 있는 유일한 프로필인지 확인합니다. 두 번째 줄은 speed-profile로 dexopt를 실행하고 -v를 전달하여 상세한 결과를 출력합니다.

프로필이 사용되는 경우 결과에 actualCompilerFilter=speed-profile이 표시됩니다. 사용되지 않으면 actualCompilerFilter=verify가 표시됩니다. 예를 들면 다음과 같습니다.

DexContainerFileDexoptResult{dexContainerFile=/data/app/~~QR0fTV0UbDbIP1Su7XzyPg==/com.google.android.gms-LvusF2uARKOtBbcaPHdUtQ==/base.apk, primaryAbi=true, abi=x86_64, actualCompilerFilter=speed-profile, status=PERFORMED, dex2oatWallTimeMillis=4549, dex2oatCpuTimeMillis=14550, sizeBytes=3715344, sizeBeforeBytes=3715344}

ART 서비스가 프로필을 사용하지 않는 일반적인 이유는 다음과 같습니다.

  • 프로필이 잘못된 파일 이름을 갖고 있거나 APK 옆에 없습니다.
  • 프로필의 형식이 잘못되었습니다.
  • 프로필이 APK와 일치하지 않습니다. 프로필의 체크섬이 APK의 .dex 파일 체크섬과 일치하지 않습니다.