APEX של הספק

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

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

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

מודולריזציה של קובצי אימג' של ספקים

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

כשתמונות של ספקים נוצרות כשי combination של 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, אלא אם יש להם ממשקים יציבים.

ממשקים מקומיים יציבים ליחסי תלות של ספקים ב-APEX כוללים את cc_library עם stubs ואת הספריות LLNDK. יחסי התלות האלה לא נכללים באריזות, והם מתועדים במניפסט של APEX. קובץ ה-manifest מעובד על ידי 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, צריך לספק את הספריות והקובצי ה-binary התואמים בתוך APEX של הספק, בדומה לדוגמאות הבאות:

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

קטעי VINTF

אפשר להציג קטעי 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, משתמשים בממשקי ה-API של libbinder_ndk כדי לקבל את המיפויים של ממשקי 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. לדוגמה, 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 סורק את הספריות /apex/*/etc/firmware כדי למצוא מודולים של קושחת.

ה-file_contexts של ה-APEX צריך לתייג כראוי את כל הרשומות של עומסי העבודה של הקושחה כדי לוודא של-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 צריך לתייג כראוי את כל הרשומות של עומסי העבודה של מודול הליבה. לדוגמה:

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

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

my_init.rc:

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

שכבות-על של משאבים בזמן ריצה

דוגמה:

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

דוגמאות:

Bootstrap Vendor APEXes

חלק משירותי HAL, כמו keymint, אמורים להיות זמינים לפני הפעלת ה-APEX. בדרך כלל, ב-HALs האלה מוגדר early_hal בהגדרת השירות שלהם בסקריפט ה-init. דוגמה נוספת היא המחלקה animation, שמתחילה בדרך כלל לפני האירוע post-fs-data. כששירות HAL מוקדם כזה ארוז ב-APEX של הספק, צריך ליצור את ה-apex "vendorBootstrap": true במניפסט APEX שלו כדי שניתן יהיה להפעיל אותו מוקדם יותר. שימו לב שאפשר להפעיל את ה-APEX של ה-bootstrap רק מהמיקום שנוצר מראש, כמו /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. מאחר שמנהלי HAL מוקדמים משתמשים ב-Vulkan driver, צריך ליצור את APEX Bootstrap APEX ולהגדיר את מרחב השמות של ה-linker כך שיהיה גלוי.

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

תכונות פיתוח נוספות

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

דוגמה:

מפתחים יכולים גם להתקין כמה גרסאות של APEX של ספקים שיש להם את אותו שם ומפתח APEX, ואז לבחור איזו גרסה תופעל בכל הפעלה באמצעות sysprops מתמידים. בתרחישי שימוש מסוימים למפתחים, האפשרות הזו עשויה להיות פשוטה יותר מאשר התקנה של עותק חדש של APEX באמצעות adb install.

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

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

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

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

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

הערך של המאפיין צריך להיות שם הקובץ של ה-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";