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 גם מפשטת את תהליך איתור הבאגים או החזרה למצב הקודם כשמזהים התנהגות לא תקינה של המכשיר. לדוגמה, אם הטלפוניה לא פועלת בצורה טובה בגרסת build חדשה, המפתחים יכולים לנסות להתקין במכשיר APEX של הטמעה ישנה יותר של טלפוניה (בלי צורך בצריבת ROM‏ (flash) של גרסת build מלאה) ולבדוק אם הפעולה התקינה משוחזרת.

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

# 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 הרלוונטיים ואת סקריפטים ההפעלה.

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",
}

ממשקי API של שאילתות

כשמוסיפים מקטעי 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.

סקריפט הפעלה ב-APEX:

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

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

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

מוודאים שהגדרת service מפנה לקובץ בינארי באותו APEX. לדוגמה, חבילת APEX‏ com.android.foo עשויה להגדיר שירות בשם 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 של ספקים, ונוספים עוד קבצים כאלה.

לדוגמה:

Bootstrap Vendor APEXes

חלק משירותי 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 מוקדמים, צריך ליצור את ה-APEX Bootstrap 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 מחפש מאפייני מערכת בפורמט ספציפי כדי להפעיל את גרסת ה-APEX הנכונה.

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

  • Bootconfig
    • המאפיין משמש להגדרת ערך ברירת המחדל ב-BoardConfig.mk.
    • androidboot.vendor.apex.<apex name>
  • Persistent sysprop
    • משמש לשינוי ערך ברירת המחדל שמוגדר במכשיר שכבר הופעל.
    • אם יש ערך, הוא מחליף את הערך של 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";