APEX של הספק

אתם יכולים להשתמש בפורמט הקובץ APEX כדי לארוז ולהתקין מודולים ברמה נמוכה יותר של מערכת ההפעלה Android. היא מאפשרת ליצור ולהתקין באופן עצמאי רכיבים כמו שירותים וספריות מקוריים, יישומי HAL, קושחה, קובצי תצורה וכו'.

מערכת ה-Build מתקינה באופן אוטומטי את קובצי ה-APEX של הספק במחיצה /vendor ומפעילה אותם בזמן הריצה באמצעות apexd, בדיוק כמו קובצי APEX במחיצות אחרות.

תרחישים לדוגמה

מודולריזציה של תמונות ספקים

חבילות APEX מאפשרות לאגד באופן טבעי ולחלק למודולים יישומי תכונות בתמונות של ספקים.

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

לדוגמה, יצרן ציוד מקורי יכול לבחור להרכיב את המכשיר שלו עם APEX של הטמעת Wi-Fi ב-AOSP, עם APEX של הטמעת Bluetooth ב-SoC ועם APEX של הטמעת טלפוניה מותאמת אישית של יצרן הציוד המקורי.

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

איטרציה של מפתח

חבילות APEX של ספקים עוזרות למפתחים לבצע איטרציות מהר יותר במהלך פיתוח מודולים של ספקים. הן עושות זאת על ידי איגוד של יישום תכונה שלמה, כמו HAL של Wi-Fi, בתוך חבילת APEX של ספק. לאחר מכן, המפתחים יכולים לבנות ולדחוף בנפרד את ספק ה-APEX כדי לבדוק שינויים, במקום לבנות מחדש את כל תמונת הספק.

השינוי הזה מפשט ומקצר את מחזור האיטרציה של מפתחים שעובדים בעיקר בתחום תכונה אחד ורוצים לבצע איטרציה רק בתחום התכונה הזה.

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

בנוסף, כשמאגדים תחום תכונות ב-APEX, קל יותר לבצע ניפוי באגים או לחזור למצב הקודם כשמזהים התנהגות לא תקינה של המכשיר. לדוגמה, אם הטלפוניה לא פועלת בצורה טובה בגרסה חדשה, המפתחים יכולים לנסות להתקין במכשיר APEX ישן יותר של הטלפוניה (בלי צורך בהפעלה של גרסה מלאה) ולבדוק אם הפעולה התקינה חוזרת.

תהליך עבודה לדוגמה:

# Build the entire device and flash. OR, obtain an already-flashed device.
source build/envsetup.sh && lunch oem_device-userdebug
m
fastboot flashall -w

# Test the device.
... testing ...

# Check previous behavior using a vendor APEX from one week ago, downloaded from
# your continuous integration build.
... download command ...
adb install <path to downloaded APEX>
adb reboot
... testing ...

# Edit and rebuild just the APEX to change and test behavior.
... edit APEX source contents ...
m <apex module name>
adb install out/<path to built APEX>
adb reboot
... testing ...

דוגמאות

יסודות

בדף הראשי בנושא פורמט קובץ APEX מופיע מידע כללי על APEX, כולל דרישות המכשיר, פרטים על פורמט הקובץ ושלבי ההתקנה.

ב-Android.bp, הגדרת המאפיין vendor: true הופכת מודול APEX ל-APEX של ספק.

apex {
  ..
  vendor: true,
  ..
}

קבצים בינאריים וספריות משותפות

חבילת APEX כוללת יחסי תלות טרנזיטיביים בתוך מטען ה-APEX, אלא אם יש להם ממשקים יציבים.

ממשקי Native יציבים ליחסי תלות של ספקי APEX כוללים cc_library עם stubs וספריות LLNDK. התלויות האלה לא נכללות באריזה, והתלויות מתועדות במניפסט של APEX. קובץ המניפסט עובר עיבוד על ידי linkerconfig כדי שהתלויות החיצוניות המקוריות יהיו זמינות בזמן הריצה.

