Cấu hình Dịch vụ ART

Trước khi bắt đầu, hãy xem thông tin tổng quan cấp cao về Dịch vụ ART.

Kể từ Android 14, quá trình biên dịch AOT trên thiết bị cho ứng dụng (còn gọi là dexopt) sẽ do Dịch vụ ART xử lý. Dịch vụ ART là một phần của mô-đun ART và bạn có thể tuỳ chỉnh dịch vụ này thông qua các thuộc tính hệ thống và API.

Thuộc tính hệ thống

Dịch vụ ART hỗ trợ tất cả tuỳ chọn dex2oat có liên quan.

Ngoài ra, Dịch vụ ART hỗ trợ các thuộc tính hệ thống sau:

pm.dexopt.<reason>

Đây là một tập hợp các thuộc tính hệ thống xác định bộ lọc trình biên dịch mặc định cho tất cả lý do biên dịch được xác định trước được mô tả trong các trường hợp Dexopt.

Để biết thêm thông tin, hãy xem phần Bộ lọc trình biên dịch.

Các giá trị mặc định chuẩn là:

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 (mặc định: tốc độ)

Đây là bộ lọc trình biên dịch dự phòng cho các ứng dụng mà các ứng dụng khác sử dụng.

Về nguyên tắc, Dịch vụ ART sẽ biên dịch theo hướng dẫn của hồ sơ (speed-profile) cho tất cả ứng dụng khi có thể, thường là trong quá trình dexopt ở chế độ nền. Tuy nhiên, có một số ứng dụng được các ứng dụng khác sử dụng (thông qua <uses-library> hoặc được tải động bằng Context#createPackageContext với CONTEXT_INCLUDE_CODE). Các ứng dụng như vậy không thể sử dụng hồ sơ cục bộ vì lý do quyền riêng tư.

Đối với một ứng dụng như vậy, nếu yêu cầu biên dịch theo hướng dẫn của hồ sơ, trước tiên, Dịch vụ ART sẽ cố gắng sử dụng hồ sơ trên đám mây. Nếu không có hồ sơ trên đám mây, Dịch vụ ART sẽ quay lại sử dụng bộ lọc trình biên dịch do pm.dexopt.shared chỉ định.

Nếu quá trình biên dịch được yêu cầu không được hướng dẫn theo hồ sơ, thì thuộc tính này sẽ không có hiệu lực.

pm.dexopt.<reason>.concurrency (mặc định: 1)

Đây là số lệnh gọi dex2oat vì một số lý do biên dịch được xác định trước (first-boot, boot-after-ota, boot-after-mainline-updatebg-dexopt).

Xin lưu ý rằng hiệu quả của tuỳ chọn này được kết hợp với các tuỳ chọn sử dụng tài nguyên dex2oat (dalvik.vm.*dex2oat-threads, dalvik.vm.*dex2oat-cpu-set và hồ sơ tác vụ):

  • dalvik.vm.*dex2oat-threads kiểm soát số lượng luồng cho mỗi lệnh gọi dex2oat, trong khi pm.dexopt.<reason>.concurrency kiểm soát số lượng lệnh gọi dex2oat. Tức là số lượng luồng đồng thời tối đa là tích của hai thuộc tính hệ thống.
  • dalvik.vm.*dex2oat-cpu-set và hồ sơ tác vụ luôn liên kết với mức sử dụng lõi CPU, bất kể số lượng luồng đồng thời tối đa (đã thảo luận ở trên).

Một lệnh gọi dex2oat có thể không sử dụng hết tất cả các lõi CPU, bất kể dalvik.vm.*dex2oat-threads. Do đó, việc tăng số lượng lệnh gọi dex2oat (pm.dexopt.<reason>.concurrency) có thể sử dụng các lõi CPU hiệu quả hơn, để tăng tốc tiến trình tổng thể của dexopt. Điều này đặc biệt hữu ích trong quá trình khởi động.

Tuy nhiên, việc có quá nhiều lệnh gọi dex2oat có thể khiến thiết bị hết bộ nhớ, mặc dù bạn có thể giảm thiểu vấn đề này bằng cách đặt dalvik.vm.dex2oat-swap thành true để cho phép sử dụng tệp hoán đổi. Quá nhiều lệnh gọi cũng có thể gây ra việc chuyển đổi ngữ cảnh không cần thiết. Do đó, bạn nên điều chỉnh cẩn thận con số này theo từng sản phẩm.

pm.dexopt.downgrade_after_inactive_days (mặc định: không đặt)

Nếu bạn đặt tuỳ chọn này, Dịch vụ ART sẽ chỉ dexopt các ứng dụng được sử dụng trong số ngày đã cho gần đây nhất.

Ngoài ra, nếu dung lượng lưu trữ sắp hết, trong quá trình dexopt ở chế độ nền, Dịch vụ ART sẽ hạ cấp bộ lọc trình biên dịch của các ứng dụng không được sử dụng trong một số ngày nhất định gần đây để giải phóng dung lượng. Lý do trình biên dịch là inactive và bộ lọc trình biên dịch được xác định bởi pm.dexopt.inactive. Ngưỡng dung lượng để kích hoạt tính năng này là ngưỡng dung lượng thấp của Trình quản lý bộ nhớ (có thể định cấu hình thông qua chế độ cài đặt chung sys_storage_threshold_percentagesys_storage_threshold_max_bytes, mặc định: 500 MB) cộng với 500 MB.

Nếu bạn tuỳ chỉnh danh sách gói thông qua ArtManagerLocal#setBatchDexoptStartCallback, thì các gói trong danh sách do BatchDexoptStartCallback cung cấp cho bg-dexopt sẽ không bao giờ bị hạ cấp.

pm.dexopt.disable_bg_dexopt (mặc định: false)

Tính năng này chỉ dành cho mục đích thử nghiệm. Thao tác này ngăn Dịch vụ ART lên lịch công việc dexopt ở chế độ nền.

Nếu công việc dexopt ở chế độ nền đã được lên lịch nhưng chưa chạy, thì tuỳ chọn này sẽ không có hiệu lực. Tức là công việc sẽ vẫn chạy.

Trình tự lệnh được đề xuất để ngăn tác vụ dexopt ở chế độ nền chạy là:

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

Dòng đầu tiên ngăn việc lên lịch công việc dexopt ở chế độ nền nếu công việc này chưa được lên lịch. Dòng thứ hai sẽ huỷ lịch biểu công việc dexopt ở chế độ nền (nếu đã lên lịch) và huỷ ngay công việc dexopt ở chế độ nền (nếu đang chạy).

API dịch vụ ART

Dịch vụ ART cung cấp các API Java để tuỳ chỉnh. Các API được xác định trong ArtManagerLocal. Xem Javadoc trong art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java để biết cách sử dụng (nguồn Android 14, nguồn phát triển chưa phát hành).

ArtManagerLocal là một singleton do LocalManagerRegistry nắm giữ. Hàm trợ giúp com.android.server.pm.DexOptHelper#getArtManagerLocal sẽ giúp bạn nhận được hàm đó.

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

Hầu hết các API đều yêu cầu một thực thể của PackageManagerLocal.FilteredSnapshot, chứa thông tin của tất cả ứng dụng. Bạn có thể lấy hàm này bằng cách gọi PackageManagerLocal#withFilteredSnapshot, trong đó PackageManagerLocal cũng là singleton do LocalManagerRegistry lưu giữ và có thể lấy từ com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal.

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

Sau đây là một số trường hợp sử dụng thường gặp của các API.

Kích hoạt dexopt cho một ứng dụng

Bạn có thể kích hoạt dexopt cho bất kỳ ứng dụng nào bất cứ lúc nào bằng cách gọi ArtManagerLocal#dexoptPackage.

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

Bạn cũng có thể truyền lý do dexopt của riêng mình. Nếu làm như vậy, bạn phải đặt rõ ràng lớp ưu tiên và bộ lọc trình biên dịch.

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());
}

