אימות התנהגות האפליקציה בזמן הריצה של Android (ART)

זמן הריצה של Android (ART) הוא זמן הריצה שמוגדר כברירת מחדל למכשירים עם מערכת Android 5.0 (רמת API 21) ואילך. בסביבת זמן הריצה הזו יש כמה תכונות שמשפרים את הביצועים והחלקות של הפלטפורמה והאפליקציות ל-Android. מידע נוסף על התכונות החדשות של ART זמין במאמר היכרות ART.

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

טיפול בבעיות שקשורות לאיסוף אשפה (GC)

במסגרת Dalvik, לעיתים קרובות אפליקציות עדיף לבצע קריאה מפורשת System.gc() כדי לשלוח הודעה לגבי איסוף אשפה (GC). זה צריך להיות הרבה פחות נחוץ עם ART, במיוחד אם אתם מפעילים איסוף אשפה כדי למנוע סוג של GC_FOR_ALLOC אירועים או לצמצם את הפרגמנטציה. אפשר לבדוק איזה סביבת זמן ריצה נמצאת בשימוש באמצעות התקשרות אל System.getProperty("java.vm.version"). אם ART נמצא בשימוש, ערך הנכס "2.0.0" ומעלה.

ב-ART נעשה שימוש באיסוף העתקה בו-זמנית (CC), שדוחס בו-זמנית את ערימת Java. לכן יש להימנע משימוש בשיטות שלא תואמים לדחיסת GC (למשל, שמירת מצביעים לאובייקט בנתוני המכונה). זה חשוב במיוחד לאפליקציות שמשתמשות במודלים של Java Native Interface (JNI). למידע נוסף, ראו מניעת בעיות הקשורות ל-JNI.

מניעת בעיות JNI

ה-JNI של ART הוא במידה מסוימת מחמירה יותר מזו של Dalvik. זה רעיון טוב במיוחד כדי להשתמש במצב CheckJNI כדי לזהות בעיות נפוצות. אם האפליקציה משתמשת ב-C/C++ מומלץ לעיין במאמר הבא:

ניפוי באגים Android JNI עם CheckJNI

המערכת בודקת אם יש בעיות באיסוף אשפה בקוד JNI

אוסף העתקה בו-זמנית (CC) יכול להעביר אובייקטים בזיכרון לצורך דחיסה. אם אתם משתמשים בקוד C/C++ לבצע פעולות שלא תואמות לדחיסת GC. שיפרנו את CheckJNI לזהות כמה בעיות פוטנציאליות (כפי שמתואר ב-JNI) שינויים בהפניות מקומיות ב-ICS).

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

  • אם מבצעים שינויים ברכיבי המערך המוחזרים, מתאימה את פונקציית Release...ArrayElements() כשמסיימים, כדי לוודא שהשינויים שביצעתם מועתקים בצורה תקינה חזרה אל באובייקט מערך.
  • כשמשחררים את הרכיבים של מערך הזיכרון, צריך להשתמש בהתאם לשינויים שביצעתם:
    • אם לא ביצעתם שינויים ברכיבי המערך, השתמשו מצב JNI_ABORT, שמשחרר את הזיכרון ללא העתקה משתנה בחזרה לאובייקט המערך הבסיסי.
    • אם ביצעתם שינויים במערך, ואתם לא צריכים את ההפניה עוד, משתמשים בקוד 0 (מעדכן את אובייקט המערך ומפנה את העותק של הזיכרון).
    • אם ביצעתם שינויים במערך שרוצים לשמור, כדי לשמור את העותק של המערך, צריך להשתמש בפונקציה JNI_COMMIT (שמתעדכנת את אובייקט המערך הבסיסי ושומר את העותק).
  • כשתתבצע שיחה אל Release...ArrayElements(), ההחזר יהיה זהה המצביע שהוחזר במקור על ידי Get...ArrayElements(). עבור לדוגמה, לא בטוח להגדיל את הסמן המקורי (כדי לסרוק את החזירו רכיבי מערך) ולאחר מכן מעבירים את הסמן המוגדל Release...ArrayElements() העברת הסמן השונה עלולה לגרום את הזיכרון הלא נכון שיש לפנות, מה שגורם לפגיעה בזיכרון.

טיפול בשגיאות

ה-JNI של ART גורם לשגיאות במספר מקרים שבהם Dalvik לא עושה זאת. (פעם אחת שוב, תוכלו לאתר הרבה מקרים כאלה באמצעות בדיקה באמצעות CheckJNI).

לדוגמה, אם נשלחת קריאה אל RegisterNatives באמצעות method אינה קיימת (אולי מפני שהשיטה הוסרה על ידי כלי כמו ProGuard), ART מריץ עכשיו NoSuchMethodError:

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

ART רושם גם שגיאה (גלוי ב-Logcat) אם RegisterNatives נקראו ללא שיטות:

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

בנוסף, ה-JNI פונקציה GetFieldID() וגם GetStaticFieldID() זורקת עכשיו כמו שצריך את NoSuchFieldError במקום פשוט להחזיר null. באופן דומה, GetMethodID() וגם GetStaticMethodID() זורקת עכשיו את NoSuchMethodError כמו שצריך. זה עלול להוביל לכשלים ב-CheckJNI בגלל החריגים שלא טופלו או חריגים המוענקים לקוראי Java של קוד נייטיב. ככה חשוב במיוחד לבדוק אפליקציות תואמות ART באמצעות מצב CheckJNI.

