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

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

Kể từ Android 14, quá trình biên dịch AOT trên thiết bị cho các ứ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 và API hệ thống.

Thuộc tính hệ thống

Dịch vụ ART hỗ trợ tất cả các lựa chọn dex2oat có liên quan.

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

pm.dexopt.<reason>

Đây là một nhóm 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 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 tiêu 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à những ứng dụng khác sử dụng.

Về nguyên tắc, Dịch vụ ART sẽ thực hiện quá trình biên dịch có hướng dẫn về hồ sơ (speed-profile) cho tất cả cá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). Những ứng dụng như vậy không thể sử dụng hồ sơ cục bộ vì lý do 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ồ sơ, Dịch vụ ART sẽ cố gắng sử dụng hồ sơ đám mây trước. 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ần gọi dex2oat cho 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 ứng của lựa chọn này được kết hợp với các lựa 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 tối đa các luồng đồng thời 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 mức sử dụng lõi CPU, bất kể số lượng tối đa các luồng đồng thời (đã 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 dexopt tổng thể. Đ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. Việc gọi quá nhiều lần 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 số này cho từng sản phẩm.

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

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

Ngoài ra, nếu bộ nhớ gần đầy, 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 những ứng dụng không được dùng trong số ngày đã cho gần đây nhất để giải phóng dung lượng. Lý do trình biên dịch cho việc này là inactive và bộ lọc trình biên dịch được xác định bằng pm.dexopt.inactive. Ngưỡng không gian để kích hoạt tính năng này là ngưỡng không gian 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 thêm 500 MB.

Nếu bạn tuỳ chỉnh danh sách các 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)

Nội dung này chỉ dành cho mục đích thử nghiệm. Thao tác này sẽ ngăn Dịch vụ ART lên lịch cho tác vụ 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ì lựa chọn này sẽ không có hiệu lực. Tức là công việc sẽ vẫn chạy.

Sau đây là một chuỗi lệnh được đề xuất để ngăn tác vụ dexopt trong nền chạy:

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 cho lệnh dexopt ở chế độ nền nếu lệnh này chưa được lên lịch. Dòng thứ hai huỷ lên lịch 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ác 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ữ. Một hàm trợ giúp com.android.server.pm.DexOptHelper#getArtManagerLocal sẽ giúp bạn lấy được thông tin này.

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

Hầu hết các API đều yêu cầu một phiên bản PackageManagerLocal.FilteredSnapshot, lưu giữ thông tin của tất cả các ứng dụng. Bạn có thể nhận được bằng cách gọi PackageManagerLocal#withFilteredSnapshot, trong đó PackageManagerLocal cũng là một singleton do LocalManagerRegistry 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 điển hình của các API này.

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 bạn 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 khởi tạo bằng lệnh gọi dexoptPackage, bạn có thể truyền tín hiệu huỷ. Tín hiệu này cho phép bạn huỷ thao tác tại 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ỷ 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ể nhận được 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. Để theo dõi tất cả kết quả dexopt, cho dù thao tác được bắt đầu bằng lệnh gọi dexoptPackage hay bằng 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ỉ bao gồm các bản cập nhật trong kết quả hay không. Nếu bạn chỉ muốn nghe các gói do dexopt cập nhật, hãy đặt giá trị này thành true.

Đối số thứ hai là trình thực thi của 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 một 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ả các lệnh gọi lại đó theo trình tự. Tất cả các lệnh gọi lại sẽ vẫn hoạt động cho tất cả các cuộc gọi trong tương lai, trừ phi bạn xoá chúng.

Nếu bạn muốn xoá một lệnh gọi lại, hãy giữ lại thông tin 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à các thông số dexopt

Dịch vụ ART tự khởi động 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 những thao tác đó, hãy 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 danh sách 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 lý do 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 cuộc gọi trong tương lai, trừ phi bạn xoá lệnh gọi lạ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 tham số của tác vụ 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 chờ và đang sạc. Bạn có thể thay đổi chế độ 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 cuộc gọi trong tương lai, trừ phi bạn xoá lệnh gọi lạ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 vô hiệu hoá dexopt

Mọi thao tác dexopt do Dịch vụ ART khởi tạo đều 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à dexopt trong nền, thì thao tác đó sẽ tuân theo chính sách mặc định về việc thử lại (30 giây, theo cấp số nhân, tối đa 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 chỉ 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 tham số dexopt, bạn phải kết hợp mã 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 đó, hoạt động này được trình quản lý gói khởi tạo thông qua lệnh gọi dexoptPackage. Do đó, nó 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 (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 nhất định 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 sẽ được dexopt, bất kể dexopt được ART Service khởi tạo trong quá trình khởi động và dexopt 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ể thiết lập 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ã thành một lệnh gọi lại. Lệnh gọi lại vẫn hoạt động cho tất cả các cuộc gọi trong tương lai, trừ phi bạn xoá lệnh gọi lạ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 tuỳ chỉnh khác

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

Đặt ngưỡng nhiệt cho dexopt nền

Trình lập lịch biểu công việc sẽ thực hiện việc kiểm soát nhiệt của tác vụ 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 dexopt nền có đang chạy hay không

Công việc dexopt ở chế độ nền do Trình lập lịch 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 Job Scheduler 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.
  ...
}

Cung cấp một hồ sơ cho dexopt

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

Tệp .prof phải là tệp cấu hình ở định dạng 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 APK có đuôi được thay thế bằng .dm. Ví dụ:

base.dm

Để xác minh rằng hồ sơ đang được 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 sẽ xoá tất cả hồ sơ do thời gian chạy tạo ra (tức là những hồ sơ trong /data/misc/profiles), nếu có, để đảm bảo rằng hồ sơ bên cạnh 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 biểu tượng 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 Dịch vụ ART không sử dụng hồ sơ:

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