Huỷ dexopt

Nếu một thao tác được thực hiện bằng lệnh gọi dexoptPackage, thì bạn có thể truyền tín hiệu huỷ để có thể huỷ thao tác đó vào một thời điểm nào đó. Điều này có thể hữu ích khi bạn chạy dexopt không đồng bộ.

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();

Bạn cũng có thể huỷ tính năng dexopt ở chế độ nền do Dịch vụ ART khởi tạo.

getArtManagerLocal().cancelBackgroundDexoptJob();

Nhận kết quả dexopt

Nếu một thao tác được bắt đầu bằng lệnh gọi dexoptPackage, bạn có thể lấy kết quả từ giá trị trả về.

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

// Process the result here.
...

Dịch vụ ART cũng tự khởi tạo các thao tác dexopt trong nhiều trường hợp, chẳng hạn như dexopt trong nền. Để nghe tất cả kết quả dexopt, cho dù thao tác được bắt đầu bằng lệnh gọi dexoptPackage hay Dịch vụ ART, hãy sử dụng ArtManagerLocal#addDexoptDoneCallback.

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

Đối số đầu tiên xác định xem chỉ đưa các bản cập nhật vào kết quả hay không. Nếu bạn chỉ muốn nghe các gói được cập nhật bằng dexopt, hãy đặt giá trị này thành true.

