בדף הזה מתוארים שינויים שנוספו ל-AOSP כדי לצמצם שינויים מיותרים בקבצים בין גרסאות build. מיישמי מכשירים שמנהלים מערכות בנייה משלהם יכולים להשתמש במידע הזה כהנחיה לצמצום הגודל של העדכונים שלהם דרך האוויר (OTA).
לפעמים עדכוני OTA של Android מכילים קבצים ששונו ולא תואמים לשינויים בקוד. הם למעשה ארטיפקטים של מערכת build. מצב כזה יכול לקרות כשאותו קוד, שנבנה בזמנים שונים, מספרייה שונה או במכונות שונות, יוצר מספר גדול של קבצים שעברו שינוי. קבצים מיותרים כאלה מגדילים את הגודל של תיקון OTA, ומקשים על זיהוי הקוד ששונה.
כדי להפוך את התוכן של OTA לשקוף יותר, ב-AOSP יש שינויים במערכת build שנועדו להקטין את הגודל של תיקוני OTA. בוטלו שינויים מיותרים בקבצים בין גרסאות build, ועדכוני OTA מכילים רק קבצים שקשורים לתיקון. AOSP כולל גם כלי להשוואת גרסאות, שמסנן שינויים נפוצים בקבצים שקשורים לגרסה כדי לספק השוואה נקייה יותר של קובץ הגרסה, וכלי למיפוי בלוקים, שעוזר לשמור על עקביות בהקצאת בלוקים.
מערכת build יכולה ליצור טלאים גדולים שלא לצורך בכמה דרכים. כדי לפתור את הבעיה הזו, ב-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, ממיינים את הקלט, ולכן לפני שממיינים את הקבצים, צריך לוודא שהכלי שבו משתמשים לא עשה זאת כבר.
דוגמאות: תוקנו הרבה מקרים במערכת build המרכזית באמצעות פקודת המאקרו 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.
חותמות זמן
בעיה: חותמות הזמן בפלט של ה-build גורמות לשינויים מיותרים בקובץ. סביר להניח שהמצב הזה יקרה במיקומים הבאים:
__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 של הבונה ואת חותמת הזמן המורחבת של Unix מקובץ ה-ZIP.
כלי חדש, ziptime (שנמצא ב-/platform/build/+/android17-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 במכשירי 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 כדי לטפל בשינויים מדי פעם בסדר של בנאים שנוצרו. נוספו לערכת הכלים פונקציות גישה דטרמיניסטיות עבור קונסטרוקטורים: 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. - קובצי .odex של אפליקציות שעברו אופטימיזציה מראש (pre-dexopt). קבצי ה-pre-dexopt (.odex) הכילו ריפוד לא מאותחל במערכות 64 ביט. התיקון בוצע כאן: https://android.googlesource.com/platform/art/+/34ed3afc41820c72a3c0ab9770be66b6668aa029.
שימוש בכלי להשוואת גרסאות
במקרים שבהם אי אפשר לבטל שינויים בקבצים שקשורים לבנייה, ב-AOSP יש כלי להשוואת בנייה, target_files_diff.py, שאפשר להשתמש בו כדי להשוות בין שני חבילות קבצים. הכלי הזה מבצע השוואה רקורסיבית בין שני build, לא כולל שינויים נפוצים בקבצים שקשורים ל-build, כמו
- שינויים צפויים בפלט של ה-build (לדוגמה, בגלל שינוי במספר ה-build).
- שינויים שנובעים מבעיות מוכרות במערכת ה-build הנוכחית.
כדי להשתמש בכלי להשוואת גרסאות, מריצים את הפקודה הבאה:
target_files_diff.py dir1 dir2
dir1 ו-dir2 הן ספריות בסיס שמכילות את קובצי היעד שחולצו לכל בנייה.
שמירה על הקצאת חסימות עקבית
בקובץ נתון, למרות שהתוכן שלו נשאר זהה בין שני מבנים, יכול להיות שהבלוקים בפועל שמכילים את הנתונים השתנו. כתוצאה מכך, תוכנת העדכון צריכה לבצע פעולות קלט/פלט מיותרות כדי להעביר את הבלוקים לצורך עדכון OTA.
בעדכון OTA של בדיקת A/B וירטואלית, פעולות קלט/פלט מיותרות יכולות להגדיל מאוד את נפח האחסון שנדרש כדי לאחסן את תמונת המצב של העתקה בעת כתיבה. בעדכון OTA שהוא לא A/B, העברת הבלוקים לצורך עדכון OTA תורמת לזמן העדכון כי יש יותר קלט/פלט בגלל העברת הבלוקים.
כדי לפתור את הבעיה הזו, ב-Android 7.0 Google הרחיבה את כלי make_ext4fs כדי לשמור על הקצאת בלוקים עקבית בכל הגרסאות. הכלי make_ext4fs מקבל דגל -d base_fs אופציונלי שמנסה להקצות קבצים לאותם בלוקים כשיוצרים תמונה של ext4. אפשר לחלץ את קובצי מיפוי הבלוקים (כמו קובצי המיפוי base_fs) מקובץ ה-ZIP של קובצי היעד של build קודם. לכל מחיצה ext4 יש קובץ .map בספרייה IMAGES (לדוגמה, IMAGES/system.map תואם למחיצה system). אחר כך אפשר לבצע Check-in לקובצי 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, יכול להיות שהאפליקציה המעודכנת כבר מותקנת אצלם, או אפילו גרסה חדשה יותר שהם קיבלו ישירות מחנויות האפליקציות.