בדף הזה מתוארים שינויים שנוספו ל-AOSP כדי לצמצם שינויים מיותרים בקבצים בין גרסאות build. מיישמי מכשירים שמנהלים מערכות בנייה משלהם יכולים להשתמש במידע הזה כהנחיה לצמצום הגודל של העדכונים שלהם דרך האוויר (OTA).
לפעמים עדכוני Android OTA מכילים קבצים ששונו ולא תואמים לשינויים בקוד. הם למעשה ארטיפקטים של מערכת הבנייה. מצב כזה יכול לקרות כשאותו קוד, שנבנה בזמנים שונים, מספרייה שונה או במכונות שונות, יוצר מספר גדול של קבצים ששונו. קבצים מיותרים כאלה מגדילים את הגודל של תיקון 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 recompression, כלי דטרמיניסטי לתיקון של deflate streams, שמטפל בפונקציות הדחיסה וההשוואה ליצירת עדכוני 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.
חותמות זמן
הבעיה: חותמות הזמן בפלט של ה-build גורמות לשינויים מיותרים בקובץ. סביר להניח שזה יקרה במיקומים הבאים:
__DATE__/__TIME__/__TIMESTAMP__
פקודות מאקרו בקוד C או C++.- חותמות זמן שמוטמעות בארכיונים מבוססי-ZIP.
פתרונות/דוגמאות: כדי להסיר את חותמות הזמן מהפלט של הבנייה, צריך לפעול לפי ההוראות שבהמשך בנושא __DATE__/__TIME__/__TIMESTAMP__ ב-C/C++ וחותמות זמן מוטמעות בארכיונים.
__DATE__/__TIME__/__TIMESTAMP__ ב-C/C++
הפקודות האלה תמיד יוצרות פלט שונה עבור מבנים שונים, ולכן לא כדאי להשתמש בהן. ריכזנו כאן כמה אפשרויות להסרת פקודות המאקרו האלה:
- להסיר אותם. לדוגמה, אפשר לעיין בכתובת 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/+/android16-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, היא עדיין תהיה שונה.
פתרון: מסירים את מספר ה-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 כדי לטפל בשינויים מדי פעם בסדר של בנאים שנוצרו. נוספו לשרשרת הכלים פונקציות גישה דטרמיניסטיות עבור בוני אובייקטים: 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
שונה, כי ה-snapshot של V8 משתנה בכל בנייה. הפתרון היה להסיר את התמונה: https://android.googlesource.com/platform/external/v8/+/e537f38c36600fd0f3026adba6b3f4cbcee1fb29. - קובצי dexopt מראש של אפליקציות (.odex). קבצי ה-pre-dexopt (.odex) הכילו ריווח לא מאותחל במערכות 64 ביט. התיקון בוצע כאן: https://android.googlesource.com/platform/art/+/34ed3afc41820c72a3c0ab9770be66b6668aa029.
שימוש בכלי להשוואת גרסאות
במקרים שבהם אי אפשר למנוע שינויים בקבצים שקשורים לבנייה, ב-AOSP יש כלי להשוואת בנייה, target_files_diff.py
, שאפשר להשתמש בו כדי להשוות בין שני חבילות קבצים. הכלי הזה מבצע השוואה רקורסיבית בין שני build, לא כולל שינויים נפוצים בקבצים שקשורים ל-build, כמו
- שינויים צפויים בפלט של הבנייה (לדוגמה, בגלל שינוי במספר הבנייה).
- שינויים שנובעים מבעיות מוכרות במערכת הבנייה הנוכחית.
כדי להשתמש בכלי להשוואת גרסאות, מריצים את הפקודה הבאה:
target_files_diff.py dir1 dir2
dir1
ו-dir2
הן ספריות בסיס שמכילות את קובצי היעד שחולצו לכל build.
שמירה על עקביות בהקצאת החסימות
בקובץ נתון, למרות שהתוכן שלו נשאר זהה בין שתי גרסאות build, יכול להיות שהבלוקים בפועל שמכילים את הנתונים השתנו. כתוצאה מכך, תוכנת העדכון צריכה לבצע פעולות קלט/פלט מיותרות כדי להזיז את הבלוקים לצורך עדכון 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
). אחר כך אפשר לבצע צ'ק-אין לקובצי 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, יכול להיות שהאפליקציה המעודכנת כבר מותקנת אצלם, או אפילו גרסה חדשה יותר שהם קיבלו ישירות מחנויות האפליקציות.