בקטע הקוד הבא, קובץ ה-APEX מכיל גם את הקובץ הבינארי (my_service) וגם את התלויות הלא יציבות שלו (קבצי *.so).

apex {
  ..
  vendor: true,
  binaries: ["my_service"],
  ..
}

בקטע הקוד הבא, קובץ ה-APEX מכיל את הספרייה המשותפת my_standalone_libואת כל יחסי התלות הלא יציבים שלה (כפי שמתואר למעלה).

apex {
  ..
  vendor: true,
  native_shared_libs: ["my_standalone_lib"],
  ..
}

הקטנת APEX

יכול להיות ש-APEX יגדל כי הוא כולל תלות לא יציבה. מומלץ להשתמש בקישור סטטי. אפשר לקשר באופן סטטי ספריות נפוצות כמו libc++.so ו-libbase.so לקבצים בינאריים של HAL. אפשרות נוספת היא ליצור תלות כדי לספק ממשק יציב. התלות לא תצורף לחבילת ה-APEX.

הטמעות של HAL

כדי להגדיר הטמעה של HAL, צריך לספק את הקבצים הבינאריים והספריות המתאימים בתוך ספק APEX, בדומה לדוגמאות הבאות:

כדי להקיף את ההטמעה של HAL, צריך לציין ב-APEX גם את כל קטעי ה-VINTF הרלוונטיים ואת סקריפטים של init.

VINTF fragments

אפשר להציג רכיבי VINTF מ-APEX של ספק כשהרכיבים נמצאים ב-etc/vintf של ה-APEX.

משתמשים במאפיין prebuilts כדי להטמיע את קטעי ה-VINTF ב-APEX.

apex {
  ..
  vendor: true,
  prebuilts: ["fragment.xml"],
  ..
}

prebuilt_etc {
  name: "fragment.xml",
  src: "fragment.xml",
  sub_dir: "vintf",
}

Query APIs

כשמוסיפים פרגמנטים של VINTF ל-APEX, משתמשים בממשקי libbinder_ndk API כדי לקבל את המיפויים של ממשקי HAL ושמות APEX.

  • AServiceManager_isUpdatableViaApex("com.android.foo.IFoo/default") : true אם מופע ה-HAL מוגדר ב-APEX.
  • AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...) : מקבל את שם ה-APEX שמגדיר את מופע ה-HAL.
  • AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...) : משמש לפתיחת HAL של העברה.

סקריפטים של init

חבילות APEX יכולות לכלול סקריפטים של init בשתי דרכים: (א) קובץ טקסט מוכן מראש בתוך מטען ה-APEX, או (ב) סקריפט init רגיל ב-/vendor/etc. אפשר להגדיר את שניהם לאותו APEX.

סקריפט Init ב-APEX:

prebuilt_etc {
  name: "myinit.rc",
  src: "myinit.rc"
}

apex {
  ..
  vendor: true,
  prebuilts: ["myinit.rc"],
  ..
}

סקריפטים של init ב-APEX של ספקים יכולים לכלול הגדרות של service והוראות של on <property or event>.

מוודאים שהגדרת service מפנה לקובץ בינארי באותו APEX. לדוגמה, com.android.foo APEX עשוי להגדיר שירות בשם foo-service.

on foo-service /apex/com.android.foo/bin/foo
  ...

חשוב להיזהר כשמשתמשים בהנחיות on. מכיוון שסקריפטים של init ב-APEXes מנותחים ומופעלים אחרי שה-APEXes מופעלים, אי אפשר להשתמש בחלק מהאירועים או המאפיינים. משתמשים ב-apex.all.ready=true כדי להפעיל פעולות מוקדם ככל האפשר. Bootstrap APEXes יכולים להשתמש ב-on init, אבל לא ב-on early-init.

