Dexpreopt ו-<uses-library> המחאות

ל-Android 12 יש שינויי מערכת בהרכבת AOT של קבצי DEX (dexpreopt) עבור מודולי Java שיש להם תלות <uses-library> . במקרים מסוימים שינויים אלה במערכת הבנייה יכולים לשבור בנייה. השתמש בדף זה כדי להתכונן לשברים, ועקוב אחר המתכונים בדף זה כדי לתקן ולהפחית אותם.

Dexpreopt הוא תהליך הידור מראש של ספריות ואפליקציות Java. Dexpreopt מתרחש במארח בזמן הבנייה (בניגוד ל- dexopt , שמתרחש במכשיר). המבנה של תלות ספרייה משותפת בשימוש על ידי מודול ג'אווה (ספרייה או אפליקציה) ידוע כ- Context loader (CLC) שלו. כדי להבטיח את נכונות ה-dexpreopt, CLCs זמן הבנייה וזמן הריצה חייבים להתאים. CLC בזמן בנייה הוא מה שהמהדר של dex2oat משתמש בזמן dexpreopt (זה מתועד בקבצי ODEX), ו-CLC בזמן ריצה הוא ההקשר שבו נטען הקוד המוקדם במכשיר.

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

מקרי שימוש מושפעים

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

אזורים מושפעים של אנדרואיד

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

הפסקת שינויים

מערכת ה-build צריכה לדעת את התלות <uses-library> לפני שהיא מייצרת כללי בנייה של dexpreopt. עם זאת, היא לא יכולה לגשת ישירות למניפסט ולקרוא את התגיות <uses-library> שבו, מכיוון שמערכת ה-build אינה מורשית לקרוא קבצים שרירותיים כאשר היא יוצרת כללי בנייה (מסיבות ביצועים). יתר על כן, המניפסט עשוי להיות ארוז בתוך APK או בנוי מראש. לכן, המידע <uses-library> חייב להיות קיים בקבצי ה-build ( Android.bp או Android.mk ).

בעבר השתמש ART בפתרון עוקף שהתעלם מתלות של ספרייה משותפת (הידוע בשם &-classpath ). זה היה לא בטוח וגרם לבאגים עדינים, אז הפתרון לעקיפת הבעיה הוסר באנדרואיד 12.

כתוצאה מכך, מודולי Java שאינם מספקים מידע נכון <uses-library> בקבצי ה-build שלהם עלולים לגרום לשבירת בנייה (הנגרמת מחוסר התאמה של CLC בזמן הבנייה) או רגרסיות בזמן האתחול (הנגרמת על ידי CLC בזמן האתחול חוסר התאמה ואחריו dexopt).

נתיב הגירה

בצע את השלבים הבאים כדי לתקן מבנה שבור:

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

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    בקובץ makefile של המוצר. זה מתקן שגיאות בנייה (למעט מקרים מיוחדים, המפורטים בסעיף תיקון שברים ). עם זאת, זהו פתרון זמני, והוא יכול לגרום לאי התאמה של CLC בזמן האתחול ואחריו dexopt.

  2. תקן את המודולים שנכשלו לפני שהשבתת באופן גלובלי את בדיקת זמן הבנייה על ידי הוספת המידע הדרוש <uses-library> לקבצי הבנייה שלהם (ראה תיקון שברים לפרטים). עבור רוב המודולים זה דורש הוספת כמה שורות ב- Android.bp , או ב- Android.mk .

  3. השבת את בדיקת זמן הבנייה ו-dexpreopt עבור המקרים הבעייתיים, על בסיס מודול. השבת את Dexpreopt כדי שלא תבזבז זמן בנייה ואחסון על חפצים שנדחו בעת האתחול.

  4. הפעל מחדש באופן גלובלי את בדיקת זמן הבנייה על ידי ביטול ההגדרה של PRODUCT_BROKEN_VERIFY_USES_LIBRARIES שהוגדר בשלב 1; הבנייה לא אמורה להיכשל לאחר השינוי הזה (בגלל שלבים 2 ו-3).

  5. תקן את המודולים שהשבתת בשלב 3, אחד בכל פעם, ולאחר מכן הפעל מחדש את dexpreopt ואת הסימון <uses-library> . קובץ באגים במידת הצורך.

בדיקות <uses-library> בזמן הבנייה נאכפות ב-Android 12.

תקן שברים

הסעיפים הבאים מסבירים כיצד לתקן סוגים ספציפיים של שבירה.

שגיאת בנייה: אי התאמה של CLC

