ב-Android 12 יש שינויים במערכת הבנייה של קומפילציית AOT של קובצי DEX (dexpreopt) למודולי Java עם תלויות <uses-library>
. במקרים מסוימים, שינויים במערכת הבנייה עלולים לגרום לכך שהבנייה תיכשל. בדף הזה מוסבר איך להתכונן למקרים של שיבושים, ואיך לתקן אותם ולצמצם את ההשפעה שלהם.
Dexpreopt הוא תהליך של קומפילציה מראש של ספריות ואפליקציות Java. התהליך Dexpreopt מתבצע במחשב המארח בזמן הבנייה (בניגוד ל-dexopt, שמתבצע במכשיר). המבנה של יחסי התלות בספרייה משותפת שמשמשים מודול Java (ספרייה או אפליקציה) נקרא הקשר של טוען המחלקות (CLC). כדי להבטיח את הנכונות של dexpreopt, צריך ש-CLCs של זמן הבנייה וזמן הריצה יהיו זהים. ה-CLC של זמן הבנייה הוא מה שהקומפיילר dex2oat משתמש בו בזמן dexpreopt (הוא מתועד בקובצי ODEX), וה-CLC של זמן הריצה הוא ההקשר שבו הקוד שעבר קומפילציה מראש נטען במכשיר.
ה-CLCs של זמן הבנייה וזמן הריצה צריכים להיות זהים, מטעמי דיוק וביצועים. כדי שהנתונים יהיו מדויקים, צריך לטפל בכיתות כפולות. אם התלות בספרייה המשותפת בזמן הריצה שונה מהתלות שבה נעשה שימוש בזמן הקומפילציה, יכול להיות שחלק מהמחלקות ייפתרו בצורה שונה, מה שיגרום לבאגים קלים בזמן הריצה. הביצועים מושפעים גם מהבדיקות בזמן הריצה של מחלקות כפולות.
תרחישים מושפעים לדוגמה
השינויים האלה משפיעים בעיקר על תרחיש השימוש של האתחול הראשון: אם ART מזהה חוסר התאמה בין CLC בזמן הבנייה לבין CLC בזמן הריצה, הוא דוחה את הארטיפקטים של dexpreopt ומריץ במקומם את dexopt. באתחולים הבאים זה לא משנה כי אפשר לבצע dexopt לאפליקציות ברקע ולאחסן אותן בדיסק.
אזורים מושפעים ב-Android
הבעיה הזו משפיעה על כל האפליקציות והספריות של Java שיש להן תלות בזמן ריצה בספריות אחרות של Java. ב-Android יש אלפי אפליקציות, ומאות מהן משתמשות בספריות משותפות. השינוי משפיע גם על השותפים, כי יש להם ספריות ואפליקציות משלהם.
שינויי תוכנה שעלולים לגרום לכשלים
מערכת ה-build צריכה לדעת את התלות של <uses-library>
לפני שהיא יוצרת כללי build של dexpreopt. עם זאת, הוא לא יכול לגשת למניפסט ישירות ולקרוא את התגים <uses-library>
שבו, כי למערכת הבנייה אין הרשאה לקרוא קבצים שרירותיים כשהיא יוצרת כללי בנייה (מסיבות שקשורות לביצועים). בנוסף, יכול להיות שהמניפסט ארוז בתוך קובץ APK או קובץ שנבנה מראש. לכן, המידע <uses-library>
צריך להופיע בקובצי ה-build (Android.bp
או Android.mk
).
בעבר, ART השתמש בפתרון עקיף שהתעלם מתלות בספריות משותפות (שנקרא &-classpath
). הפתרון העקיף הזה לא היה בטוח וגרם לבאגים לא בולטים, ולכן הוא הוסר ב-Android 12.
כתוצאה מכך, מודולים של Java שלא מספקים מידע נכון בקובצי ה-build שלהם עלולים לגרום לשיבושים ב-build (שנגרמים מחוסר התאמה של CLC בזמן ה-build) או לרגרסיות בזמן האתחול הראשון (שנגרמות מחוסר התאמה של CLC בזמן האתחול ואחריו dexopt).<uses-library>
נתיב המיגרציה
כדי לתקן בנייה שנכשלה:
כדי להשבית באופן גלובלי את הבדיקה בזמן הבנייה של מוצר מסוים, מגדירים
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
ב-makefile של המוצר. השינוי הזה מתקן שגיאות ב-build (למעט מקרים מיוחדים שמפורטים בקטע תיקון בעיות). עם זאת, זהו פתרון זמני, והוא עלול לגרום לחוסר התאמה של CLC בזמן האתחול, ואחריו dexopt.
כדי לפתור את הבעיה, צריך להוסיף את פרטי
<uses-library>
לקובצי ה-build של המודולים שנכשלו (פרטים נוספים זמינים במאמר פתרון בעיות). ברוב המודולים, כדי לעשות את זה צריך להוסיף כמה שורות ב-Android.bp
או ב-Android.mk
.משביתים את הבדיקה בזמן הבנייה ואת dexpreopt במקרים הבעייתיים, על בסיס כל מודול. משביתים את dexpreopt כדי לא לבזבז זמן build ואחסון על פריטי מידע שנוצרו בתהליך פיתוח (artifacts) שנדחים בזמן האתחול.
כדי להפעיל מחדש את הבדיקה בזמן הבנייה באופן גלובלי, מבטלים את ההגדרה של
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
שהוגדרה בשלב 1. אחרי השינוי הזה, הבנייה לא אמורה להיכשל (בגלל שלבים 2 ו-3).מתקנים את המודולים שהשבתתם בשלב 3, אחד בכל פעם, ואז מפעילים מחדש את dexpreopt ואת הבדיקה
<uses-library>
. אם צריך, מדווחים על באגים.
ב-Android 12, יש אכיפה של בדיקות <uses-library>
בזמן ה-build.
תיקון בעיות
בקטעים הבאים מוסבר איך לתקן סוגים ספציפיים של שיבושים.
שגיאת build: חוסר התאמה של CLC
מערכת ה-build מבצעת בדיקת עקביות בזמן ה-build בין המידע בקובצי Android.bp
או Android.mk
לבין המניפסט. מערכת ה-build לא יכולה לקרוא את המניפסט, אבל היא יכולה ליצור כללי build כדי לקרוא את המניפסט (לחלץ אותו מקובץ APK אם צריך), ולהשוות בין תגי <uses-library>
במניפסט לבין פרטי <uses-library>
בקובצי ה-build. אם הבדיקה נכשלת,
השגיאה נראית כך:
error: mismatch in the <uses-library> tags between the build system and the manifest:
- required libraries in build system: []
vs. in the manifest: [org.apache.http.legacy]
- optional libraries in build system: []
vs. in the manifest: [com.x.y.z]
- tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
<uses-library android:name="com.x.y.z"/>
<uses-library android:name="org.apache.http.legacy"/>
note: the following options are available:
- to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
- to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
- to fix the check, make build system properties coherent with the manifest
- see build/make/Changes.md for details
כפי שמופיע בהודעת השגיאה, יש כמה פתרונות, בהתאם לדחיפות:
- כדי להגדיר תיקון זמני לכל המוצרים, מגדירים את הערך
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
בקובץ ה-Makefile של המוצר. בדיקת העקביות בזמן הבנייה עדיין מתבצעת, אבל אם הבדיקה נכשלת זה לא אומר שהבנייה נכשלה. במקום זאת, אם הבדיקה נכשלת, מערכת ה-build מורידה את המסנן של dex2oat ל-verify
ב-dexpreopt, מה שמשבית לחלוטין את הקומפילציה של AOT עבור המודול הזה. - כדי לבצע תיקון מהיר וגלובלי בשורת הפקודה, משתמשים במשתנה הסביבה
RELAX_USES_LIBRARY_CHECK=true
. ההשפעה שלו זהה לזו שלPRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, אבל הוא מיועד לשימוש בשורת הפקודה. משתנה הסביבה מבטל את משתנה המוצר. - כדי לתקן את שורש הבעיה, צריך לוודא שמערכת ה-Build מודעת לתגי
<uses-library>
במניפסט. בדיקה של הודעת השגיאה תראה אילו ספריות גורמות לבעיה (כך גם בדיקה שלAndroidManifest.xml
או של המניפסט בתוך קובץ APK שאפשר לבדוק באמצעותaapt dump badging $APK | grep uses-library
).
ל-Android.bp
מודולים:
מחפשים את הספרייה החסרה במאפיין
libs
של המודול. אם היא קיימת, בדרך כלל Soong מוסיף ספריות כאלה באופן אוטומטי, למעט במקרים המיוחדים הבאים:- הספרייה לא מוגדרת כספריית SDK (היא מוגדרת כ-
java_library
ולא כ-java_sdk_library
). - הספרייה כוללת שם ספרייה שונה (במניפסט) משם המודול שלה (במערכת הבנייה).
כדי לפתור את הבעיה באופן זמני, מוסיפים
provides_uses_lib: "<library-name>"
להגדרת הספרייהAndroid.bp
. כדי לפתור את הבעיה לטווח ארוך, צריך להמיר את הספרייה לספריית SDK או לשנות את השם של המודול שלה.- הספרייה לא מוגדרת כספריית SDK (היא מוגדרת כ-
אם השלב הקודם לא פתר את הבעיה, מוסיפים את המחרוזת
uses_libs: ["<library-module-name>"]
לספריות הנדרשות, או את המחרוזתoptional_uses_libs: ["<library-module-name>"]
לספריות האופציונליות, להגדרהAndroid.bp
של המודול. המאפיינים האלה מקבלים רשימה של שמות מודולים. הסדר היחסי של הספריות ברשימה צריך להיות זהה לסדר במניפסט.
ל-Android.mk
מודולים:
בודקים אם בספרייה יש שם ספרייה שונה (בקובץ המניפסט) משם המודול שלה (במערכת הבנייה). אם כן, אפשר לפתור את הבעיה באופן זמני על ידי הוספת
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
לקובץAndroid.mk
של הספרייה, או הוספתprovides_uses_lib: "<library-name>"
לקובץAndroid.bp
של הספרייה (שני המקרים אפשריים כי מודולAndroid.mk
עשוי להיות תלוי בספרייהAndroid.bp
). כדי לפתור את הבעיה לטווח ארוך, צריך לתקן את הבעיה הבסיסית: לשנות את השם של מודול הספרייה.מוסיפים
LOCAL_USES_LIBRARIES := <library-module-name>
לספריות הנדרשות ו-LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
לספריות האופציונליות להגדרת המודולAndroid.mk
. במאפיינים האלה אפשר להזין רשימה של שמות מודולים. הסדר היחסי של הספריות ברשימה צריך להיות זהה לסדר שלהן בקובץ המניפסט.
שגיאת בנייה: נתיב ספרייה לא ידוע
אם מערכת ה-Build לא מוצאת נתיב ל-DEX jar (<uses-library>
) (נתיב בזמן ה-Build במארח או נתיב התקנה במכשיר), בדרך כלל ה-Build נכשל. אם לא נמצא נתיב, יכול להיות שהספרייה מוגדרת בצורה לא צפויה. כדי לפתור את הבעיה ב-build באופן זמני, משביתים את dexpreopt עבור המודול הבעייתי.
Android.bp (מאפייני מודול):
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
},
Android.mk (משתני מודול):
LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false
כדי לבדוק תרחישים שלא נתמכים, צריך לשלוח דוח על באג.
שגיאת בנייה: חסרה תלות בספרייה
ניסיון להוסיף את <uses-library>
X מהמניפסט של מודול Y לקובץ הבנייה של Y עלול לגרום לשגיאת בנייה בגלל התלות החסרה, X.
זו דוגמה להודעת שגיאה במודולים של Android.bp:
"Y" depends on undefined module "X"
זו דוגמה להודעת שגיאה במודולים של Android.mk:
'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it
מקור נפוץ לשגיאות כאלה הוא מצב שבו שם הספרייה שונה מהשם של המודול התואם במערכת ה-build. לדוגמה, אם הרשומה במניפסט <uses-library>
היא com.android.X
, אבל השם של מודול הספרייה הוא רק X
, תופיע שגיאה. כדי לפתור את הבעיה הזו, צריך להודיע למערכת הבנייה שהמודול בשם X
מספק <uses-library>
בשם com.android.X
.
זו דוגמה לספריות Android.bp
(מאפיין מודול):
provides_uses_lib: “com.android.X”,
דוגמה לספריות Android.mk (משתנה מודול):
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
אי-התאמה בין CLC בזמן האתחול
באתחול הראשון, מחפשים ב-logcat הודעות שקשורות לחוסר התאמה של CLC, כמו שמוצג בהמשך:
$ adb wait-for-device && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1
הפלט יכול לכלול הודעות מהסוג שמוצג כאן:
[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...
אם מופיעה אזהרה על חוסר התאמה של CLC, מחפשים פקודת dexopt עבור המודול הפגום. כדי לפתור את הבעיה, מוודאים שהבדיקה בזמן הבנייה של המודול עוברת. אם זה לא עובד, יכול להיות שמדובר במקרה מיוחד שלא נתמך על ידי מערכת ה-build (למשל אפליקציה שמעלה קובץ APK אחר, ולא ספרייה). מערכת ה-build לא מטפלת בכל המקרים, כי בזמן ה-build אי אפשר לדעת בוודאות מה האפליקציה טוענת בזמן הריצה.
הקשר של טוען המחלקות
ה-CLC הוא מבנה דמוי עץ שמתאר את ההיררכיה של טועני המחלקות. מערכת ה-build משתמשת ב-CLC במובן מצומצם (היא כוללת רק ספריות, לא חבילות APK או טועני מחלקות בהתאמה אישית): זהו עץ של ספריות שמייצג סגירה טרנזיטיבית של כל יחסי התלות <uses-library>
של ספרייה או אפליקציה. הרכיבים ברמה העליונה של CLC הם יחסי התלות <uses-library>
הישירים שצוינו במניפסט (נתיב המחלקה). כל צומת בעץ CLC הוא צומת <uses-library>
שיכול להיות שיש לו צומתי משנה משלו מסוג <uses-library>
.
מכיוון שהתלויות של <uses-library>
הן גרף מכוון ללא מעגלים, ולא בהכרח עץ, ה-CLC יכול להכיל כמה תתי-עצים לאותה ספרייה. במילים אחרות, CLC הוא גרף התלות ש "נפרס" לעץ. השכפול מתבצע רק ברמה הלוגית. טועני המחלקות הבסיסיים בפועל לא משוכפלים (בזמן הריצה יש מופע יחיד של טוען מחלקות לכל ספרייה).
ה-CLC מגדיר את סדר החיפוש של הספריות כשמבצעים המרה של מחלקות Java שמשמשות את הספרייה או האפליקציה. סדר החיפוש חשוב כי ספריות יכולות להכיל מחלקות כפולות, והמחלקה מומרת להתאמה הראשונה.
CLC במכשיר (בזמן ריצה)
PackageManager
(ב-frameworks/base
) יוצר CLC לטעינת מודול Java במכשיר. הוא מוסיף את הספריות שמופיעות בתגי <uses-library>
במניפסט של המודול כרכיבי CLC ברמה העליונה.
לכל ספרייה בשימוש, PackageManager
מקבל את כל התלויות שלה <uses-library>
(שמצוינות כתגים במניפסט של הספרייה) ומוסיף CLC מקונן לכל תלות. התהליך הזה ממשיך באופן רקורסיבי עד שכל צמתי העלים בעץ ה-CLC שנבנה הם ספריות ללא <uses-library>
תלות.
PackageManager
מודע רק לספריות משותפות. ההגדרה של 'משותף' בהקשר הזה שונה מהמשמעות הרגילה שלו (כמו ב'משותף' לעומת 'סטטי'). ב-Android, ספריות Java משותפות הן אלה שמופיעות בהגדרות XML שמותקנות במכשיר (/system/etc/permissions/platform.xml
). כל רשומה מכילה את השם של ספרייה משותפת, נתיב לקובץ ה-JAR של ה-DEX שלה ורשימה של יחסי תלות (ספריות משותפות אחרות שהספרייה הזו משתמשת בהן בזמן ריצה, ומצוינות בתגי <uses-library>
במניפסט שלה).
במילים אחרות, יש שני מקורות מידע שמאפשרים ל-PackageManager
ליצור CLC בזמן ריצה: תגי <uses-library>
במניפסט ויחסי תלות של ספריות משותפות בהגדרות XML.
CLC במארח (בזמן הבנייה)
ה-CLC נדרש לא רק כשמטעינים ספרייה או אפליקציה, אלא גם כשמבצעים קומפילציה שלהן. הקומפילציה יכולה להתבצע במכשיר (dexopt) או במהלך הבנייה (dexpreopt). התהליך dexopt מתבצע במכשיר, ולכן יש לו את אותם נתונים כמו PackageManager
(מניפסטים ויחסי תלות של ספריות משותפות).
לעומת זאת, Dexpreopt מתבצע במארח ובסביבה שונה לחלוטין, והוא צריך לקבל את אותו מידע ממערכת ה-build.
לכן, ה-CLC של זמן הבנייה שמשמש את dexpreopt וה-CLC של זמן הריצה שמשמש את PackageManager
הם אותו דבר, אבל הם מחושבים בשתי דרכים שונות.
קוד ה-CLC בזמן הבנייה ובזמן הריצה חייב להיות זהה, אחרת קוד ה-AOT שנוצר על ידי dexpreopt יידחה. כדי לבדוק את השוויון בין CLC בזמן הבנייה לבין CLC בזמן הריצה, מהדר dex2oat מתעד את ה-CLC בזמן הבנייה בקובצי *.odex
(בשדה classpath
בכותרת של קובץ ה-OAT). כדי למצוא את ה-CLC המאוחסן, משתמשים בפקודה הבאה:
oatdump --oat-file=<FILE> | grep '^classpath = '
חוסר התאמה בין CLC בזמן הבנייה לבין CLC בזמן הריצה מדווח ב-logcat במהלך האתחול. מחפשים אותו באמצעות הפקודה הבאה:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
חוסר התאמה פוגע בביצועים, כי הוא מחייב את הספרייה או האפליקציה לעבור dexopt או לפעול ללא אופטימיזציות (לדוגמה, יכול להיות שיהיה צורך לחלץ את הקוד של האפליקציה בזיכרון מתוך ה-APK, וזו פעולה יקרה מאוד).
ספרייה משותפת יכולה להיות אופציונלית או נדרשת. מנקודת המבט של dexpreopt, ספרייה נדרשת צריכה להיות נוכחת בזמן הבנייה (היעדר שלה הוא שגיאת בנייה). ספרייה אופציונלית יכולה להיות קיימת או לא קיימת בזמן הבנייה: אם היא קיימת, היא מתווספת ל-CLC, מועברת ל-dex2oat ומתועדת בקובץ *.odex
. אם ספרייה אופציונלית לא קיימת, המערכת מדלגת עליה ולא מוסיפה אותה ל-CLC. אם יש אי התאמה בין הסטטוס בזמן הבנייה לסטטוס בזמן הריצה (הספרייה האופציונלית קיימת במקרה אחד, אבל לא במקרה השני), אז ה-CLCs בזמן הבנייה ובזמן הריצה לא תואמים והקוד שעבר קומפילציה נדחה.
פרטים מתקדמים על מערכת ה-build (כלי לתיקון מניפסטים)
לפעמים תגי <uses-library>
חסרים במניפסט המקור של ספרייה או אפליקציה. זה יכול לקרות, למשל, אם אחד מיחסי התלות הטרנזיטיביים של הספרייה או האפליקציה מתחיל להשתמש בתג <uses-library>
אחר, והמניפסט של הספרייה או האפליקציה לא מעודכן כך שיכלול אותו.
מערכת Soong יכולה לחשב באופן אוטומטי חלק מתגי <uses-library>
שחסרים בספרייה או באפליקציה מסוימת, כי ספריות ה-SDK נמצאות בסגירת התלות הטרנזיטיבית של הספרייה או האפליקציה. הסגירה נדרשת כי יכול להיות שהספרייה (או האפליקציה) תלויה בספרייה סטטית שתלויה בספריית SDK, ויכול להיות שהיא תלויה שוב באופן טרנזיטיבי דרך ספרייה אחרת.
לא ניתן לחשב את כל התגים של <uses-library>
בדרך הזו, אבל כשזה אפשרי, עדיף לאפשר ל-Soong להוסיף רשומות למניפסט באופן אוטומטי. כך יש פחות סיכוי לטעות והתחזוקה פשוטה יותר. לדוגמה, אם הרבה אפליקציות משתמשות בספרייה סטטית שמוסיפה תלות חדשה, צריך לעדכן את כל האפליקציות, וזה קשה לתחזוקה.<uses-library>