בעבר, מערכת Android תמכה רק בדפים עם זיכרון בגודל 4KB, מה שאפשר למערכת לייעל את הביצועים של הזיכרון הכולל במכשירים עם נפח זיכרון ממוצע. החל מ-Android 15, AOSP תומך במכשירים שמגדירים בהם גודל דף של 16KB (מכשירים עם 16KB). אם האפליקציה שלך משתמשת בספריות NDK, באופן ישיר או בעקיפין דרך SDK, תצטרכו לבנות מחדש את האפליקציה כדי שהיא תפעל במכשירים האלה עם 16 KB.
יצרני המכשירים ממשיכים לפתח מכשירים עם נפחים גדולים יותר של זיכרון פיזי (RAM). במכשירים רבים כאלה יוטמעו דפים בגודל 16KB (ובסופו של דבר בגודל גדול יותר) כדי לשפר את הביצועים של המכשיר. הוספת תמיכה במכשירים עם גודל דף של 16KB מאפשרת לאפליקציה לפעול במכשירים האלה, וליהנות משיפורי הביצועים שקשורים לכך. בלי קומפילציה מחדש, האפליקציות לא יפעלו במכשירים עם 16 KB בגרסאות עתידיות של Android.
כדי לעזור לכם להוסיף תמיכה לאפליקציה, סיפקנו הנחיות בנושא בדיקה אם יש השפעה על האפליקציה, בנייה מחדש של האפליקציה (אם רלוונטי) ובדיקת האפליקציה בסביבה של 16KB באמצעות אמולטורים (כולל תמונות מערכת של Android 15 לאמולטור של Android).
דרישת התאימות של Google Play
כדי לוודא שהאפליקציה פועלת בצורה תקינה בגרסאות העדכניות ביותר של Android, כל האפליקציות שמטרגטות ל-Android 15 (רמת API 35) ומעלה חייבות לתמוך בדפים עם זיכרון בגודל 16KB במכשירים עם 64 ביט ב-Google Play. החל מ-1 בפברואר 2027, אם העדכונים לאפליקציה לא יתמכו בדפים עם זיכרון בגודל 16KB, לא תהיה לך אפשרות לפרסם אותם.
יתרונות ושיפורים בביצועים
במכשירים שמוגדרים בהם דפים בגודל 16KB, נעשה שימוש במעט יותר זיכרון בממוצע, אבל יש גם שיפורים שונים בביצועים של המערכת ושל האפליקציות:
- זמני השקה קצרים יותר של אפליקציות בזמן שהמערכת נמצאת בלחץ על הזיכרון: נמוך ב-3.16% בממוצע, עם שיפורים משמעותיים יותר (עד 30%) בחלק מהאפליקציות שבדקנו
- צריכת חשמל מופחתת במהלך השקת האפליקציה: הפחתה של 4.56% בממוצע
- הפעלה מהירה יותר של המצלמה: הפעלה מתוך הזיכרון (hot start) מהירה יותר ב-4.48% בממוצע, והפעלה מחדש (cold start) מהירה יותר ב-6.60% בממוצע
- זמן הפעלה משופר של המערכת: שיפור של 8% (כ-950 אלפיות השנייה) בממוצע
השיפורים האלה מבוססים על הבדיקה הראשונית שלנו, וסביר להניח שהתוצאות במכשירים בפועל יהיו שונות. נמשיך לספק ניתוח נוסף של היתרונות הפוטנציאליים של האפליקציות.
איך בודקים אם האפליקציה מושפעת מהשינוי
אם האפליקציה שלך משתמשת בקוד Native, עליך לבנות מחדש את האפליקציה עם תמיכה במכשירים עם דפי זיכרון בגודל 16 KB. אם אתם לא בטוחים אם האפליקציה שלכם משתמשת בקוד Native, אתם יכולים להשתמש בכלי APK Analyzer כדי לזהות אם יש קוד Native, ואז לבדוק את ההתאמה של פלחי ELF לכל הספריות המשותפות שמצאתם. ב-Android Studio יש גם תכונות שעוזרות לכם לזהות באופן אוטומטי בעיות שקשורות ליישור.
אם האפליקציה שלך משתמשת רק בקוד שנכתב בשפת התכנות Java או ב-Kotlin, כולל כל הספריות או ערכות ה-SDK, אז האפליקציה כבר תומכת במכשירים עם נפח אחסון של 16 KB. עם זאת, מומלץ לבצע בדיקה של האפליקציה בסביבה של 16 KB כדי לוודא שאין נסיגות לא צפויות בהתנהגות האפליקציה.
האם באפליקציה שלך נעשה שימוש בקוד Native?
האפליקציה משתמשת בקוד Native אם מתקיים אחד מהתנאים הבאים:
- האפליקציה משתמשת בקוד C/C++ (קוד Native). אם האפליקציה משתמשת ב-Android NDK, היא משתמשת בקוד Native.
- האפליקציה מקושרת לספריות Native או ליחסי תלות (כמו ערכות SDK) של צד שלישי שמשתמשים בהם.
- האפליקציה שלך פותחה באמצעות כלי של צד שלישי לפיתוח אפליקציות, שמשתמש בספריות Native במכשיר.
זיהוי ספריות Native באמצעות הכלי לניתוח APK
הכלי לניתוח APK מאפשר להעריך היבטים שונים של קובץ APK שנוצר. כדי לבדוק אם האפליקציה משתמשת בקוד Native (גם אם היא תואמת ל-16KB):
- פותחים את Android Studio, לוחצים על File > Open (קובץ > פתיחה) ובוחרים פרויקט כלשהו.
בסרגל התפריטים, לוחצים על Build > Analyze APK... (פיתוח > ניתוח APK...).
בוחרים את ה-APK שרוצים לנתח.
מחפשים בתיקייה
lib, שמכילה קובצי אובייקטים משותפים (.so) אם יש כאלה. אם יש קובצי אובייקטים משותפים, האפליקציה משתמשת בקוד Native. בעמודה Alignment מוצגות אזהרות לגבי קבצים שיש בהם בעיות תזמון. אם אין קובצי אובייקטים משותפים או שאין תיקייתlib, האפליקציה לא משתמשת בקוד Native.
זיהוי בעיות בהתאמה באמצעות בדיקות אוטומטיות
Android Studio מזהיר אתכם באופן יזום אם קובצי ה-APK או הספריות שנוצרו מראש לא תואמים ל-16KB. השתמשו בכלי לניתוח APK כדי לבדוק אילו ספריות צריך לעדכן או אם נדרשים שינויים בקוד.
Lint ב-Android Studio גם מדגיש ספריות Native שלא מותאמות ל-16KB.
בדיקת ההתאמה של קטעי ELF לספריות משותפות
בכל ספרייה משותפת, מוודאים שקטעי ה-ELF של הספרייה המשותפת מיושרים כמו שצריך באמצעות יישור ELF בגודל 16KB.
שימוש בכלי לניתוח APK (מומלץ)
הדרך הקלה ביותר לוודא שהספריות המשותפות מיושרות היא להשתמש בכלי לניתוח APK ב-Android Studio. אם קובץ אובייקט משותף לא מיושר ל-16KB, בעמודה Alignment תופיע הודעת אזהרה.
שימוש בסקריפט check_elf_alignment.sh (ב-Linux או ב-macOS)
אם אתם מפתחים ב-Linux או ב-macOS ומעדיפים להשתמש בסקריפט, אתם יכולים להשתמש בסקריפט check_elf_alignment.sh:
שומרים את הסקריפט
check_elf_alignment.shבקובץ.מריצים את הסקריפט בקובץ ה-APK של האפליקציה:
check_elf_alignment.sh APK_NAME.apkהסקריפט מפיק את הפלט
ALIGNEDאוUNALIGNEDלכל הספריות המשותפות שלarm64-v8a.אם יש ספריות משותפות של
arm64-v8aאוx86_64שהןUNALIGNED, צריך לעדכן את האריזה של הספריות האלה, ואז לקמפל מחדש את האפליקציה ולבדוק אותה שוב לפי השלבים בקטע הזה.
שימוש ישיר בכלי שורת הפקודה
כדי לבדוק את ההתאמה של מקטעי ELF באמצעות כלי שורת הפקודה, פועלים לפי השלבים הבאים:
- מוודאים שמותקנים גם Android SDK Build-Tools בגרסה 35.0.0 ואילך וגם Android NDK באמצעות SDK Manager ב-Android Studio או כלי שורת הפקודה
sdkmanager. מחלצים את קובץ ה-APK של האפליקציה:
Linux או macOS
unzip APK_NAME.apk -d /tmp/my_apk_outWindows (PowerShell)
Expand-Archive -Path .\APK_NAME.apk -DestinationPath ~\tmp\my_apk_outבספרייה הזמנית שחילצתם אליה את קובץ ה-APK, בודקים את התוכן של הספרייה
libכדי למצוא קובצי אובייקטים משותפים (.so). אלה אותם קובצי אובייקטים משותפים שהייתם רואים בזמן זיהוי ספריות Native באמצעות הכלי לניתוח APK. מריצים את הפקודה הבאה בכל קובץ אובייקט משותף:Linux או macOS
SDK_ROOT_LOCATION/Android/sdk/ndk/NDK_VERSION/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objdump -p SHARED_OBJECT_FILE.so | grep LOADWindows (PowerShell)
SDK_ROOT_LOCATION\Android\sdk\ndk\NDK_VERSION\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-objdump.exe -p SHARED_OBJECT_FILE.so | Select-String -Pattern "LOAD"כאשר
SDK_ROOT_LOCATIONהוא הנתיב לספרייה שבה התקנתם את Android SDK,SHARED_OBJECT_FILEהוא השם של קובץ האובייקט המשותף שאתם בודקים ו-NDK_VERSIONהוא הגרסה של Android NDK שהתקנתם (לדוגמה,28.0.12433566). הפלט ייראה בערך כמו בדוגמה הבאה לכל קובץ שאתם בודקים:LOAD off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**14 LOAD off 0x0000000000042a90 vaddr 0x0000000000043a90 paddr 0x0000000000043a90 align 2**14 LOAD off 0x0000000000046230 vaddr 0x0000000000048230 paddr 0x0000000000048230 align 2**14בודקים את שורות הפלט כדי לוודא שאין ערכים קטנים מ-
2**14בקטעי הטעינה. אם יש קטעי טעינה עם הערכים2**13,2**12או ערכים נמוכים יותר, תצטרכו לעדכן את האריזה של הספריות האלה, ואז לקמפל מחדש את האפליקציה ולבדוק אותה שוב לפי השלבים בקטע הזה.לאחר מכן, מריצים את כלי שורת הפקודה
zipalignבקובץ ה-APK של האפליקציה:Linux או macOS
SDK_ROOT_LOCATION/Android/sdk/build-tools/35.0.0/zipalign -v -c -P 16 4 APK_NAME.apkWindows (PowerShell)
SDK_ROOT_LOCATION\Android\sdk\build-tools\35.0.0\zipalign.exe -v -c -P 16 4 APK_NAME.apkSDK_ROOT_LOCATIONהוא הנתיב לספרייה שבה התקנתם את Android SDK, ו-APK_NAMEהוא השם של קובץ ה-APK של האפליקציה. אם כל הספריות המשותפות מיושרות בצורה נכונה, בשורה האחרונה של הפלט יופיע הכיתוב "Verification successful".אם האימות נכשל, צריך לשנות את המיקום של כמה ספריות משותפות. לשם כך, צריך לעדכן את האריזה של הספריות האלה, לקמפל מחדש את האפליקציה ולבדוק אותה שוב לפי השלבים שמפורטים בקטע הזה.
בדיקת דגל האבטחה של RELRO
כדי לצמצם את הסיכון לניצול פרצות אבטחה, מקשרים מודרניים משתמשים בדגל Relocation Read-Only (RELRO) כדי להגדיר את קטעי היעד של ההעברה (כמו .got ו-.data.rel.ro) של קובץ האובייקט המשותף כקריאה בלבד אחרי שההעברות הדינמיות הושלמו.
מפעילים את הדגל RELRO ב-build.
כש-GNU_RELRO משתף קטע LOAD שניתן לכתיבה עם נתונים שניתנים לכתיבה (כמו .data או .bss), כתובת הסיום של הקטע GNU_RELRO (VirtAddr + MemSiz) חייבת להיות מותאמת ל-16KB (0x4000). אחרת, כשAndroid Linker מסמן את הדף בגודל 16KB שמכיל את הסוף של GNU_RELRO כדף לקריאה בלבד, כל הנתונים הסמוכים שניתנים לכתיבה ונמצאים בשאר הדף בגודל 16KB הופכים גם הם לנתונים לקריאה בלבד, והאפליקציה קורסת בזמן הריצה עם שגיאת חלוקה (SIGSEGV) כשמתבצעת כתיבה של הנתונים האלה.
בדרך כלל זה קורה אם קובץ ה-.so נוצר באמצעות NDK toolchain r27 וגרסאות קודמות בלי להפעיל את הדגלים הרלוונטיים.
שימוש בכלי לניתוח APK (מומלץ)
אתם יכולים להשתמש בכלי לניתוח APK ב-Android Studio כדי לבדוק ישירות את סטטוס ההתאמה של RELRO בקובצי האובייקטים המשותפים.
שימוש בכלי שורת הפקודה
אם אתם מעדיפים לבדוק את ההתאמה באופן ידני או צריכים להפוך את התהליך לאוטומטי, בצעו את השלבים הבאים:
שלב 1: בודקים את כותרות התוכנית
מריצים את הפקודה הבאה בכל קובץ אובייקט משותף (ב-Linux, מחליפים את darwin-x86_64 ב-linux-x86_64):
Linux או macOS
SDK_ROOT_LOCATION/Android/sdk/ndk/NDK_VERSION/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-readelf -Wl SHARED_OBJECT_FILE.so | grep 'LOAD\|RELRO\|Type'
Windows (PowerShell)
SDK_ROOT_LOCATION\Android\sdk\ndk\NDK_VERSION\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-readelf.exe -Wl SHARED_OBJECT_FILE.so | Select-String -Pattern 'LOAD|RELRO|Type'
המחרוזת GNU_RELRO מודפסת אם יש קטע RELRO.
שלב 2: בודקים אם RELRO תואם ל-16KB
קובץ אובייקט משותף תואם ל-16KB ביחס ל-RELRO אם מתקיים אחד מהתנאים הבאים:
- לא קיים פלח
GNU_RELROבכותרות של התוכנית. - כתובת הסיום של
GNU_RELROמותאמת ל-16KB: הסכום של הכתובת הווירטואלית (VirtAddr) וגודל הזיכרון (MemSiz) חלקי 16KB (0x4000) משאיר שארית (מודולו) של אפס:(VirtAddr + MemSiz) % 0x4000 == 0. - אין נתונים שניתנים לכתיבה אחרי
GNU_RELROבאותו קטעLOAD: GNU_RELROתופס את כל שאר הקטעLOADשמכיל אותו (כלומרLOAD.VirtAddr + LOAD.MemSiz == RELRO.VirtAddr + RELRO.MemSiz), ולכן אין קטעי.dataאו.bssשניתנים לכתיבה שחולקים את הדף האחרון.
דוגמאות
בדוגמאות הבאות מוסבר איך להעריך את הפלט של llvm-readelf בהגדרות שונות של GNU_RELRO.
דוגמה 1: קובץ .so לא תואם (לא עומד בדרישות)
בדוגמה הזו, GNU_RELRO משתף קטע RW LOAD עם נתונים שניתנים לכתיבה, וכתובת הסיום שלו היא לא כפולה של 0x4000:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0cfaf0 0x0cfaf0 R E 0x4000
LOAD 0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x02510 0x04510 RW 0x4000
GNU_RELRO 0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x01510 0x01510 R 0x1
- נוסחה:
(VirtAddr + MemSiz) % 0x4000 == 0 - התוצאה:
(0xdfaf0 + 0x01510) % 0x4000 = 0xE1000 % 0x4000 == 0x1000
מכיוון שהערך של 0x1000 הוא לא אפס, והקטע LOAD שמכיל אותו ממשיך מעבר ל-0xE1000 (ומסתיים ב-0xdfaf0 + 0x04510 = 0xE4000), קובץ ה-.so הזה לא תואם ל-16KB. במכשיר 16KB, המקשר מעגל את טווח ההגנה של RELRO לגבולות דף של 16KB, ומסמן את 0xDC000 (מעבר הדף הקודם) עד 0xE4000 כ"לקריאה בלבד". עם זאת, הקטעים .data ו-.bss של הקובץ .so שניתנים לכתיבה נמצאים בטווח המשנה מ-0xE1000 עד 0xE4000, וכתוצאה מכך נוצרת שגיאת פילוח כשמשנים אותם בזמן הריצה. צריך לבנות מחדש את הקובץ .so כמו שמתואר בקטע הידור האפליקציה באמצעות יישור ELF בגודל 16KB.
דוגמה 2: קובץ .so תואם עם קטע RELRO (תואם)
בדוגמה הזו, הסוף של הקטע GNU_RELRO נמצא בגבול של דף בגודל 16KB:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0cfaf0 0x0cfaf0 R E 0x4000
LOAD 0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x01510 0x03510 RW 0x4000
GNU_RELRO 0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x00510 0x00510 R 0x1
- נוסחה:
(VirtAddr + MemSiz) % 0x4000 == 0 - התוצאה:
(0xdfaf0 + 0x00510) % 0x4000 = 0xE0000 % 0x4000 == 0
מכיוון שהשארית היא 0, ההגנה של RELRO מסתיימת באופן נקי בגבול הדף 0xE0000, וכל הנתונים שניתנים לכתיבה החל מ-0xE0000 לא מושפעים.
דוגמה 3: קובץ .so בלי קטע RELRO (תואם)
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000680 0x000680 R E 0x4000
LOAD 0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x0001d8 RW 0x4000
בקובץ .so הזה אין קטע GNU_RELRO, ולכן בעיות שקשורות לגבולות הדף של RELRO לא רלוונטיות (אבל עדיין מומלץ להפעיל את RELRO מטעמי אבטחה).
דוגמה 4: GNU_RELRO בפלח LOAD משלו (תואם)
בדוגמה הזו, GNU_RELRO לא מסתיים בגבול של 16KB, אבל הוא עדיין תואם כי אין אחריו נתונים שניתנים לכתיבה באותו קטע LOAD:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000680 0x000680 R E 0x4000
LOAD 0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x000980 RW 0x4000
GNU_RELRO 0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x000980 R 0x1
- נוסחה:
(VirtAddr + MemSiz) % 0x4000 == 0 - התוצאה:
(0x4680 + 0x0980) % 0x4000 = 0x5000 % 0x4000 == 0x1000
למרות שהערך של 0x1000 הוא לא אפס, הקובץ .so עדיין תואם לדפים בגודל 16KB.
אם משווים בין GNU_RELRO לבין הקטע LOAD שמכיל אותו (הרשומה השנייה של LOAD), רואים ששניהם מתחילים ב-VirtAddr 0x4680 ומסתיימים בדיוק באותה כתובת (0x4680 + 0x0980 = 0x5000).
מכיוון שכל הקטע LOAD מכיל רק נתוני RELRO, אין נתוני .data או .bss שניתנים לכתיבה בטווח המשנה מ-0x5000 עד 0x8000 (או שלספרייה אין נתונים שניתנים לכתיבה שאינם RELRO, או שכל קטע LOAD שניתן לכתיבה מתחיל בגבול דף נפרד של 16KB ב-0x8000 או מעבר לכך). ההגנה לקריאה בלבד של המקשר לא תחפוף לנתונים שניתן לכתוב, כך שלא תתרחש שגיאת פילוח.
איך מפתחים אפליקציה שתומכת במכשירים עם דפים בגודל 16KB
אם האפליקציה שלכם משתמשת בקוד Native, בצעו את השלבים שמפורטים בקטעים הבאים כדי לוודא שהאפליקציה תומכת במכשירים עם דפי זיכרון בגודל 16KB:
- עדכון האריזה של הספריות המשותפות
- הידור האפליקציה באמצעות יישור ELF בגודל 16KB
- תיקון קוד ופתרון בעיות בזמן ריצה
- אופטימיזציה של מקצי הזיכרון בהתאמה אישית (אם רלוונטי)
- בדיקה אם ערכות ה-SDK תומכות ב-16KB
עדכון האריזה של הספריות המשותפות
כדאי לשדרג לגרסה 8.5.1 ומעלה של AGP ולהשתמש בספריות משותפות לא דחוסות.
משתמשים ב-bundletool כדי לוודא שהרוכסן ישר
כדי לראות את היישור של החבילה, משתמשים בפקודה:
bundletool dump config --bundle=<my .aab> | grep alignment
אם מופיע הערך PAGE_ALIGNMENT_16K, סימן שה-bundle מבקש יישור ZIP של 16KB. אם מופיע PAGE_ALIGNMENT_4K, זה אומר שקובץ ה-APK שנוצר מה-AAB הזה צריך להכיל קובצי .so מיושרים בגודל 4KB בקובץ ה-ZIP.
AGP בגרסה 8.5.1 ואילך
במכשירים עם 16KB, אפליקציות שכוללות ספריות משותפות לא דחוסות צריכות ליישר אותן לפי גבולות של 16KB ב-zip. כדי לעשות את זה, צריך לשדרג לגרסה 8.5.1 ומעלה של הפלאגין של Android Gradle (AGP). בקטע מסייע השדרוג של הפלאגין של Android Gradle מוסבר איך משדרגים את הפלאגין.
AGP בגרסה 8.5 ומטה
אם אתם לא יכולים לשדרג את AGP לגרסה 8.5.1 ואילך, אתם יכולים לעבור להשתמש בספריות משותפות דחוסות. כדי למנוע בעיות בהתקנת האפליקציה שקשורות לספריות משותפות לא מיושרות, צריך לעדכן את הגדרת Gradle כך ש-Gradle ידחוס את הספריות המשותפות בזמן אריזת האפליקציה.
מגניב
בקובץ build.gradle, מוסיפים את האפשרות הבאה:
android {
...
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
}
Kotlin
בקובץ build.gradle.kts, מוסיפים את האפשרות הבאה:
android {
...
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
}
AGP בגרסה 8.0 ומטה
אם אתם משתמשים בגרסה 8.0 או בגרסה ישנה יותר של AGP, אתם צריכים גם להשבית את האפשרות של ספריית Native לא דחוסה לחבילות App Bundle בקובץ gradle.properties:
android.bundle.enableUncompressedNativeLibs=false
הידור האפליקציה באמצעות יישור ELF בגודל 16KB
כדי שהאפליקציה תפעל במכשירים עם דפי זיכרון בגודל 16KB, צריך ליישר את מקטעי ה-ELF של הספריות המשותפות באמצעות יישור ELF בגודל 16KB.
מפתחי משחקים שמשתמשים במנוע המשחק Unity יכולים לעיין במדריך של Unity. אם המשחק פועל על מנוע המשחק Unreal, עיינו במדריך של Unreal.אם מדובר במנועי משחק מקוריים, המשיכו לקרוא את המדריך הזה.
כדי להדר את האפליקציה באמצעות יישור ELF בגודל 16KB, צריך לבצע את השלבים באחד מהקטעים הבאים, בהתאם לגרסת Android NDK שבה אתם משתמשים.
Android NDK r28 ואילך
ב-NDK מגרסה r28 ואילך, הקומפילציה מיושרת כברירת מחדל ל-16KB.
Android NDK r27 וגרסאות קודמות
כדי לתמוך בהידור של ספריות משותפות שתואמות ל-16KB באמצעות Android NDK בגרסה r27 או גרסה ישנה יותר, צריך להשתמש בדגלי הקישור הבאים:
-Wl,-z,max-page-size=16384
-Wl,-z,common-page-size=16384
כך מעדכנים את קובצי התצורה של מערכת ה-build:
ndk-build
אם אתם משתמשים ב-ndk-build, עדכנו את Android.mk כדי להפעיל יישור ELF של 16KB:
LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384
CMake
אם אתם משתמשים ב-CMake, עדכנו את CMakeLists.txt כדי להפעיל יישור ELF של 16KB:
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
"-Wl,-z,max-page-size=16384"
"-Wl,-z,common-page-size=16384"
)
תיקון קוד ופתרון בעיות בזמן ריצה
גם אם האפליקציה מותאמת ל-16KB, יכולות להיות בה שגיאות אם יש מקומות בקוד שבהם מניחים שהמכשיר משתמש בגודל דף ספציפי. כדי למנוע את זה, צריך לבצע את הפעולות הבאות:
מסירים את כל יחסי התלות בתוך הקוד שמפנים לקבוע
PAGE_SIZEאו מופעים בלוגיקה של הקוד שמניחים שגודל הדף של המכשיר הוא 4KB (4096).במקומה, צריך להשתמש ב-
getpagesize()או ב-sysconf(_SC_PAGESIZE).חפשו שימושים ב-
mmap()ובממשקי API אחרים שדורשים ארגומנטים מיושרים לדף, והחליפו אותם בחלופות לפי הצורך.
במקרים מסוימים, אם האפליקציה משתמשת ב-PAGE_SIZE כערך נוח שלא קשור לגודל הדף הבסיסי, האפליקציה לא תפסיק לפעול כשתשתמשו בה במצב 16KB. עם זאת, אם הערך הזה מועבר לליבה באמצעות mmap בלי MAP_FIXED, הליבה עדיין משתמשת בדף שלם, מה שגורם לבזבוז של חלק מהזיכרון. לכן, PAGE_SIZE לא מוגדר כשמצב 16KB מופעל ב-NDK r27 ואילך.
אם האפליקציה משתמשת ב-PAGE_SIZE באופן הזה, ואף פעם לא מעבירה את הערך הזה ישירות לליבה, במקום להשתמש ב-PAGE_SIZE, צריך ליצור משתנה חדש עם שם חדש כדי לציין שהוא משמש למטרות אחרות ולא מייצג דף זיכרון אמיתי.
אופטימיזציה של מקציבי זיכרון מותאמים אישית
במערכות של 16KB, היחידה הקטנה ביותר של זיכרון פיזי שמערכת ההפעלה מקצה גדולה פי ארבעה מאשר במערכות של 4KB. אם מקצה זיכרון מותאם אישית תוכנן לפי הנחות של 4KB, הוא יכול לפזר אובייקטים קטנים על פני כמה דפים של 16KB ולשמור זיכרון ריק שלא לצורך. הפעולה הזו יכולה להגדיל באופן משמעותי את השימוש בזיכרון הפיזי (RSS) ולפגוע ביעילות של שטח ההחלפה הדחוס (ZRAM).
אם הקוד שלכם מנהל מאגרי זיכרון משלו, כדאי לפעול לפי ההמלצות הבאות:
1. הימנעו מספי בייטים בתוך הקוד לשחרור זיכרון
הרבה מקצים משתמשים במגבלות בייטים קבועות כדי להחליט מתי לשחרר זיכרון בחזרה למערכת ההפעלה באמצעות madvise(MADV_DONTNEED) (לדוגמה, לשחרר זיכרון רק אם אובייקטים פעילים תופסים פחות מ-8KB).
במערכת של 16KB, אפילו אובייקט פעיל אחד של 16 בייטים מצמיד דף שלם של 16KB (שגדול מ-8KB). כתוצאה מכך, אף פעם לא מגיעים לסף השחרור, והמקצה אף פעם לא מחזיר לליבה את הזיכרון שמסביב שלא נמצא בשימוש.
מה לעשות: אף פעם אל תשתמשו בקבועי בייטים בתוך הקוד בשביל היוריסטיקות של שחרור דפים.
הגדלה דינמית של ספי השחרור בזמן הריצה על סמך גודל הדף בפועל באמצעות sysconf(_SC_PAGESIZE).
2. מילוי דפים שכבר נמצאים בשימוש (הקצאה לפי דחיסות)
אם המקצה מחלק זיכרון בסדר נכנס ראשון יוצא ראשון (FIFO) או בסבב, הקצאות חדשות מפוזרות על פני הרבה דפים של 16KB שמולאו באופן חלקי. אובייקט אחד בדף שומר את כל 16KB בזיכרון ה-RAM הפיזי.
מה לעשות: תמיד להקצות אובייקטים חדשים מהדף או מה-slab המלאים ביותר (הצפופים ביותר) לפני שנוגעים בדפים ריקים או בדפים שנעשה בהם שימוש מועט. ריכוז ההקצאות החדשות בדפים שכבר מלוכלכים מאפשר לדפים שנעשה בהם שימוש מועט להתרוקן באופן טבעי לאפס אובייקטים פעילים, כך שניתן לשחרר את כל הדף בגודל 16KB למערכת ההפעלה.
3. יישור מאגרי הזיכרון ל-16KB ושמירה על גודל מאגרים בינוני
טווחים של כמה דפים שמיועדים למערכות של 4KB יכולים להכיל עודף זיכרון שלא שוחרר בליבות של 16KB. בנוסף, מחלקות גודל שלא מתחלקות באופן שווה ל-16KB גורמות לפרגמנטציה בסוף כל דף.
מה לעשות:
- חשוב לוודא שכל מאגרי הזיכרון, גבולות ה-slab והיישורים של המאגרים הם כפולות מדויקות של גודל הדף בזמן הריצה.
- כדאי לבדוק מחדש את הגדלים של טווחי הדפים המרובים עבור מחלקות של אובייקטים קטנים, כדי להימנע מהקצאת לוחות גדולים מדי שכולאים זיכרון לא פעיל.
4. שחרור מיידי של זיכרון פיזי למאגרים גדולים שנשמרו במטמון
מקציבים מותאמים אישית שומרים לעיתים קרובות מאגרים גדולים (> 64 KB) במטמון במאגר בזיכרון, כדי שיהיה אפשר להשתמש בהם שוב בלי לשלם את התקורה של קריאות המערכת mmap או munmap. עם זאת, שמירת המאגרים המלוכלכים האלה בזיכרון מבזבזת מגה-בייטים של זיכרון RAM פיזי בזמן ההמתנה לטיימר הפינוי.
מה לעשות: לשמור את טווח הכתובות של הזיכרון הווירטואלי לשימוש חוזר מהיר, אבל להפעיל את madvise(..., MADV_DONTNEED) או את madvise(..., MADV_FREE) מיד כשמחזירים מאגר למטמון. מערכת ההפעלה מחזירה את ה-RAM הפיזי מיד, והאפליקציה עדיין יכולה להשתמש שוב בכתובת הווירטואלית באופן מיידי בלי להקצות אותה מחדש.
5. איפוס הזיכרון בזמן שחרור במקום בזמן הקצאה לדחיסת ZRAM
מערכת Android משתמשת בהחלפת זיכרון דחוס (ZRAM) כדי לשמור אפליקציות ברקע בזיכרון. במכשירים עם דפים בגודל 16KB, אם דף מכיל אפילו אובייקט פעיל אחד, כל הדף נשאר בזיכרון או מועבר ל-ZRAM. נתוני זבל שנותרו (כמו מצביעים ומחרוזות לא עדכניים) בחלקים של הדף שפונו קודם, נדחסים בצורה לא טובה.
אם המקצה מאפס את הזיכרון (למשל, למטרות אבטחה או הקצאות שמאופסות), כדאי לאפס בזמן ביטול ההקצאה (free()) ולא בזמן ההקצאה:
- דפים מוצמדים עולים פחות: זיכרון לא פעיל בדפים בגודל 16KB שמלאים חלקית נדחס כמעט לאפס ב-ZRAM, כך שדפים מוצמדים לא מבזבזים שטח החלפה פיזי.
- תקורה מינימלית של מטמון: בזמן הקריאה ל-
free(), הזיכרון כבר חם במטמון של המעבד (CPU), וכך נמנעים מקרים נוספים של אי מציאה במטמון בהמשך.
סיכום ההמלצות
| אזור | המלצה | ההשפעה הצפויה |
|---|---|---|
| ערכי סף לטיהור | התאמה דינמית של ערכי הסף לפרסום באמצעות sysconf(_SC_PAGESIZE). |
מונעת מצב שבו לוגיקת השחרור נתקעת באופן סופי. |
| סדר ההקצאה | קודם כל, מקצים מהדף או מהלוח הצפוף ביותר (כמעט מלא). | מפחית את הזיכרון התושב (RSS) על ידי אריזת אובייקטים פעילים יחד. |
| גודל הטווח | צריך להתאים את מחלקות הגודל לכפולות של 16KB ולהימנע משימוש ב-slabs גדולים מדי. | מבטל את הפרגמנטציה של דפי הזנב ומצמצם את הזיכרון המבוזבז. |
| מאגרי מטמון | צריך להפעיל את madvise(MADV_DONTNEED) מיד כששומרים מאגרים גדולים במטמון. |
התכונה הזו מונעת ניפוח של זיכרון RAM לא פעיל, תוך שמירה על שימוש חוזר וירטואלי מהיר. |
| Swap / ZRAM | איפוס הזיכרון ב-free() במקום בהקצאה. |
משפר את יחסי הדחיסה של ZRAM, כך שדפים מוצמדים צורכים פחות משאבים. |
בדיקה אם ערכות ה-SDK תומכות ב-16KB
הרבה ערכות SDK תואמות לדפים בגודל 16KB, במיוחד אם אתם יוצרים אותן בעצמכם או משתמשים בגרסאות שנוצרו מראש לאחרונה. עם זאת, חלק מהגרסאות או מהרכיבים המובנים של ערכות ה-SDK לא תואמים ל-16KB, לכן צריך לבדוק באתר של כל ספק SDK באיזו גרסה להשתמש עם 16KB.
בדיקת האפליקציה בסביבה של 16KB
אחרי שיוצרים אפליקציה עם תמיכה במכשירים עם דפים בגודל 16KB, צריך לבדוק את חוויית השימוש באפליקציה בסביבה של 16KB כדי לראות אם יש בה רגרסיות. לשם כך, בצע את הצעדים הבאים:
מגדירים את Android 15 SDK ואילך.
מגדירים את אחת מסביבות הבדיקה הבאות:
מפעילים את מכשיר הבדיקה ומריצים את הפקודה הבאה כדי לוודא שהוא משתמש בסביבה של 16KB:
adb shell getconf PAGE_SIZEהפקודה אמורה להחזיר את הערך
16384.מריצים את הפקודה הבאה של
zipalignכדי לוודא שהאפליקציה מותאמת ל-16KB, כאשר APK_NAME הוא השם של קובץ ה-APK של האפליקציה:zipalign -c -P 16 -v 4 APK_NAME.apkבודקים את האפליקציה ביסודיות, ומתמקדים באזורים שעשויים להיות מושפעים משינוי מופעי קוד שמפנים לגדלים ספציפיים של דפים.
הגדרת אמולטור Android עם קובץ אימג' של מערכת שמבוססת על 16KB
כדי להגדיר סביבה של 16KB באמצעות אמולטור Android, פועלים לפי השלבים הבאים:
- ב-Android Studio, לוחצים על Tools > SDK Manager (כלים > SDK Manager).
בכרטיסייה SDK Platforms, בוחרים באפשרות Show Package Details, מרחיבים את הקטע Android VanillaIceCream או גרסה חדשה יותר ובוחרים אחת או שתיים מתמונות המערכת הבאות של האמולטור, בהתאם למכשירים הווירטואליים שרוצים ליצור:
- תמונת מערכת של Google APIs Experimental 16 KB Page Size ARM 64 v8a
- Google APIs Experimental 16 KB Page Size Intel x86_64 Atom System Image
לוחצים על Apply > OK כדי להוריד את קובצי האימג' של המערכת שבחרתם.
פועלים לפי השלבים להגדרת מכשיר וירטואלי ל-Android 15, וכשמוצגת בקשה לבחור קובץ אימג' של המערכת, בוחרים את קובץ האימג' של המערכת בגודל 16KB שהורדתם. אם היא לא מומלצת באופן אוטומטי, אפשר למצוא את קובץ האימג' של המערכת בגודל 16KB בכרטיסייה Other Images.
הפעלת האמולטור
אחרי שמסיימים להגדיר את אמולטור Android ואת המכשירים הווירטואליים, מפעילים את האמולטור מתפריט מכשיר היעד או משורת הפקודה.
הפעלת מצב 16 KB במכשיר באמצעות אפשרויות למפתחים
כדי להפעיל מכשיר במצב 16KB, מעבירים את המתג של אפשרות המפתחים הפעלה בגודל דף של 16KB.
בגרסאות QPR של Android 15, אפשר להשתמש באפשרות למפתחים שזמינה במכשירים מסוימים כדי להפעיל את המכשיר במצב 16KB ולבצע בדיקות במכשיר. לפני שמשתמשים באפשרות למפתחים, עוברים אל הגדרות > מערכת > עדכוני תוכנה ומחילים את כל העדכונים שזמינים.
אפשרות למפתחים זו זמינה במכשירים הבאים:
Pixel 8 ו-Pixel 8 Pro (עם Android 15 QPR1 ומעלה)
Pixel 8a (עם Android 15 QPR1 ומעלה)
Pixel 9, Pixel 9 Pro ו-Pixel 9 Pro XL (עם Android 15 QPR2 ומעלה)
Pixel 9a (עם Android מגרסה 16 ואילך)
מצב תאימות לאחור של 16KB
אזהרה במצב תאימות לגודל הדף
האפשרות לתאימות לאחור של 16KB זמינה כשהמכשיר פועל עם ליבה של 16KB. מנהל החבילות מפעיל אפליקציה במצב תאימות לאחור של 16KB כשהתנאים הבאים מתקיימים:
- אם באפליקציה יש קובצי ELF (עם סיומת
.so) עם יישור של מקטע LOAD בגודל 4KB. - אם קובץ ה-APK הדחוס מכיל קובצי ELF לא דחוסים שמותאמים ל-ZIP בגודל 4KB.
אם מנהל החבילות הפעיל את מצב התאימות לאחור של 16KB באפליקציה, האפליקציה תציג אזהרה כשהיא תופעל בפעם הראשונה, ותציין שהיא פועלת במצב תאימות לאחור של 16KB.
מצב תאימות לאחור של 16KB מאפשר לאפליקציות מסוימות לפעול, אבל כדי שהאפליקציות יהיו אמינות ויציבות ככל האפשר, הן עדיין צריכות להיות מיושרות ל-16KB.
בדף המידע על האפליקציה, בקטע מתקדם, מפעילים או משביתים את ההגדרה הפעלת האפליקציה במצב של תאימות לגודל הדף כדי להפעיל או להשבית את מצב התאימות לאחור של 16KB באפליקציה ספציפית. ההגדרה הזו גלויה רק כשהמכשיר פועל עם גודל דף של 16KB.
הגדרת מצב תאימות לגודל הדף
כדי לאלץ תאימות לאחור של 16KB בכל אפליקציה במכשיר:
adb shell setprop bionic.linker.16kb.app_compat.enabled true
adb shell setprop pm.16kb.app_compat.disabled false
כדי להשבית את התאימות לאחור של 16KB בכל האפליקציות במכשיר:
adb shell setprop bionic.linker.16kb.app_compat.enabled false
adb shell setprop pm.16kb.app_compat.disabled true
ב-Android 17, אתם יכולים גם להשבית את התאימות לאחור של 16KB לכל אפליקציה, ולגרום לכל קובץ בינארי לא תואם להפסיק לפעול באופן מיידי:
adb shell setprop bionic.linker.16kb.app_compat.enabled fatal
adb shell setprop pm.16kb.app_compat.disabled true
כדי להפעיל או להשבית את מצב התאימות לאחור באפליקציה ספציפית, מגדירים את המאפיין android:pageSizeCompat כ"מופעל" או "מושבת" בקובץ AndroidManifest.xml של האפליקציה. כשהמאפיין הזה מוגדר, האפליקציה לא תציג אזהרות לגבי מצב תאימות לאחור כשהיא מופעלת.