הגדרת השירות של ART

לפני שמתחילים, כדאי לעיין בסקירה הכללית של שירות ART.

החל מ-Android 14, שירות ART מטפל בהידור AOT במכשיר לאפליקציות (שנקרא גם dexopt). השירות 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 (ברירת מחדל: מהירות)

זהו מסנן המהדר לגיבוי עבור אפליקציות שמשמשות אפליקציות אחרות.

באופן עקרוני, שירות ART מבצע קומפילציה מונחית-פרופיל (speed-profile) לכל האפליקציות כשאפשר, בדרך כלל במהלך dexopt ברקע. עם זאת, יש אפליקציות שמשמשות אפליקציות אחרות (באמצעות <uses-library> או נטענות באופן דינמי באמצעות Context#createPackageContext עם CONTEXT_INCLUDE_CODE). אפליקציות כאלה לא יכולות להשתמש בפרופילים מקומיים מסיבות שקשורות לפרטיות.

אם מוגשת בקשה לאפליקציה כזו לבצע קומפילציה מונחית פרופיל, שירות ART ינסה קודם להשתמש בפרופיל ענן. אם פרופיל הענן לא קיים, שירות ART חוזר להשתמש במסנן של הקומפיילר שצוין על ידי pm.dexopt.shared.

אם הקומפילציה המבוקשת לא מבוססת על פרופיל, המאפיין הזה לא משפיע.

pm.dexopt.<reason>.concurrency (default: 1)

זהו מספר הקריאות לפונקציה dex2oat מסיבות קומפילציה מסוימות שהוגדרו מראש (first-boot, ‏boot-after-ota, ‏boot-after-mainline-update ו-bg-dexopt).

שימו לב שההשפעה של האפשרות הזו משולבת עם אפשרויות השימוש במשאבים של 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 ופרופילי המשימות תמיד מגבילים את השימוש בליבת המעבד, ללא קשר למספר המרבי של השרשורים בו-זמניים (כפי שמוסבר למעלה).

יכול להיות שקריאה יחידה של dex2oat לא תנצל את כל ליבות המעבד, ללא קשר לערך של dalvik.vm.*dex2oat-threads. לכן, הגדלת מספר הקריאות ל-dex2oat ‏ (pm.dexopt.<reason>.concurrency) יכולה לשפר את השימוש בליבות המעבד, כדי להאיץ את ההתקדמות הכוללת של dexopt. האפשרות הזו שימושית במיוחד במהלך האתחול.

עם זאת, אם יש יותר מדי הפעלות של dex2oat, יכול להיות שלא יישאר מספיק זיכרון במכשיר. אפשר לצמצם את הסיכון הזה על ידי הגדרת dalvik.vm.dex2oat-swap לערך true כדי לאפשר שימוש בקובץ החלפה. יותר מדי הפעלות יכולות גם לגרום למעבר הקשר מיותר. לכן, חשוב להתאים את המספר הזה בקפידה לכל מוצר בנפרד.

pm.dexopt.downgrade_after_inactive_days (ברירת מחדל: לא מוגדר)

אם האפשרות הזו מוגדרת, שירות ART מבצע אופטימיזציה של קובצי DEX רק באפליקציות שהיו בשימוש במהלך מספר הימים האחרונים שצוין.

בנוסף, אם נפח האחסון כמעט מלא, במהלך dexopt ברקע, שירות ART מוריד את רמת המסנן של הקומפיילר של אפליקציות שלא נעשה בהן שימוש במספר הימים האחרון שצוין, כדי לפנות מקום. הסיבה לכך שקומפיילר לא יכול לבצע את הפעולה היא inactive, והמסנן של הקומפיילר נקבע לפי pm.dexopt.inactive. הסף במרחב שגורם להפעלת התכונה הזו הוא הסף הנמוך של נפח האחסון במנהל האחסון (אפשר להגדיר אותו דרך ההגדרות הגלובליות sys_storage_threshold_percentage וsys_storage_threshold_max_bytes, ברירת מחדל: 500MB) בתוספת 500MB.

אם מתאימים אישית את רשימת החבילות דרך ArtManagerLocal#setBatchDexoptStartCallback, החבילות ברשימה שסופקה על ידי BatchDexoptStartCallback עבור bg-dexopt אף פעם לא יורדות בדרגה.

pm.dexopt.disable_bg_dexopt (default: false)

האוסף הזה מיועד לבדיקה בלבד. היא מונעת מ-ART Service לתזמן את העבודה dexopt ברקע.

אם משימת dexopt ברקע כבר מתוזמנת אבל עדיין לא הופעלה, לא תהיה לאפשרות הזו השפעה. כלומר, העבודה עדיין תפעל.

רצף מומלץ של פקודות למניעת ההפעלה של dexopt ברקע:

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

השורה הראשונה מונעת את התזמון של עבודת הרקע dexopt, אם היא עדיין לא תזמנה. השורה השנייה מבטלת את התזמון של עבודת ה-dexopt ברקע, אם היא כבר מתוזמנת, ומבטלת אותה באופן מיידי אם היא פועלת.

ממשקי API של שירות ART

שירות ART חושף ממשקי Java API להתאמה אישית. ממשקי ה-API מוגדרים ב-ArtManagerLocal. אפשר לעיין ב-Javadoc ב-art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java כדי לראות דוגמאות לשימוש (מקור Android 14, מקור פיתוח שלא פורסם).

ArtManagerLocal הוא singleton שנמצא בבעלות של LocalManagerRegistry. פונקציית העזרה com.android.server.pm.DexOptHelper#getArtManagerLocal עוזרת לכם להשיג אותו.

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

ברוב ממשקי ה-API נדרש מופע של PackageManagerLocal.FilteredSnapshot, שמכיל את המידע של כל האפליקציות. אפשר לקבל אותו באמצעות התקשרות אל PackageManagerLocal#withFilteredSnapshot, כאשר PackageManagerLocal הוא גם סינגלטון שנשמר על ידי LocalManagerRegistry ואפשר לקבל אותו מ-com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal.

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

הנה כמה תרחישי שימוש אופייניים בממשקי ה-API.

הפעלת dexopt לאפליקציה

אפשר להפעיל את dexopt לכל אפליקציה בכל שלב על ידי קריאה ל-ArtManagerLocal#dexoptPackage.

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

אפשר גם לבטל את dexopt ברקע, שמופעל על ידי ART Service.

getArtManagerLocal().cancelBackgroundDexoptJob();

קבלת תוצאות dexopt

אם פעולה מופעלת על ידי קריאה ל-dexoptPackage, אפשר לקבל את התוצאה מערך ההחזרה.

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

// Process the result here.
...

שירות ART גם יוזם פעולות dexopt בעצמו בתרחישים רבים, כמו dexopt ברקע. כדי להאזין לכל התוצאות של dexopt, בין אם הפעולה מופעלת על ידי קריאה ל-dexoptPackage או על ידי ART Service, משתמשים ב-ArtManagerLocal#addDexoptDoneCallback.

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

הארגומנט הראשון קובע אם לכלול בתוצאה רק עדכונים. אם רוצים להאזין רק לחבילות שמתעדכנות על ידי dexopt, מגדירים את הערך כ-true.

הארגומנט השני הוא הפונקציה שמבצעת את הקריאה החוזרת. כדי להפעיל את הקריאה החוזרת באותו השרשור שמבצע את dexopt, משתמשים ב-Runnable::run. אם לא רוצים שהקריאה החוזרת תחסום את dexopt, צריך להשתמש ב-executor אסינכרוני.

אפשר להוסיף כמה קריאות חוזרות, ושירות ART יבצע את כולן ברצף. כל ההצעות לחזרה לשיחה יישארו פעילות בכל השיחות העתידיות, אלא אם תסירו אותן.

כדי להסיר קריאה חוזרת (callback), צריך לשמור את ההפניה לקריאה החוזרת כשמוסיפים אותה, ולהשתמש ב-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.
      }
    });

