הגדרת שירות ART

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

החל מ-Android 14, אוסף AOT במכשיר של אפליקציות (שנקראות גם dexopt) מטופלות על ידי ART Service. ART Service הוא חלק מ-ART ולהתאים אותו אישית באמצעות מאפייני המערכת וממשקי API.

מאפייני המערכת

שירות ART תומך בכל אפשרויות dex2oat.

בנוסף, ART Service תומך במאפייני המערכת הבאים:

pm.dexopt.<reason>

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

זהו מסנן המהדר (compiler) החלופי לאפליקציות שמשמשות אפליקציות אחרות.

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

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

אם האוסף המבוקש לא מנוהל על ידי פרופיל, למאפיין הזה אין השפעה.

pm.dexopt.<reason>.concurrency (ברירת מחדל: 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 ופרופילים של המשימות תמיד קשורים לליבת המעבד (CPU) בשימוש, ללא קשר למספר המקסימלי של שרשורים בו-זמנית (כפי שצוין שלמעלה).

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

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

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

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

בנוסף, אם נפח האחסון כמעט נמוך, במהלך dexopt ברקע, שירות ART משדרג לאחור את מסנן המהדר של אפליקציות שלא נעשה בהן שימוש עד מספר ימים כדי לפנות מקום. סיבת המהדר (compiler) באפשרות הזו היא 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 (ברירת המחדל: false)

האוסף הזה מיועד לבדיקה בלבד. הפעולה מונעת משירות ART לתזמן את הרקע עבודת פירוק.

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

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

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

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

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

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

ArtManagerLocal הוא סינגלטון המוחזק על ידי 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());
}

אתם יכולים גם להעביר את הסיבה שלכם לבדיקה. אם תעשו זאת, סיווג העדיפות יש להגדיר את מסנן המהדר (compiler) באופן מפורש.

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.

getArtManagerLocal().cancelBackgroundDexoptJob();

קבלת תוצאות של dexopt

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

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

// Process the result here.
...

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

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

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

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

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

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

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

כדי למחוק את הקריאה החוזרת, צריך להשתמש ArtManagerLocal#clearBatchDexoptStartCallback

getArtManagerLocal().clearBatchDexoptStartCallback();

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

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

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

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

כדי למחוק את הקריאה החוזרת, צריך להשתמש ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

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

כל פעולת dexopt שמתרחשת על ידי שירות ART מפעילה 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 שירות. במקום זאת, מנהל החבילות מופעל באמצעות שיחת dexoptPackage. לכן, היא לא מפעילה BatchDexoptStartCallback. כדי להשבית את dexopt בהתקנת אפליקציה, מנע מנהל חבילות מ-dexoptPackage.

שינוי מסנן המהדר בחבילות מסוימות (Android 15 ואילך)

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

אם לא צריך לבצע התאמה בחבילה, צריך להחזיר את הקריאה החוזרת originalCompilerFilter

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

אפשר להגדיר רק AdjustCompilerFilterCallback אחד. אם רוצים להשתמש AdjustCompilerFilterCallback כדי לשנות את מסנן המהדר של מספר מיקומים חבילות, עליך לשלב את הקוד לקריאה חוזרת אחת. הקריאה החוזרת (callback) נשארה פעיל לכל השיחות העתידיות, אלא אם תמחקו את ההגדרה.

כדי למחוק את הקריאה החוזרת, צריך להשתמש ArtManagerLocal#clearAdjustCompilerFilterCallback

getArtManagerLocal().clearAdjustCompilerFilterCallback();

התאמות אישיות אחרות

שירות ART תומך גם בכמה התאמות אישיות אחרות.

הגדרת סף הטמפרטורה של dexopt ברקע

בקרת התרמית של משימת dexopt ברקע מתבצעת על ידי Job Scheduler. המשימה מתבטלת מיד כשהטמפרטורה תגיע לאפס 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 Service לא משתמש בפרופיל:

  • לפרופיל יש שם קובץ שגוי או שהוא לא מופיע לצד ה-APK.
  • הפורמט של הפרופיל שגוי.
  • הפרופיל לא תואם ל-APK. (סיכומי הביקורת בפרופיל לא תואם לסיכומי הביקורת של .dex קובצי ה-APK).