Đối số thứ hai là trình thực thi lệnh gọi lại. Để thực thi lệnh gọi lại trên cùng một luồng thực hiện dexopt, hãy sử dụng Runnable::run. Nếu bạn không muốn lệnh gọi lại chặn dexopt, hãy sử dụng trình thực thi không đồng bộ.

Bạn có thể thêm nhiều lệnh gọi lại và Dịch vụ ART sẽ thực thi tất cả lệnh gọi lại theo tuần tự. Tất cả lệnh gọi lại sẽ vẫn hoạt động cho tất cả các lệnh gọi trong tương lai, trừ phi bạn xoá các lệnh gọi đó.

Nếu bạn muốn xoá một lệnh gọi lại, hãy giữ lại tham chiếu của lệnh gọi lại đó khi bạn thêm lệnh gọi lại và sử dụng 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);

Tuỳ chỉnh danh sách gói và thông số dexopt

Dịch vụ ART tự khởi tạo các thao tác dexopt trong quá trình khởi động và dexopt ở chế độ nền. Để tuỳ chỉnh danh sách gói hoặc các tham số dexopt cho các thao tác đó, hãy sử dụng 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.
      }
    });

Bạn có thể thêm các mục vào danh sách gói, xoá các mục khỏi danh sách đó, sắp xếp gói hoặc thậm chí sử dụng một danh sách hoàn toàn khác.

Lệnh gọi lại của bạn phải bỏ qua các lý do không xác định vì có thể sẽ có thêm các lý do khác trong tương lai.

Bạn có thể đặt tối đa một BatchDexoptStartCallback. Lệnh gọi lại sẽ vẫn hoạt động cho tất cả các lệnh gọi trong tương lai, trừ phi bạn xoá lệnh gọi đó.

Nếu bạn muốn xoá lệnh gọi lại, hãy sử dụng ArtManagerLocal#clearBatchDexoptStartCallback.

getArtManagerLocal().clearBatchDexoptStartCallback();

Tuỳ chỉnh các thông số của công việc dexopt trong nền

Theo mặc định, công việc dexopt ở chế độ nền sẽ chạy một lần mỗi ngày khi thiết bị ở trạng thái rảnh và đang sạc. Bạn có thể thay đổi điều này bằng cách sử dụng ArtManagerLocal#setScheduleBackgroundDexoptJobCallback.

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

Bạn có thể đặt tối đa một ScheduleBackgroundDexoptJobCallback. Lệnh gọi lại sẽ vẫn hoạt động cho tất cả các lệnh gọi trong tương lai trừ phi bạn xoá lệnh gọi đó.

Nếu bạn muốn xoá lệnh gọi lại, hãy sử dụng ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback.

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Tạm thời tắt dexopt

Mọi thao tác dexopt do Dịch vụ ART khởi tạo sẽ kích hoạt BatchDexoptStartCallback. Bạn có thể tiếp tục huỷ các thao tác để tắt dexopt một cách hiệu quả.

Nếu thao tác mà bạn huỷ là thao tác dexopt ở chế độ nền, thì thao tác đó sẽ tuân theo chính sách thử lại mặc định (30 giây, theo cấp số nhân, giới hạn là 5 giờ).

// 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);

Bạn có thể có tối đa một BatchDexoptStartCallback. Nếu cũng muốn sử dụng BatchDexoptStartCallback để tuỳ chỉnh danh sách gói hoặc các thông số dexopt, bạn phải kết hợp mã này thành một lệnh gọi lại.

// Bad example.

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

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

