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

במסמך הזה מופיעה רשימה חלקית של בעיות נפוצות שאתם עשויים להיתקל בהן במהלך השימוש ב-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 לא פועלים בין גבולות הספרייה

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

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

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

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

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

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

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

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

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

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

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

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