לפני שמתחילים, כדאי לעיין בסקירה הכללית על שירות ART.
החל מגרסה 14 של Android, שירות 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 (ברירת המחדל: speed)
זהו מסנן המהדר החלופי לאפליקציות שבהן משתמשות אפליקציות אחרות.
באופן עקרוני, כשהדבר אפשרי, שירות 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
ופרופילי המשימות תמיד מגבילים את השימוש בליבות המעבד, ללא קשר למספר המקסימלי של שרשורים בו-זמניים (כפי שצוין למעלה).
יכול להיות שהפעלה אחת של dex2oat לא תעשה שימוש מלא בכל ליבות המעבד, ללא קשר לערך של dalvik.vm.*dex2oat-threads
. לכן, הגדלת מספר ההפעלות של dex2oat (pm.dexopt.<reason>.concurrency
) יכולה לנצל טוב יותר את ליבות המעבד, כדי לזרז את ההתקדמות הכוללת של dexopt. האפשרות הזו שימושית במיוחד במהלך האתחול.
עם זאת, אם יהיו יותר מדי קריאות ל-dex2oat, יכול להיות שהזיכרון במכשיר יגמר. אפשר לצמצם את הבעיה הזו על ידי הגדרת dalvik.vm.dex2oat-swap
ל-true
כדי לאפשר שימוש בקובץ החלפה. יותר מדי קריאות יכולות גם לגרום למעברים מיותרים בהקשר. לכן, צריך לשנות את המספר הזה בזהירות בהתאם למוצר.
pm.dexopt.downgrade_after_inactive_days (ברירת המחדל: לא מוגדר)
אם האפשרות הזו מוגדרת, שירות ART יבצע דקס-אופציה רק לאפליקציות שבהן נעשה שימוש במספר הימים האחרון.
בנוסף, אם נפח האחסון נמוך, במהלך 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 (ברירת המחדל: false)
זהו קוד לבדיקה בלבד. הוא מונע מ-ART Service לתזמן את המשימה של dexopt ברקע.
אם כבר תזמנתם את המשימה dexopt ברקע אבל היא עדיין לא הופעלה, לאפשרות הזו אין השפעה. כלומר, המשימה עדיין תפעל.
רצף הפקודות המומלץ למניעת הפעלת המשימה dexopt ברקע הוא:
setprop pm.dexopt.disable_bg_dexopt true
pm bg-dexopt-job --disable
השורה הראשונה מונעת את תזמון המשימה dexopt ברקע, אם היא עדיין לא תוזמנה. השורה השנייה מבטלת את התזמון של משימת ה-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.
getArtManagerLocal().cancelBackgroundDexoptJob();
הצגת תוצאות dexopt
אם הפעולה מופעלת על ידי קריאה ל-dexoptPackage
, אפשר לקבל את התוצאה מהערך המוחזר.
DexoptResult result;
try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
result = getArtManagerLocal().dexoptPackage(...);
}
// Process the result here.
...
שירות ART גם מתחיל פעולות dexopt בעצמו בתרחישים רבים, כמו dexopt ברקע. כדי לשמוע את כל התוצאות של dexopt, בין שהפעולה מופעלת על ידי קריאה ל-dexoptPackage
ובין שהיא מופעלת על ידי שירות ART, צריך להשתמש ב-ArtManagerLocal#addDexoptDoneCallback
.
getArtManagerLocal().addDexoptDoneCallback(
false /* onlyIncludeUpdates */,
Runnable::run,
(result) -> {
// Process the result here.
...
});
הארגומנט הראשון קובע אם התוצאה תכלול רק עדכונים. אם רוצים להאזין רק לחבילות שמעודכנות על ידי dexopt, צריך להגדיר את הערך כ-true.
הארגומנט השני הוא מבצע ההפעלה החוזרת. כדי להריץ את פונקציית ה-callback באותו חוט שבו מתבצעת ה-dexopt, משתמשים ב-Runnable::run
. אם אתם לא רוצים שהקריאה החוזרת (callback) תחסום את dexopt, השתמשו במבצע אסינכרוני.
אפשר להוסיף כמה קריאות חזרה, ו-ART Service יבצע את כולן ברצף. כל הקריאות החוזרות יישארו פעילות לכל השיחות העתידיות, אלא אם תסירו אותן.
אם רוצים להסיר קריאה חוזרת, שומרים את ההפניה לקריאה החוזרת כשמוסיפים אותה ומשתמשים ב-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
אחד לכל היותר. הקריאה החוזרת תיוותר פעילה לכל השיחות העתידיות, אלא אם תמחקו אותה.
כדי לבטל את הקריאה החוזרת, צריך להשתמש ב-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 ואילך)
אפשר לשנות את ברירת המחדל של מסנן המהדר עבור חבילות מסוימות על ידי רישום של פונקציית קריאה חוזרת (callback) דרך setAdjustCompilerFilterCallback
. פונקציית ה-callback נקראת בכל פעם שצריך לבצע דקס-אופטיקציה של חבילה, בין שהדקס-אופטיקציה מופעלת על ידי שירות ART במהלך האתחול ובדקס-אופטיקציה ברקע, ובין שהיא מופעלת על ידי קריאה ל-API dexoptPackage
.
אם אין צורך לבצע שינויים בחבילה, הפונקציה להחזרת קריאה (callback) צריכה להחזיר את הערך 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
. כדי לבדוק אם המשימה פועלת, משתמשים בממשקי ה-API של Job Scheduler.
// 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 Service יכול להשתמש בו. בשורה השנייה מפעילים את 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).