קושחה

דוגמה:

משבצים קושחה ב-APEX של ספק באמצעות סוג המודול prebuilt_firmware, באופן הבא.

prebuilt_firmware {
  name: "my.bin",
  src: "path_to_prebuilt_firmware",
  vendor: true,
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.bin"],  // installed inside APEX as /etc/firmware/my.bin
  ..
}

מודולים של prebuilt_firmware מותקנים בספרייה <apex name>/etc/firmware של APEX. ‫ueventd scans /apex/*/etc/firmware directories to find firmware modules.

ה-file_contexts של ה-APEX צריך לתייג כראוי את כל רכיבי ה-payload של הקושחה כדי להבטיח ש-ueventd יוכל לגשת לקבצים האלה בזמן הריצה. בדרך כלל התווית vendor_file מספיקה. לדוגמה:

(/.*)? u:object_r:vendor_file:s0

מודולים של ליבת מערכת ההפעלה

כדי להטמיע מודולים של ליבת מערכת ההפעלה ב-APEX של ספק כמודולים מוכנים מראש, פועלים לפי השלבים הבאים.

prebuilt_etc {
  name: "my.ko",
  src: "my.ko",
  vendor: true,
  sub_dir: "modules"
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.ko"],  // installed inside APEX as /etc/modules/my.ko
  ..
}

השדה file_contexts של APEX צריך לתייג בצורה נכונה את כל רשומות המטען הייעודי (payload) של מודול הליבה. לדוגמה:

/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0

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

my_init.rc:

on early-boot
  insmod /apex/myapex/etc/modules/my.ko
  ..

חבילות של שכבות-על בזמן ריצה (RRO)

דוגמה:

הטמעת שכבות-על של משאבים בזמן ריצה ב-APEX של ספק באמצעות המאפיין rros.

runtime_resource_overlay {
    name: "my_rro",
    soc_specific: true,
}


apex {
  ..
  vendor: true,
  rros: ["my_rro"],  // installed inside APEX as /overlay/my_rro.apk
  ..
}

קבצים אחרים של הגדרות

מודולי APEX של ספקים תומכים בקובצי הגדרה שונים אחרים שנמצאים בדרך כלל במחיצת הספק כקובצי prebuilt בתוך מודולי APEX של ספקים, ונוספים עוד קבצים כאלה.

דוגמאות:

אתחול של קובצי APEX של ספקים

חלק משירותי HAL, כמו keymint, צריכים להיות זמינים לפני שמפעילים את חבילות ה-APEX. בדרך כלל, קובצי ה-HAL האלה מוגדרים ב-early_hal בהגדרת השירות שלהם בסקריפט ההפעלה. דוגמה נוספת היא מחלקת animation, שבדרך כלל מתחילה לפני אירוע post-fs-data. כששירות HAL מוקדם כזה נארז ב-APEX של הספק, צריך להוסיף את ה-APEX "vendorBootstrap": true למניפסט ה-APEX שלו כדי שאפשר יהיה להפעיל אותו בשלב מוקדם יותר. שימו לב שאפשר להפעיל bootstrap APEXes רק מהמיקום המובנה כמו /vendor/apex, ולא מ-/data/apex.

מאפייני מערכת

אלה מאפייני המערכת שהמסגרת קוראת כדי לתמוך ב-APEX של ספקים:

  • input_device.config_file.apex=<apex name> – אם ההגדרה הזו מופעלת, המערכת מחפשת את קובצי התצורה של הקלט (*.idc,‏ *.kl ו-*.kcm) בספרייה /etc/usr של ה-APEX.
  • ro.vulkan.apex=<apex name> – אם הערך מוגדר, מנהל ההתקן של Vulkan נטען מ-APEX. מכיוון שמנהל ההתקן של Vulkan משמש ל-HAL מוקדמים, צריך ליצור את Bootstrap APEX של APEX ולהגדיר את מרחב השמות של המקשר כך שיהיה גלוי.

מגדירים את מאפייני המערכת בסקריפטים של init באמצעות הפקודה setprop.

תכונות נוספות

בחירת APEX בזמן האתחול

דוגמה:

אפשר להפעיל את ספקי ה-APEX במהלך האתחול. אם מציינים שם קובץ באמצעות מאפיין המערכת ro.vendor.apex.<apex name>, רק ה-APEX שתואם לשם הקובץ מופעל עבור <apex name> הספציפי. המערכת מתעלמת מ-APEX עם <apex name> (הוא לא מופעל) אם מאפיין המערכת הזה מוגדר ל-none. אתם יכולים להשתמש בתכונה הזו כדי להתקין כמה עותקים של APEX עם אותו שם. אם יש כמה גרסאות של אותו APEX, הן צריכות לחלוק את אותו מפתח.

תרחישים לדוגמה:

  • התקנה של 3 גרסאות של ספק ה-APEX של ה-HAL של ה-Wi-Fi: צוותי QA יכולים להריץ בדיקות ידניות או אוטומטיות באמצעות גרסה אחת, ואז להפעיל מחדש לגרסה אחרת ולהריץ מחדש את הבדיקות, ואז להשוות את התוצאות הסופיות.
  • התקנה של 2 גרסאות של ספק ה-HAL של המצלמה APEX, הגרסה הנוכחית והגרסה הניסיונית: משתמשי Dogfood יכולים להשתמש בגרסה הניסיונית בלי להוריד ולהתקין קובץ נוסף, כך שהם יכולים לחזור בקלות לגרסה הקודמת.

במהלך האתחול, apexd מחפש sysprops בפורמט ספציפי כדי להפעיל את גרסת ה-APEX הנכונה.

הפורמטים הנדרשים למפתח המאפיין הם:

  • Bootconfig
    • המאפיין משמש להגדרת ערך ברירת המחדל ב-BoardConfig.mk.
    • androidboot.vendor.apex.<apex name>
  • מאפיין מערכת מתמשך
    • משמש לשינוי ערך ברירת המחדל שמוגדר במכשיר שכבר הופעל.
    • אם יש ערך, הוא מחליף את הערך של bootconfig.
    • persist.vendor.apex.<apex name>

ערך המאפיין צריך להיות שם הקובץ של ה-APEX שרוצים להפעיל, או none כדי להשבית את ה-APEX.

// Default version.
apex {
  name: "com.oem.camera.hal.my_apex_default",
  vendor: true,
  ..
}

// Non-default version.
apex {
  name: "com.oem.camera.hal.my_apex_experimental",
  vendor: true,
  ..
}

צריך גם להגדיר את גרסת ברירת המחדל באמצעות bootconfig ב-BoardConfig.mk:

# Example for APEX "com.oem.camera.hal" with the default above:
BOARD_BOOTCONFIG += \
    androidboot.vendor.apex.com.oem.camera.hal=com.oem.camera.hal.my_apex_default

אחרי שהמכשיר מופעל, משנים את הגרסה שהופעלה על ידי הגדרת sysprop מתמשך:

$ adb root;
$ adb shell setprop \
    persist.vendor.apex.com.oem.camera.hal \
    com.oem.camera.hal.my_apex_experimental;
$ adb reboot;

אם המכשיר תומך בעדכון של bootconfig אחרי ההפעלה (למשל באמצעות פקודות fastboot oem), שינוי המאפיין bootconfig עבור APEX שהותקן בכמה מקומות משנה גם את הגרסה שמופעלת בהפעלה.

במכשירי הפניה וירטואליים שמבוססים על Cuttlefish, אפשר להשתמש בפקודה --extra_bootconfig_args כדי להגדיר את המאפיין bootconfig ישירות במהלך ההפעלה. לדוגמה:

launch_cvd --noresume \
  --extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";