אתם יכולים להוסיף פריטים לרשימת החבילות, להסיר ממנה פריטים, למיין אותה או אפילו להשתמש ברשימה אחרת לגמרי.

פונקציית ה-callback צריכה להתעלם מסיבות לא ידועות, כי יכול להיות שבעתיד יתווספו עוד סיבות.

אפשר להגדיר לכל היותר BatchDexoptStartCallback אחד. האפשרות לקבל שיחה חוזרת תישאר פעילה לכל השיחות העתידיות, אלא אם תמחקו אותה.

כדי לבטל את השיחה החוזרת, משתמשים ב-ArtManagerLocal#clearBatchDexoptStartCallback.

getArtManagerLocal().clearBatchDexoptStartCallback();

התאמה אישית של הפרמטרים של עבודת ה-dexopt ברקע

כברירת מחדל, עבודת הרקע dexopt מופעלת פעם ביום כשהמכשיר לא פעיל ונטען. אפשר לשנות את זה באמצעות ArtManagerLocal#setScheduleBackgroundDexoptJobCallback.

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

אפשר להגדיר לכל היותר ScheduleBackgroundDexoptJobCallback אחד. התקשרות חזרה תישאר פעילה לכל השיחות העתידיות, אלא אם תמחקו אותה.

כדי לבטל את השיחה החוזרת, משתמשים ב-ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback.

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