ART מצפה למשתמשים בשיטות JNI CallNonvirtual...Method() (למשל CallNonvirtualVoidMethod()) כדי להשתמש בהצהרה של השיטה ולא מחלקה משנית, כפי שנדרש במפרט ה-JNI.

מניעת בעיות בגודל המקבץ

ל-Dalvik היו מקבצים נפרדים לקוד של Native ולקוד Java, עם ברירת מחדל של Java גודל ערימה של 32KB וגודל ערימה מקורי כברירת מחדל של 1MB. ל-ART יש איחוד מאוחד כדי לשפר את הרשות המוניציפאלית. בדרך כלל, סטאק Thread של ART הגודל אמור להיות זהה לזה של Dalvik. אבל אם מגדירים במפורש להגדיר גדלים של מקבצים, יכול להיות שתצטרכו לחזור על הערכים האלה באפליקציות שפועלות שעון ארגנטינה.

  • ב-Java, בודקים קריאות ל-constructor של Thread שמציינות סטאק מפורש גודל. לדוגמה, צריך להגדיל את הקובץ אם הערך StackOverflowError קיים.
  • ב-C/C++, יש לבדוק את השימוש ב-pthread_attr_setstack() pthread_attr_setstacksize() לשרשורים שמריצים גם קוד Java דרך JNI. הנה דוגמה לשגיאה שתועדה כשאפליקציה מנסה לקרוא ל-JNI AttachCurrentThread() כשה-pthread קטן מדי:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

שינויים במודל אובייקט

Dalvik אישר באופן שגוי למחלקות משנה לעקוף שיטות מסוג חבילה פרטית. ART מנפיק אזהרה במקרים כאלו:

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

אם אתם מתכוונים לשנות שיטה של כיתה בחבילה אחרת, צריך להצהיר על כ-public או protected.

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

Class.getSuperclass() == java.lang.Object.class

במקום להמשיך עד שה-method תחזיר null.

שרת ה-proxy InvocationHandler.invoke() מקבל עכשיו null אם לא ולא מערך ריק. ההתנהגות הזו תועדה בעבר, אבל לא מטופל כראוי Dalvik. בגרסאות הקודמות של Mockito יש קשיים עם לכן עליך להשתמש בגרסה מעודכנת של Mockito כשמבצעים בדיקה באמצעות ART.

פתרון בעיות בהידור מסוג AOT

קומפילציית Java מסוג Ahead-Of-Time (AOT) של ART אמור לפעול בכל גרסאות Java הרגילות ההידור מתבצע על ידי ART כלי dex2oat; אם אתם נתקלים בבעיות שקשורות אל dex2oat בזמן ההתקנה, מודיעים לנו (מידע נוסף זמין בקטע דיווח על בעיות) כדי שנוכל לפתור אותן במהירות ככל האפשר. שתי בעיות שכדאי לשים לב אליהן:

  • ART מבצע אימות בייטקוד קפדני יותר בזמן ההתקנה מאשר Dalvik. הקוד שהופק על ידי כלי ה-build של Android אמור להיות תקין. אבל, חלק כלים לאחר עיבוד (במיוחד כלים שמבצעים ערפול קוד) עלולים להפיק קבצים לא חוקיים שנתמכים על ידי Dalvik אבל נדחו על ידי ART. היינו לעבוד עם ספקי כלים כדי למצוא ולפתור בעיות כאלה. במקרים רבים, קבלת הגרסאות האחרונות של הכלים ויצירה מחדש של קובצי ה-DEX יכולים לתקן או בעיות.
  • דוגמאות לבעיות אופייניות שמסומנות על ידי מאמת ART:
    • תהליך בקרה לא חוקי
    • לא מאוזן monitorenter/monitorexit
    • גודל רשימת סוגי הפרמטרים באורך 0-0
  • לאפליקציות מסוימות יש תלות בקובץ .odex שמותקן בפורמט /system/framework, /data/dalvik-cache, או בספריית הפלט האופטימלית של DexClassLoader. האלה הקבצים הם עכשיו קובצי ELF ולא צורה מורחבת של קובצי DEX. בזמן ש-ART מנסה לפעול בהתאם לאותם כללי מתן שמות ונעילה כמו Dalvik, אפליקציות לא צריכות להיות תלויות בפורמט הקובץ; הפורמט כפוף לשינויים ללא הודעה מוקדמת.

    הערה: ב-Android 8.0 (רמת API 26) וב- גבוהה יותר, ספריית הפלט האופטימלית DexClassLoader הוצא משימוש. מידע נוסף זמין במסמכי התיעוד של DexClassLoader() constructor.

בעיות בדיווח

אם נתקלים בבעיות שלא נובעות מבעיות JNI באפליקציה, צריך לדווח על באמצעות הכלי למעקב אחר בעיות בפרויקט קוד פתוח של Android, בכתובת https://code.google.com/p/android/issues/list. הכללת "adb bugreport" וקישור לאפליקציה ב-Google חנות Play, אם היא זמינה. אחרת, אם אפשר, יש לצרף APK שמשחזר את חבילת ה-APK הבעיה. חשוב לזכור שהבעיות (כולל קבצים מצורפים) גלויות לכולם גלוי.