Thao tác dexopt được thực hiện khi cài đặt ứng dụng không do Dịch vụ ART khởi tạo. Thay vào đó, gói này do trình quản lý gói khởi tạo thông qua lệnh gọi dexoptPackage. Do đó, phương thức này không kích hoạt BatchDexoptStartCallback. Để tắt dexopt khi cài đặt ứng dụng, hãy ngăn trình quản lý gói gọi dexoptPackage.

Ghi đè bộ lọc trình biên dịch cho một số gói nhất định (Android 15 trở lên)

Bạn có thể ghi đè bộ lọc trình biên dịch cho một số gói bằng cách đăng ký một lệnh gọi lại thông qua setAdjustCompilerFilterCallback. Lệnh gọi lại được gọi bất cứ khi nào một gói được chuyển sang dex, bất kể dịch vụ dexopt do Dịch vụ ART thực hiện trong quá trình khởi động và dexopt ở chế độ nền hay bằng lệnh gọi API dexoptPackage.

Nếu một gói không cần điều chỉnh, lệnh gọi lại phải trả về originalCompilerFilter.

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

Bạn chỉ có thể đặt một AdjustCompilerFilterCallback. Nếu muốn sử dụng AdjustCompilerFilterCallback để ghi đè bộ lọc trình biên dịch cho nhiều gói, bạn phải kết hợp mã vào một lệnh gọi lại. Lệnh gọi lại vẫn hoạt động cho mọi lệnh gọi trong tương lai trừ phi bạn xoá lệnh gọi đó.

Nếu bạn muốn xoá lệnh gọi lại, hãy sử dụng ArtManagerLocal#clearAdjustCompilerFilterCallback.

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Các chế độ tuỳ chỉnh khác

Dịch vụ ART cũng hỗ trợ một số cách tuỳ chỉnh khác.

Đặt ngưỡng nhiệt cho dexopt ở chế độ nền

Trình lập lịch biểu công việc thực hiện việc kiểm soát nhiệt của công việc dexopt trong nền. Công việc sẽ bị huỷ ngay lập tức khi nhiệt độ đạt đến THERMAL_STATUS_MODERATE. Bạn có thể điều chỉnh ngưỡng của THERMAL_STATUS_MODERATE.

Xác định xem tính năng dexopt ở chế độ nền có đang chạy hay không

Công việc dexopt ở chế độ nền do Trình lập lịch biểu công việc quản lý và mã công việc của công việc này là 27873780. Để xác định xem công việc có đang chạy hay không, hãy sử dụng API Trình lập lịch biểu công việc.

// 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.
  ...
}

Cung cấp hồ sơ cho dexopt

Để sử dụng hồ sơ nhằm hướng dẫn dexopt, hãy đặt tệp .prof hoặc tệp .dm bên cạnh tệp APK.

Tệp .prof phải là tệp cấu hình ở định dạng tệp nhị phân và tên tệp phải là tên tệp của APK + .prof. Ví dụ:

base.apk.prof

Tên tệp của tệp .dm phải là tên tệp của tệp APK, trong đó đuôi tệp được thay thế bằng .dm. Ví dụ:

base.dm

Để xác minh rằng hồ sơ đang được sử dụng cho dexopt, hãy chạy dexopt bằng speed-profile và kiểm tra kết quả.

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

Dòng đầu tiên xoá tất cả hồ sơ do môi trường thời gian chạy tạo ra (tức là các hồ sơ trong /data/misc/profiles) nếu có, để đảm bảo rằng hồ sơ bên cạnh tệp APK là hồ sơ duy nhất mà Dịch vụ ART có thể sử dụng. Dòng thứ hai chạy dexopt bằng speed-profile và truyền -v để in kết quả chi tiết.

Nếu hồ sơ đang được sử dụng, bạn sẽ thấy actualCompilerFilter=speed-profile trong kết quả. Nếu không, bạn sẽ thấy actualCompilerFilter=verify. Ví dụ:

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}

Sau đây là những lý do thường gặp khiến ART Service không sử dụng hồ sơ này:

  • Hồ sơ có tên tệp không chính xác hoặc không nằm bên cạnh tệp APK.
  • Hồ sơ có định dạng không chính xác.
  • Hồ sơ không khớp với APK. (Checksum trong hồ sơ không khớp với checksum của các tệp .dex trong APK.)