השבתה זמנית של dexopt

כל פעולת dexopt שמופעלת על ידי ART Service מפעילה 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);

אפשר להוסיף רק BatchDexoptStartCallback אחד. אם רוצים להשתמש גם ב-BatchDexoptStartCallback כדי להתאים אישית את רשימת החבילות או את הפרמטרים של dexopt, צריך לשלב את הקוד לקריאה חוזרת אחת.

// Bad example.

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

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

פעולת dexopt שמתבצעת בהתקנת האפליקציה לא מופעלת על ידי ART Service. במקום זאת, היא מופעלת על ידי מנהל החבילות באמצעות קריאה ל-dexoptPackage. לכן, הוא לא מפעיל את BatchDexoptStartCallback. כדי להשבית את dexopt בהתקנת אפליקציה, צריך למנוע ממנהל החבילות לקרוא ל-dexoptPackage.

עקיפת מסנן הקומפיילר לחבילות מסוימות (Android 15 ואילך)

אפשר לבטל את המסנן של הקומפיילר לחבילות מסוימות על ידי רישום של קריאה חוזרת (callback) דרך setAdjustCompilerFilterCallback. הקריאה החוזרת מופעלת בכל פעם שחבילה עומדת לעבור אופטימיזציה של DEX, לא משנה אם האופטימיזציה הזו מופעלת על ידי ART Service במהלך האתחול ואופטימיזציה של DEX ברקע, או על ידי קריאה ל-API של dexoptPackage.

אם לא צריך לבצע שינוי בחבילה, פונקציית הקריאה החוזרת צריכה להחזיר את הערך 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 ברקע מנוהלת על ידי Job Scheduler, ומזהה המשימה שלה הוא 27873780. כדי לבדוק אם המשימה פועלת, משתמשים בממשקי 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.
  ...
}

הוספת פרופיל ל-dexopt

כדי להשתמש בפרופיל כדי להנחות את dexopt, צריך להציב קובץ .prof או קובץ .dm לצד קובץ ה-APK.

הקובץ .prof צריך להיות קובץ פרופיל בפורמט בינארי, ושם הקובץ צריך להיות שם הקובץ של ה-APK + .prof. לדוגמה,

base.apk.prof

שם הקובץ של קובץ ה-.dm צריך להיות שם הקובץ של ה-APK, עם הסיומת .dm במקום הסיומת המקורית. לדוגמה,

base.dm

כדי לוודא שהפרופיל משמש ל-dexopt, מריצים את dexopt עם speed-profile ובודקים את התוצאה.

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

השורה הראשונה מוחקת את כל הפרופילים שנוצרו על ידי זמן הריצה (כלומר, אלה שב-/data/misc/profiles), אם יש כאלה, כדי לוודא שהפרופיל שליד ה-APK הוא הפרופיל היחיד ששירות ART יכול להשתמש בו. בשורה השנייה מופעל dexopt עם speed-profile, והוא מעביר את -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. (סכומי הביקורת בפרופיל לא תואמים לסכומי הביקורת של קובצי .dex ב-APK).