לפני שמתחילים, כדאי לעיין בסקירה הכללית ברמה גבוהה על שירות 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)
זהו מסנן המהדר (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
ופרופילי המשימות תמיד מגבילים את השימוש בליבות המעבד, ללא קשר למספר המקסימלי של שרשורים בו-זמניים (כפי שצוין למעלה).
יכול להיות שהפעלה אחת של dex2oat לא תעשה שימוש מלא בכל ליבות המעבד, ללא קשר לערך של dalvik.vm.*dex2oat-threads
. לכן, הגדלת מספר ההפעלות של dex2oat (pm.dexopt.<reason>.concurrency
) יכולה לנצל טוב יותר את ליבות המעבד, כדי לזרז את ההתקדמות הכוללת של dexopt. האפשרות הזו שימושית במיוחד במהלך האתחול.
עם זאת, אם יהיו יותר מדי קריאות ל-dex2oat, יכול להיות שהמכשיר ייצא משימוש בגלל מחסור בזיכרון. אפשר לצמצם את הבעיה הזו על ידי הגדרת dalvik.vm.dex2oat-swap
לערך true
כדי לאפשר שימוש בקובץ החלפה. כמו כן, יותר מדי קריאות יכולות לגרום למעברים מיותרים של הקשר. לכן, צריך לשנות את המספר הזה בזהירות בהתאם למוצר.
pm.dexopt.downgrad_after_inactive_days (ברירת מחדל: לא מוגדר)
אם האפשרות הזו מוגדרת, שירות ART יבצע דקס-אופציה רק לאפליקציות שבהן נעשה שימוש במספר הימים האחרון.
בנוסף, אם נפח האחסון נמוך, במהלך dexopt ברקע, שירות ART משדרג לאחור את מסנן המהדר של אפליקציות שלא נעשה בהן שימוש במספר הימים האחרון, כדי לפנות מקום. סיבת המהדר (compiler) היא inactive
, ומסנן המהדר נקבע על ידי pm.dexopt.inactive
. הסף של נפח האחסון שגורם להפעלת התכונה הזו הוא הסף של נפח האחסון הנמוך ב-Storage Manager (ניתן להגדיר אותו דרך ההגדרות הגלובליות 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. אם עושים זאת, צריך להגדיר באופן מפורש את סיווג העדיפות ואת מסנן המהדר (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 ברקע. כדי להאזין לכל התוצאות של 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) נקראת
בכל פעם שמסירים חבילה, לא משנה שה-Dexopt מופעל על ידי שירות 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
. כדי לבדוק אם המשימה פועלת, משתמשים בממשקי 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 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 Service לא משתמש בפרופיל:
- שם הקובץ של הפרופיל שגוי או שהוא לא ליד קובץ ה-APK.
- הפרופיל בפורמט שגוי.
- הפרופיל לא תואם ל-APK. (בדיקות הסיכום בפרופיל לא תואמות לבדיקות הסיכום של קובצי
.dex
בקובץ ה-APK).