בדף הזה מתוארים שינויים שנוספו ל-AOSP כדי לצמצם שינויים מיותרים בקבצים בין גרסאות build. מיישמי מכשירים שמנהלים מערכות בנייה משלהם יכולים להשתמש במידע הזה כהנחיות לצמצום הגודל של העדכונים שלהם דרך האוויר (OTA).
לפעמים עדכוני OTA של Android מכילים קבצים ששונו ולא תואמים לשינויים בקוד. הם למעשה ארטיפקטים של מערכת הבנייה. מצב כזה יכול לקרות כשאותו קוד, שנבנה בזמנים שונים, מספרייה שונה או במכונות שונות, יוצר מספר גדול של קבצים שהשתנו. קבצים מיותרים כאלה מגדילים את הגודל של תיקון OTA, ומקשים על זיהוי השינויים בקוד.
כדי להפוך את התוכן של עדכון OTA לשקוף יותר, ב-AOSP יש שינויים במערכת הבנייה שנועדו לצמצם את הגודל של תיקוני OTA. שינויים מיותרים בקבצים בין גרסאות build בוטלו, ועדכוני OTA מכילים רק קבצים שקשורים לתיקון. AOSP כולל גם כלי להשוואת גרסאות, שמסנן שינויים נפוצים בקבצים שקשורים לגרסה כדי לספק השוואה נקייה יותר של קובץ הגרסה, וכלי למיפוי בלוקים, שעוזר לשמור על עקביות בהקצאת בלוקים.
מערכת בנייה יכולה ליצור תיקונים גדולים שלא לצורך בכמה דרכים. כדי לפתור את הבעיה הזו, ב-Android 8.0 ואילך, הוטמעו תכונות חדשות כדי להקטין את גודל הטלאי לכל קובץ diff. השיפורים שהובילו להקטנת הגודל של חבילות העדכון דרך האוויר (OTA) כוללים את הדברים הבאים:
-
שימוש ב-ZSTD, אלגוריתם דחיסה ללא אובדן נתונים לשימוש כללי, לעדכונים של תמונות מלאות במכשירים שאינם A/B. אפשר להתאים אישית את ZSTD כדי לקבל יחסי דחיסה גבוהים יותר על ידי הגדלת רמת הדחיסה. רמת הדחיסה מוגדרת במהלך יצירת ה-OTA
ואפשר להגדיר אותה באמצעות העברת הדגל
--vabc_compression_param=zstd,$COMPRESSION_LEVEL -
הגדלת גודל חלון הדחיסה שמשמש במהלך OTA. אפשר להגדיר את הגודל המקסימלי של חלון הדחיסה על ידי התאמה אישית של פרמטר ה-build בקובץ
.mkשל המכשיר. המשתנה הזה מוגדר כ-PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR := 262144 - שימוש בדחיסה מחדש של Puffin, כלי דטרמיניסטי לתיקון של זרמי deflate, שמטפל בפונקציות הדחיסה וההפרש ליצירת עדכון OTA של בדיקת A/B.
-
שינויים בשימוש בכלי ליצירת דלתא, למשל איך נעשה שימוש בספרייה
bsdiffלדחיסת תיקונים. ב-Android 9 ואילך, הכליbsdiffבוחר את אלגוריתם הדחיסה שיניב את תוצאות הדחיסה הטובות ביותר לתיקון. -
שיפורים ב-
update_engineהביאו לצריכת זיכרון נמוכה יותר כשמחילים תיקונים לעדכוני מכשירים מסוג A/B.
בקטעים הבאים נדון בבעיות שונות שמשפיעות על הגודל של עדכוני OTA, בפתרונות שלהן ובדוגמאות להטמעה ב-AOSP.
סדר הקבצים
בעיה: מערכות קבצים לא מבטיחות סדר קבצים כשמבקשים רשימה של קבצים בספרייה, למרות שבדרך כלל הסדר זהה באותו תהליך תשלום. כלים כמו ls ממיינים את התוצאות כברירת מחדל, אבל הפונקציה של התו הכללי שמשמשת בפקודות כמו find ו-make לא ממיינת. לפני שמשתמשים בכלים האלה, צריך למיין את הפלט.
פתרון: כשמשתמשים בכלים כמו find ו-make עם פונקציית התו הכללי, צריך למיין את הפלט של הפקודות האלה לפני שמשתמשים בהן. כשמשתמשים ב-$(wildcard) או ב-$(shell find) בקובצי Android.mk, צריך למיין אותם גם כן. יש כלים, כמו Java, שממיינים את הקלט, ולכן לפני שממיינים את הקבצים, צריך לוודא שהכלי שבו משתמשים לא עשה זאת כבר.
דוגמאות: תוקנו הרבה מקרים במערכת הבנייה המרכזית באמצעות מאקרו all-*-files-under מובנה, שכולל all-cpp-files-under (כי כמה הגדרות היו מפוזרות בקובצי makefile אחרים).
פרטים נוספים זמינים במאמרים הבאים:
- https://android.googlesource.com/platform/build/+/4d66adfd0e6d599d8502007e4ea9aaf82e95569f
- https://android.googlesource.com/platform/build/+/379f9f9cec4fe1c66b6d60a6c19fecb81b9eb410
- https://android.googlesource.com/platform/build/+/7c3e3f8314eec2c053012dd97d2ae649ebeb5653
- https://android.googlesource.com/platform/build/+/5c64b4e81c1331cab56d8a8c201f26bb263b630c
בניית ספרייה
בעיה: שינוי הספרייה שבה נוצרים הפריטים יכול לגרום להבדלים בקבצים הבינאריים. רוב הנתיבים ב-build של Android הם נתיבים יחסיים, ולכן __FILE__ ב-C/C++ לא מהווה בעיה. עם זאת, סמלי הניפוי באגים מקודדים את נתיב השם המלא כברירת מחדל, והערך .note.gnu.build-id נוצר מגיבוב של הקובץ הבינארי לפני ההסרה, ולכן הוא ישתנה אם סמלי הניפוי באגים ישתנו.
הפתרון: ב-AOSP, נתיבי הניפוי באגים הם יחסיים. פרטים נוספים זמינים ב-CL: https://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02.
חותמות זמן
הבעיה: חותמות הזמן בפלט של הבנייה גורמות לשינויים מיותרים בקובץ. סביר להניח שהמצב הזה יקרה במיקומים הבאים:
__DATE__/__TIME__/__TIMESTAMP__פקודות מאקרו בקוד C או C++.- חותמות זמן שמוטמעות בארכיונים מבוססי-ZIP.
פתרונות/דוגמאות: כדי להסיר את חותמות הזמן מהפלט של הבנייה, צריך לפעול לפי ההוראות שבהמשך בנושא __DATE__/__TIME__/__TIMESTAMP__ ב-C/C++ וחותמות זמן מוטמעות בארכיונים.
__DATE__/__TIME__/__TIMESTAMP__ ב-C/C++
הפקודות האלה תמיד יוצרות פלט שונה לגרסאות build שונות, ולכן לא כדאי להשתמש בהן. ריכזנו כאן כמה אפשרויות להסרת פקודות המאקרו האלה:
- להסיר אותם. לדוגמה, אפשר לעיין בכתובת https://android.googlesource.com/platform/system/core/+/30622bbb209db187f6851e4cf0cdaa147c2fca9f.
- כדי לזהות באופן ייחודי את הקובץ הבינארי שפועל, קוראים את מזהה ה-build מכותרת ה-ELF.
-
כדי לדעת מתי מערכת ההפעלה נוצרה, קוראים את
ro.build.date(האפשרות הזו פועלת לכל הגרסאות חוץ מגרסאות מצטברות, שבהן התאריך הזה לא מתעדכן). לדוגמה, אפשר לעיין בכתובת https://android.googlesource.com/platform/external/libchrome/+/8b7977eccc94f6b3a3896cd13b4aeacbfa1e0f84.
חותמות זמן מוטמעות בארכיונים (zip, jar)
ב-Android 7.0 תוקנה הבעיה של חותמות זמן מוטמעות בארכיוני zip, על ידי הוספת -X לכל השימושים בפקודה zip. הפעולה הזו מסירה את ה-UID/GID של ה-builder ואת חותמת הזמן המורחבת של Unix מקובץ ה-ZIP.
כלי חדש, ziptime (שנמצא ב-/platform/build/+/android16-qpr2-release/tools/ziptime/), מאפס את חותמות הזמן הרגילות בכותרות של קובצי ה-ZIP. פרטים נוספים מופיעים בקובץ ה-README.
כלי signapk מגדיר חותמות זמן לקובצי ה-APK, שיכולות להשתנות בהתאם לאזור הזמן של השרת. פרטים נוספים זמינים ב-CL
https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028.
כלי signapk מגדיר חותמות זמן לקובצי ה-APK, שיכולות להשתנות בהתאם לאזור הזמן של השרת. פרטים נוספים זמינים ב-CL
https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028.
מחרוזות גרסה
הבעיה: מחרוזות של גרסאות APK לרוב כללו את התו BUILD_NUMBER בסוף הגרסאות שהוצמדו להן. גם אם שום דבר אחר לא השתנה ב-APK, כתוצאה מכך, ה-APK עדיין יהיה שונה.
פתרון: מסירים את מספר ה-build ממחרוזת הגרסה של ה-APK.
דוגמאות:
- https://android.googlesource.com/platform/packages/apps/Camera2/+/5e0f4cf699a4c7c95e2c38ae3babe6f20c258d27
- https://android.googlesource.com/platform/build/+/d75d893da8f97a5c7781142aaa7a16cf1dbb669c
הפעלת חישוב של אימות במכשיר
אם dm-verity מופעל במכשיר, כלי ה-OTA בוחרים אוטומטית את הגדרת ה-verity ומפעילים את חישוב ה-verity במכשיר. כך אפשר לחשב בלוקים של verity במכשירי Android, במקום לאחסן אותם כבייטים גולמיים בחבילת ה-OTA. בלוקים של Verity יכולים להשתמש בכ-16MB למחיצה של 2GB.
עם זאת, חישוב האמינות במכשיר יכול להימשך זמן רב. במיוחד, יכול להיות שייקח הרבה זמן להשתמש בקוד לתיקון שגיאות של העברה. במכשירי Pixel, התהליך בדרך כלל נמשך עד 10 דקות. במכשירים בסיסיים, התהליך עשוי להימשך זמן רב יותר. אם רוצים להשבית את החישוב של אימות במכשיר, אבל עדיין להפעיל את dm-verity, אפשר לעשות זאת על ידי העברת --disable_fec_computation לכלי ota_from_target_files כשיוצרים עדכון OTA. הדגל הזה משבית את החישוב של האמינות במכשיר במהלך עדכונים דרך האוויר (OTA).
היא מקצרת את זמן ההתקנה של OTA, אבל מגדילה את גודל חבילת ה-OTA. אם התכונה dm-verity לא מופעלת במכשיר, העברת הדגל הזה לא משפיעה.
כלי בנייה עקביים
הבעיה: הכלים שיוצרים קבצים מותקנים צריכים להיות עקביים (אותו קלט צריך תמיד להפיק את אותו פלט).
פתרונות/דוגמאות: נדרשו שינויים בכלי הבנייה הבאים:
- יוצר קובץ NOTICE. היוצר של קובץ ה-NOTICE שונה כדי ליצור אוספים של הודעות שניתן לשחזר. אפשר לעיין ב-CL: https://android.googlesource.com/platform/build/+/8ae4984c2c8009e7a08e2a76b1762c2837ad4f64.
- Java Android Compiler Kit (Jack). נדרש עדכון ב-Jack toolchain כדי לטפל בשינויים מדי פעם בסדר של בנאי שנוצר. נוספו לשרשרת הכלים פונקציות גישה דטרמיניסטיות עבור קונסטרוקטורים: https://android.googlesource.com/toolchain/jack/+/056a5425b3ef57935206c19ecb198a89221ca64b.
- מהדר ART AOT (dex2oat). קובץ הבינארי של מהדר ART קיבל עדכון שנוספה בו אפשרות ליצור תמונה דטרמיניסטית: https://android.googlesource.com/platform/art/+/ace0dc1dd5480ad458e622085e51583653853fb9.
-
הקובץ libpac.so (V8). כל בנייה יוצרת קובץ
/system/lib/libpac.soשונה, כי תמונת ה-V8 משתנה בכל בנייה. הפתרון היה להסיר את התמונה: https://android.googlesource.com/platform/external/v8/+/e537f38c36600fd0f3026adba6b3f4cbcee1fb29. - קובצי pre-dexopt של אפליקציות (.odex). קבצי ה-pre-dexopt (.odex) הכילו ריפוד לא מאותחל במערכות 64 ביט. התיקון בוצע כאן: https://android.googlesource.com/platform/art/+/34ed3afc41820c72a3c0ab9770be66b6668aa029.
שימוש בכלי להשוואת גרסאות
במקרים שבהם אי אפשר למנוע שינויים בקבצים שקשורים לבנייה, ב-AOSP יש כלי להשוואת בנייה, target_files_diff.py, שאפשר להשתמש בו כדי להשוות בין שני חבילות קבצים. הכלי הזה מבצע השוואה רקורסיבית בין שני build, לא כולל שינויים נפוצים בקבצים שקשורים ל-build, כמו
- שינויים צפויים בפלט של הבנייה (לדוגמה, בגלל שינוי במספר הבנייה).
- שינויים שנובעים מבעיות מוכרות במערכת הבנייה הנוכחית.
כדי להשתמש בכלי להשוואת גרסאות build, מריצים את הפקודה הבאה:
target_files_diff.py dir1 dir2
dir1 ו-dir2 הן ספריות בסיס שמכילות את קובצי היעד שחולצו לכל בנייה.
שמירה על הקצאת חסימות עקבית
בקובץ נתון, למרות שהתוכן שלו נשאר זהה בין שתי גרסאות build, יכול להיות שהבלוקים בפועל שמכילים את הנתונים השתנו. כתוצאה מכך, תוכנת העדכון צריכה לבצע פעולות קלט/פלט מיותרות כדי להעביר את הבלוקים לצורך עדכון OTA.
בעדכון OTA של A/B וירטואלי, פעולות קלט/פלט מיותרות יכולות להגדיל מאוד את נפח האחסון שנדרש כדי לאחסן את תמונת המצב של העתקה בעת כתיבה. בעדכון OTA שאינו A/B, העברת הבלוקים לצורך עדכון OTA תורמת לזמן העדכון, כי יש יותר קלט/פלט בגלל העברת הבלוקים.
כדי לפתור את הבעיה הזו, ב-Android 7.0 Google הרחיבה את כלי make_ext4fs כדי לשמור על הקצאת בלוקים עקבית בגרסאות build שונות. הכלי make_ext4fs מקבל דגל -d base_fs אופציונלי שמנסה להקצות קבצים לאותם בלוקים כשיוצרים תמונה מסוג ext4. אפשר לחלץ את קובצי מיפוי הבלוקים (כמו קובצי המיפוי base_fs) מקובץ ה-ZIP של קובצי היעד של build קודם. לכל מחיצה ext4 יש קובץ .map בספרייה IMAGES (לדוגמה, IMAGES/system.map מתאים למחיצה system). אחר כך אפשר להכניס את קובצי base_fs האלה ולציין אותם באמצעות PRODUCT_<partition>_BASE_FS_PATH, כמו בדוגמה הזו:
PRODUCT_SYSTEM_BASE_FS_PATH := path/to/base_fs_files/base_system.map PRODUCT_SYSTEM_EXT_BASE_FS_PATH := path/to/base_fs_files/base_system_ext.map PRODUCT_VENDOR_BASE_FS_PATH := path/to/base_fs_files/base_vendor.map PRODUCT_PRODUCT_BASE_FS_PATH := path/to/base_fs_files/base_product.map PRODUCT_ODM_BASE_FS_PATH := path/to/base_fs_files/base_odm.map
השיטה הזו לא עוזרת להקטין את הגודל הכולל של חבילת ה-OTA, אבל היא משפרת את הביצועים של עדכון ה-OTA על ידי הקטנת כמות הקלט/פלט. במקרה של עדכוני A/B וירטואליים, נפח האחסון שנדרש להחלת העדכון דרך האוויר (OTA) מצטמצם באופן משמעותי.
לא לעדכן אפליקציות
בנוסף לצמצום ההבדלים בין הגרסאות, אפשר להקטין את גודל העדכונים ב-OTA על ידי החרגת עדכונים של אפליקציות שמתעדכנות דרך חנויות אפליקציות. חבילות APK כוללות בדרך כלל חלק משמעותי ממחיצות שונות במכשיר. הכללת הגרסאות האחרונות של אפליקציות שמתעדכנות על ידי חנויות אפליקציות בעדכון OTA עלולה להגדיל מאוד את גודל חבילות ה-OTA, ולא להועיל למשתמשים. כשמשתמשים מקבלים חבילת OTA, יכול להיות שהאפליקציה המעודכנת כבר מותקנת אצלם, או אפילו גרסה חדשה יותר שהם קיבלו ישירות מחנויות האפליקציות.