מערכת הבנייה מבצעת בדיקת קוהרנטיות בזמן הבנייה בין המידע בקבצי Android.bp או Android.mk לבין המניפסט. מערכת ה-build לא יכולה לקרוא את המניפסט, אבל היא יכולה ליצור כללי בנייה כדי לקרוא את המניפסט (לחלץ אותו מ-APK במידת הצורך), ולהשוות תגיות <uses-library> במניפסט מול מידע <uses-library> ב קבצי הבנייה. אם הבדיקה נכשלת, השגיאה נראית כך:

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 של המוצר. בדיקת הקוהרנטיות בזמן הבנייה עדיין מתבצעת, אך כשל בבדיקה אינו אומר כשל בבנייה. במקום זאת, כשל בבדיקה גורם למערכת הבנייה לשדרג לאחור את מסנן המהדר של dex2oat כדי verify ב-dexpreopt, מה שמשבית את קומפילציה AOT לחלוטין עבור מודול זה.
  • לתיקון מהיר וגלובלי של שורת הפקודה , השתמש במשתנה הסביבה RELAX_USES_LIBRARY_CHECK=true . יש לו אותה השפעה כמו ל- PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , אך מיועד לשימוש בשורת הפקודה. משתנה הסביבה עוקף את משתנה המוצר.
  • לקבלת פתרון לסיבות השורש לתקן את השגיאה, הפוך את מערכת הבנייה מודעה לתגיות <uses-library> במניפסט. בדיקה של הודעת השגיאה מראה אילו ספריות גורמות לבעיה (כמו כן בדיקת AndroidManifest.xml או המניפסט בתוך APK שניתן לבדוק באמצעות ` aapt dump badging $APK | grep uses-library `).

עבור מודולי Android.bp :

  1. חפש את הספרייה החסרה במאפיין libs של המודול. אם זה שם, Soong בדרך כלל מוסיף ספריות כאלה באופן אוטומטי, למעט במקרים מיוחדים אלה:

    • הספרייה אינה ספריית SDK (היא מוגדרת כ- java_library ולא כ- java_sdk_library ).
    • לספרייה יש שם ספרייה שונה (במניפסט) משם המודול שלה (במערכת ה-build).

    כדי לתקן זאת באופן זמני, הוסף provides_uses_lib: "<library-name>" בהגדרת הספרייה Android.bp . לפתרון ארוך טווח, תקן את הבעיה הבסיסית: המר את הספרייה לספריית SDK, או שנה את שם המודול שלה.

  2. אם השלב הקודם לא סיפק פתרון, הוסף uses_libs: ["<library-module-name>"] עבור ספריות נדרשות, או optional_uses_libs: ["<library-module-name>"] עבור ספריות אופציונליות Android.bp הגדרת Android.bp של המודול. מאפיינים אלה מקבלים רשימה של שמות מודולים. הסדר היחסי של הספריות ברשימה חייב להיות זהה לסדר במניפסט.

עבור מודולי Android.mk :

  1. בדוק אם לספרייה יש שם ספרייה שונה (במניפסט) משם המודול שלה (במערכת הבנייה). אם כן, תקן זאת באופן זמני על ידי הוספת LOCAL_PROVIDES_USES_LIBRARY := <library-name> בקובץ Android.mk של הספרייה, או הוסף provides_uses_lib: "<library-name>" בקובץ Android.bp של הספרייה (בשני המקרים אפשריים מכיוון שמודול Android.mk עשוי להיות תלוי בספריית Android.bp ). לפתרון ארוך טווח, תקן את הבעיה הבסיסית: שנה את שם מודול הספרייה.

  2. הוסף LOCAL_USES_LIBRARIES := <library-module-name> עבור ספריות נדרשות; הוסף LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> עבור ספריות אופציונליות להגדרת Android.mk של המודול. מאפיינים אלה מקבלים רשימה של שמות מודולים. הסדר היחסי של הספריות ברשימה חייב להיות זהה לזה במניפסט.

שגיאת בנייה: נתיב ספרייה לא ידוע

אם מערכת הבנייה לא מוצאת נתיב ל <uses-library> DEX jar (או נתיב בזמן בנייה במארח או נתיב התקנה במכשיר), היא בדרך כלל נכשלת בבנייה. כשל במציאת נתיב יכול להצביע על כך שהספרייה מוגדרת בצורה בלתי צפויה. תקן את ה-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 לקובץ ה-build עבור 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

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

הקשר של מטעין מחלקות

ה-CLC הוא מבנה דמוי עץ המתאר את היררכיית מטעני המחלקה. מערכת ה-build משתמשת ב-CLC במובן מצומצם (היא מכסה רק ספריות, לא APKs או מעמיסים מסוג מותאם אישית): זה עץ של ספריות שמייצג סגירה טרנזיטיבית של כל התלות של <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 מודע רק לספריות משותפות. ההגדרה של משותף בשימוש זה שונה מהמשמעות הרגילה שלו (כמו בשיתוף לעומת סטטי). באנדרואיד, ספריות משותפות של Java הן אלו הרשומות בתצורות XML המותקנות במכשיר ( /system/etc/permissions/platform.xml ). כל ערך מכיל שם של ספרייה משותפת, נתיב לקובץ ה-DEX jar שלה ורשימת תלות (ספריות משותפות אחרות שבהן משתמשת זו בזמן ריצה, ומציינת בתגיות <uses-library> במניפסט שלה).

