בעיות נפוצות ופתרונות

במסמך הזה מופיעה רשימה חלקית של הבעיות הכי נפוצות שאתם עשויים להיתקל בהן כשאתם משתמשים ב-NDK, ופתרונות לבעיות האלה (אם יש כאלה).

שימוש ב-_FILE_OFFSET_BITS=64 עם רמות API ישנות יותר

לפני הכותרות המאוחדות, ה-NDK לא תמך ב-_FILE_OFFSET_BITS=64. אם הגדרתם אותה כשבניתם את האפליקציה, היא התעלמה ממנה בשקט. האפשרות _FILE_OFFSET_BITS=64 נתמכת עכשיו עם כותרות מאוחדות, אבל בגרסאות ישנות של Android, רק מעט מ-API‏ off_t היו זמינים כגרסת off64_t. לכן, השימוש בתכונה הזו עם רמות API ישנות יותר מוביל לכך שפחות פונקציות זמינות.

הבעיה הזו מוסברת בפירוט בפוסט בבלוג בנושא r16 ובמסמכי bionic.

בעיה: ה-build מבקש ממשקי API שלא קיימים ב-minSdkVersion.

הפתרון: משביתים את _FILE_OFFSET_BITS=64 או מגדילים את minSdkVersion.

הגדרה לא מוצהרת או מרומזת של mmap

יכול להיות שתופיע השגיאה הבאה ב-C++‎:

שגיאה: נעשה שימוש במזהה שלא הוגדר 'mmap'

או השגיאה הבאה ב-C:

warning: implicit declaration of function 'mmap' is invalid in C99

השימוש ב-_FILE_OFFSET_BITS=64 מורה לספריית C להשתמש ב-mmap64 במקום ב-mmap. mmap64 לא היה זמין עד android-21. אם הערך של minSdkVersion נמוך מ-21, ספריית C לא מכילה את mmap שתואם ל-_FILE_OFFSET_BITS=64, ולכן הפונקציה לא זמינה.

‫minSdkVersion set higher than device API level

לרמת ה-API שאתם יוצרים באמצעות NDK יש משמעות שונה מאוד מזו של compileSdkVersion ב-Java. רמת ה-API של NDK היא רמת ה-API המינימלית שנתמכת באפליקציה. ב-ndk-build, זו ההגדרה APP_PLATFORM. ב-CMake, זה -DANDROID_PLATFORM.

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

בעיה: רמת ה-API של NDK גבוהה מרמת ה-API שנתמכת במכשיר.

פתרון: צריך להגדיר את רמת ה-API של NDK ‏ (APP_PLATFORM) לגרסה המינימלית של Android שהאפליקציה תומכת בה.

מערכת build הגדרה
ndk-build APP_PLATFORM
CMake ANDROID_PLATFORM
externalNativeBuild android.minSdkVersion

למידע על מערכות build אחרות, אפשר לעיין במאמר שימוש ב-NDK עם מערכות build אחרות.

לא ניתן למצוא את הסמלים __aeabi

ההודעה הבאה:

‪UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__aeabi_memcpy"

היא דוגמה אחת לשגיאות בזמן ריצה. השגיאות האלה מופיעות ביומן כשמנסים לטעון את הספריות המקוריות. הסמל יכול להיות כל אחד מהבאים: __aeabi_*; __aeabi_memcpy ו-__aeabi_memclr הם הנפוצים ביותר.

הבעיה הזו מתועדת בגיליון 126

אי אפשר לאתר את הסמל rand

עבור הודעת יומן השגיאות הבאה:

‪UnsatisfiedLinkError: dlopen failed: cannot locate symbol "rand"

אפשר לעיין בתשובה המפורטת הזו ב-Stack Overflow.

הפניה לא מוגדרת אל __atomic_*

בעיה: חלק מה-ABI צריכים ש-libatomic יספק הטמעות מסוימות לפעולות אטומיות.

פתרון: מוסיפים -latomic כשמקשרים.

לגבי הודעת השגיאה הבאה:

שגיאה: הפניה לא מוגדרת אל '__atomic_exchange_4'

הסמל בפועל יכול להיות כל דבר שמתחיל ב-__atomic_.

‫RTTI/exceptions לא פועל בין גבולות של ספריות

בעיה: חריגים לא נתפסים כשהם מופעלים מעבר לגבולות של ספרייה משותפת, או שהפעולה dynamic_cast נכשלת.

הפתרון: מוסיפים פונקציית מפתח לסוגים. פונקציית מפתח היא הפונקציה הווירטואלית הראשונה שאינה טהורה ואינה מוטמעת בשורה עבור סוג מסוים. לדוגמה, אפשר לעיין בדיון בנושא Issue 533.

ב-C++ ABI מצוין שלשני אובייקטים יש את אותו סוג אם ורק אם מצביעי ה-type_info שלהם זהים. אפשר לטפל בחריגים רק אם type_info של ה-catch תואם לחריג שהופעל. אותו כלל חל על dynamic_cast.

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

שימוש בספריות מוכנות מראש שלא תואמות

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

  • רמת ה-API המינימלית של האפליקציה שמתקבלת היא המקסימום של minSdkVersion של כל הספריות של האפליקציה.

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

  • כל הספריות צריכות להיווצר עם אותה גרסת NDK.

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

  • באפליקציות עם כמה ספריות משותפות צריך להשתמש ב-STL משותפת.

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