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

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

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

Thuộc tính hệ thống

ART Service hỗ trợ tất cả các dịch vụ tuỳ chọn dex2oat.

Ngoài ra, ART Service hỗ trợ các thuộc tính hệ thống sau:

pm.dexopt.<lý>

Đây là một tập hợp các thuộc tính hệ thống giúp xác định bộ lọc mặc định của trình biên dịch cho mọi lý do biên dịch định sẵn như mô tả trong các trường hợp sử dụng ngoại lệ.

Để biết thêm thông tin, hãy xem 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 những ứng dụng được các ứng dụng khác dùng.

Theo nguyên tắc, Dịch vụ ART thực hiện việc biên dịch theo 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ố một số ứng dụng mà các ứng dụng khác dùng (thông qua <uses-library> hoặc đã tải linh động bằng cách sử dụng Context#createPackageContext với CONTEXT_INCLUDE_CODE). Những ứng dụng như vậy không thể sử dụng vì lý do bảo mậ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ơ, trước tiên, hãy sử dụng Dịch vụ ART cố gắng sử dụng hồ sơ trên đám mây. Nếu hồ sơ trên đám mây không tồn tại, Dịch vụ ART 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 yêu cầu không theo hướng dẫn của hồ sơ, 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 cho quá trình biên dịch xác định trước nhất định lý do (first-boot, boot-after-ota, boot-after-mainline-updatebg-dexopt).

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

  • dalvik.vm.*dex2oat-threads kiểm soát số lượng luồng cho mỗi dex2oat lệnh gọi, 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 tính chất hệ thống.
  • dalvik.vm.*dex2oat-cpu-set và các hồ sơ tác vụ luôn liên kết với lõi CPU mức sử dụng, bất kể số lượng tối đa các luồng đồng thời (được thảo luận) ở trên).

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

Tuy nhiên, việc có quá nhiều lệnh gọi dex2oat có thể khiến thiết bị bị hết bộ nhớ, mặc dù bạn có thể giảm thiểu điều 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ể khiến chuyển đổi ngữ cảnh không cần thiết. Do đó, bạn cần phải theo dõi cẩn thận số liệu này trên cơ sở từng sản phẩm.

pm.dexopt.down_after_activate_days (mặc định: chưa đặt)

Nếu bạn đặt tùy chọn này, thì Dịch vụ ART chỉ loại bỏ các ứng dụng được sử dụng trong phạm vi cung cấp gần đây nhất số ngày.

Ngoài ra, nếu bộ nhớ gần hết, trong quá trình chuyển đổi dexopt ở chế độ nền, Dịch vụ ART hạ cấp bộ lọc trình biên dịch của các ứng dụng không được dùng trong phạm vi ngày gần đây nhất số ngày để giải phóng dung lượng. Lý do trình biên dịch cho thao tác này là inactive, và bộ lọc trình biên dịch được xác định bởi pm.dexopt.inactive. Không gian ngưỡ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 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 các gói thông qua ArtManagerLocal#setBatchDexoptStartCallback, các gói trong danh sách đã cung cấp theo BatchDexoptStartCallback cho bg-dexopt sẽ không bao giờ được 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. Tính năng này ngăn Dịch vụ ART lên lịch ở chế độ nền công việc dexopt.

Nếu công việc dexopt ở chế độ nền đã được lên lịch nhưng chưa chạy, không có tác dụng. Tức là công việc sẽ vẫn chạy.

Một trình tự các lệnh được đề xuất để ngăn công việc dexopt ở chế độ nền đang 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 đó là chưa lên lịch. Dòng thứ hai huỷ lên lịch công việc dexopt ở chế độ nền, nếu việc đó đã được lên lịch và sẽ huỷ công việc dexopt ở chế độ nền ngay lập tức nếu nó đ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 này được định nghĩa trong ArtManagerLocal. Xem Javadoc trong art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java cho (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 người trợ giúp hàm com.android.server.pm.DexOptHelper#getArtManagerLocal giúp bạn để có được nó.

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, nơi 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 điện PackageManagerLocal#withFilteredSnapshot, trong đó PackageManagerLocal cũng nằm một singleton do LocalManagerRegistry nắm 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.

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

Bạn có thể kích hoạt tính năng dexopt cho bất kỳ ứng dụng nào và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, lớp ưu tiên và bộ lọc trình biên dịch phải được đặt rõ ràng.

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ởi lệnh gọi dexoptPackage, bạn có thể truyền một tín hiệu huỷ, cho phép bạn huỷ hoạt động 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 khởi tạo bằng lệnh gọi dexoptPackage, thì bạn có thể nhận được kết quả khỏi 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ự bắt đầu các hoạt động dexopt trong nhiều trường hợp, chẳng hạn như dexopt ở chế độ nền. Để nghe tất cả kết quả dexopt, cho dù thao tác có do lệnh gọi dexoptPackage hoặc do Dịch vụ ART khởi tạo, 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 có chỉ đưa nội dung 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 thành đúng.

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

Nếu bạn muốn loại bỏ 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 dữ liệu 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 bắt đầu các thao tác dexopt trong quá trình khởi động và ở chế độ nền dexopt. Để tuỳ chỉnh danh sách gói hoặc các tham số dexopt cho các thao tác đó, 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ặt hàng vào danh sách gói, xoá các mặt hàng khỏi danh sách, sắp xếp các mặt hàng hoặc thậm chí hãy 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 nguyên nhân không xác định vì có thể có nhiều lý do khác trong lệnh gọi lại tương lai.

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

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 ở chế độ nền

Theo mặc định, công việc dexopt ở chế độ nền sẽ chạy 1 lần/ngày khi thiết bị ở trạng thái rảnh 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ả cuộc gọi trong tương lai trừ phi bạn xoá.

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 sẽ kích hoạt một BatchDexoptStartCallback. Bạn có thể tiếp tục huỷ các hoạt động để vô hiệu hoá hiệu quả dexopt.

Nếu thao tác mà bạn huỷ là thao tác dexopt ở chế độ nền, thao tác đó sẽ tuân theo mặc định thử lại chính sách (30 giây, theo cấp số nhân, giới hạn trong 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 bạn 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ã này vào 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 ART thực hiện Dịch vụ. Thay vào đó, gói này do trình quản lý gói khởi tạo thông qua Cuộc gọi dexoptPackage. Do đó, hàm này không kích hoạt BatchDexoptStartCallback. Để tắt tính năng 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 (thử nghiệm AOSP) 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ý 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 rút khỏi dex, bất kể quá trình dexopt được khởi tạo bằng Dịch vụ ART trong khi khởi động và dexopt ở chế độ nền hoặc 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 bạn muốn sử dụng AdjustCompilerFilterCallback để ghi đè bộ lọc trình biên dịch cho nhiều bạn phải kết hợp mã này thành một lệnh gọi lại. Lệnh gọi lại vẫn còn hoạt động cho tất cả cuộc gọi trong tương lai trừ phi bạn xoá thông tin đó.

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ố tuỳ chỉnh khác.

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

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

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 công việc quản lý và mã công việc là 27873780. Để xác định xem công việc có đang chạy hay không, hãy sử dụng các 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 APK.

Tệp .prof phải là một 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ó phần mở rộng đượ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 với speed-profile rồi 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 (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 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 điển hình khiến Dịch vụ ART 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ơ bị sai định dạng.
  • Hồ sơ không khớp với APK. (Giá trị tổng kiểm trong hồ sơ không khớp với giá trị tổng kiểm của các tệp .dex trong APK.)