במילים אחרות, ישנם שני מקורות מידע המאפשרים PackageManager לבנות CLC בזמן ריצה: תגיות <uses-library> במניפסט, ותלות ספרייה משותפת בתצורות XML.

CLC במארח (זמן בנייה).

CLC לא נחוץ רק בעת טעינת ספרייה או אפליקציה, היא גם נחוצה בעת קומפילציה. קומפילציה יכולה להתרחש במכשיר (dexopt) או במהלך הבנייה (dexpreopt). מכיוון שה-dexopt מתרחש במכשיר, יש לו את אותו מידע כמו PackageManager (מניפסטים ותלות בספרייה משותפת). Dexpreopt, לעומת זאת, מתרחש במארח ובסביבה שונה לחלוטין, והוא צריך לקבל את אותו מידע ממערכת הבנייה.

לפיכך, ה-CLC בזמן הבנייה בשימוש על ידי dexpreopt ו-CLC בזמן הריצה המשמש את PackageManager הם אותו הדבר, אך מחושבים בשתי דרכים שונות.

CLCs של זמן בנייה וזמן ריצה חייבים להיות תואמים, אחרת הקוד המורכב של AOT שנוצר על ידי dexpreopt יידחה. כדי לבדוק את השוויון של CLCs בזמן בנייה וזמן ריצה, מהדר dex2oat מתעד CLC בזמן בנייה בקבצי *.odex (בשדה classpath של כותרת הקובץ OAT). כדי למצוא את ה-CLC המאוחסן, השתמש בפקודה זו:

oatdump --oat-file=<FILE> | grep '^classpath = '

חוסר התאמה של CLC בזמן בנייה וזמן ריצה מדווח ב-logcat במהלך האתחול. חפש אותו בפקודה הזו:

logcat | grep -E 'ClassLoaderContext [az ]+ mismatch'

חוסר התאמה פוגע בביצועים, מכיוון שהוא מאלץ את הספרייה או האפליקציה לשחרר או לפעול ללא אופטימיזציות (לדוגמה, ייתכן שיהיה צורך לחלץ את קוד האפליקציה בזיכרון מה-APK, פעולה יקרה מאוד).

ספרייה משותפת יכולה להיות אופציונלית או חובה. מנקודת המבט של dexpreopt, ספרייה נדרשת חייבת להיות נוכחת בזמן הבנייה (היעדרה היא שגיאת בנייה). ספרייה אופציונלית יכולה להיות קיימת או נעדרת בזמן הבנייה: אם היא קיימת, היא מתווספת ל-CLC, מועברת ל-dex2oat ומתועדת בקובץ *.odex . אם ספרייה אופציונלית נעדרת, היא מדלגת ולא מתווספת ל-CLC. אם יש חוסר התאמה בין מצב זמן הבנייה לזמן ריצה (הספרייה האופציונלית קיימת במקרה אחד, אך לא במקרה השני), אזי ה-CLC של זמן הבנייה וזמן הריצה אינם תואמים והקוד המהודר נדחה.

פרטי מערכת בנייה מתקדמים (מתקן מניפסט)

לפעמים חסרים תגיות <uses-library> במניפסט המקור של ספרייה או אפליקציה. זה יכול לקרות, למשל, אם אחת מתלות המעבר של הספרייה או האפליקציה מתחילה להשתמש בתג <uses-library> אחר, והמניפסט של הספרייה או האפליקציה לא מתעדכן כך שיכלול אותו.

Soong יכול לחשב חלק מתגי <uses-library> החסרים עבור ספרייה או אפליקציה נתונה באופן אוטומטי, כאשר ספריות ה-SDK בסגירת התלות הטרנזיטיבית של הספרייה או האפליקציה. הסגירה נחוצה מכיוון שהספרייה (או האפליקציה) עשויה להיות תלויה בספרייה סטטית שתלויה בספריית SDK, ואולי עשויה להיות תלויה שוב באופן טרנזיטיבי דרך ספרייה אחרת.

לא ניתן לחשב את כל תגי <uses-library> בצורה זו, אך כשאפשר, עדיף לאפשר ל-Soong להוסיף ערכי מניפסט באופן אוטומטי; זה פחות מועד לשגיאות ומפשט את התחזוקה. לדוגמה, כאשר אפליקציות רבות משתמשות בספרייה סטטית שמוסיפה תלות חדשה <uses-library> , יש לעדכן את כל האפליקציות, דבר שקשה לתחזק.