מדריך סגנון AIDL

השיטות המומלצות שמתוארות כאן עוזרות לפתח ממשקי AIDL באופן יעיל ובדגש על גמישות של הממשק, במיוחד כשמשתמשים ב-AIDL כדי להגדיר API או לקיים אינטראקציה עם פלטפורמות API.

אפשר להשתמש ב-AIDL כדי להגדיר API כשאפליקציות צריכות להתממשק אחת עם השנייה בתהליך ברקע או צריכות להתממשק עם המערכת. למידע נוסף על פיתוח ממשקי תכנות באפליקציות עם AIDL, ראו שפת ההגדרה של ממשק Android (AIDL). לדוגמאות של AIDL, ראו AIDL ל-HALs ו-Stable AIDL.

ניהול גרסאות

כל תמונת מצב שתואמת לאחור של AIDL API תואמת לגרסה. כדי לצלם תמונת מצב, מריצים את m <module-name>-freeze-api. בכל פעם שמשוחררים לקוח או שרת של ה-API (למשל, ברכבת ראשי), אתם צריכים לצלם תמונת מצב וליצור גרסה חדשה. כשמדובר בממשקי API מסוג מערכת לספק, זה אמור להתרחש בגרסה השנתית של הפלטפורמה.

לפרטים נוספים ומידע על סוגי השינויים המותרים, ראו ממשקים של ניהול גרסאות.

הנחיות לעיצוב API

כללי

‫1. לתעד הכול

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

2. כיסוי

צריך להשתמש בכיסוי גמל עליון לסוגים שונים ובכיסוי גמל תחתון לשיטות, לשדות ולארגומנטים. לדוגמה, MyParcelable עבור סוג מגרש ו-anArgument לארגומנט. בראשי תיבות, יש להתייחס לראשי התיבות כמילה (NFC -> Nfc).

[-Wconst-name] ערכי Enum וקבועים צריכים להיות ENUM_VALUE ו-CONSTANT_NAME

ממשקים

‫1. מתן שמות

[ -Winterface-name] שם הממשק צריך להתחיל ב-I, למשל IFoo.

2. נמנעים מממשק Big עם 'אובייקטים' שמבוססים על מזהה

יש להעדיף תת-ממשקים כשיש הרבה קריאות שקשורות ל-API ספציפי. כך אפשר ליהנות מהיתרונות הבאים:

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

לא מומלץ: ממשק יחיד וגדול עם אובייקטים המבוססים על מזהים

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

מומלץ: ממשקים נפרדים

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. לא לשלב שיטות חד-כיווניות עם שיטות דו-כיווניות.

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

4. הימנעות מהחזרת קודי סטטוס

ה-methods צריכות להימנע מקודי סטטוס כערכי החזרה, כי לכל methods של AIDL יש קוד החזרת סטטוס מרומז. אפשר לעיין ב-ServiceSpecificException או ב-EX_SERVICE_SPECIFIC. באופן המוסכמה, הערכים האלה מוגדרים כקבועים בממשק AIDL. מידע מפורט יותר מופיע בקטע טיפול בשגיאות בקצוות העורפיים של AIDL.

5. מערכים כפרמטרים של פלט שנחשבים מזיקים

[-Wout-array] שיטות שכוללות פרמטרים של פלט מערך, כמו void foo(out String[] ret), הן בדרך כלל לא תקינות כי השרת צריך להצהיר על גודל מערך הפלט ולהקצות אותו ב-Java, ולכן השרת לא יכול לבחור את גודל פלט המערך. זו התנהגות לא רצויה בגלל האופן שבו מערכים פועלים ב-Java (לא ניתן להעביר אותם מחדש). במקום זאת, עדיף להשתמש בממשקי API כמו String[] foo().

6. הימנעות מפרמטרים של Inout

[-Winout-parameter] זה עלול לבלבל את הלקוחות כי אפילו פרמטרים של in נראים כמו פרמטרים של out.

7. נמנעים מפלט או מפלט של פרמטרים מסוג @nullable שאינם מערך

[-Wout-nullable] מכיוון שהקצה העורפי של Java לא מטפל בהערת @nullable בזמן שקצוות עורפיים אחרים כן, out/inout @nullable T עלול להוביל להתנהגות לא עקבית בקצוות העורפיים. לדוגמה, קצוות עורפיים שאינם של Java יכולים להגדיר את הפרמטר @nullable כ-null (ב-C++ כשמגדירים אותו כ-std::nullopt), אבל לקוח Java לא יכול לקרוא אותו כ-null.

מגרשים מובנים

‫1. מתי להשתמש?

כדאי להשתמש במגרשים מובְנים שבהם יש כמה סוגי נתונים לשליחה.

לחלופין, אם יש לכם סוג נתונים אחד אבל סביר להניח שתצטרכו להרחיב אותו בעתיד. לדוגמה, אין להשתמש ב-String username. צריך להשתמש במארז שניתן להרחבה, כמו:

parcelable User {
    String username;
}

כדי שבעתיד תוכלו להאריך אותו באופן הבא:

parcelable User {
    String username;
    int id;
}

2. צריך לציין ברירות מחדל באופן מפורש

[-Wexplicit-default, -Wenum-explicit-default] כדי לציין ברירות מחדל מפורשות לשדות.

מגרשים לא מובנים

‫1. מתי להשתמש?

מגרשים לא מובנים זמינים ב-Java עם @JavaOnlyStableParcelable ובקצה העורפי של NDK עם @NdkOnlyStableParcelable. בדרך כלל מדובר במגרשים ישנים וקיימים שאי אפשר לבנות.

קבועים וטיפוסים בני מנייה (enum)

‫1. שדות ביט צריכים להשתמש בשדות קבועים

שדות Bitfield צריכים להשתמש בשדות קבועים (לדוגמה, const int FOO = 3; בממשק).

2. ערכים צריכים להיות קבוצות סגורות.

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

3. הימנעות מערכים כמו "NUM_ElementS"

מכיוון שלטיפוסים יש גרסאות, יש להימנע מערכים שמציינים כמה ערכים קיימים. ב-C++ אפשר לעקוף את הבעיה הזו עם enum_range<>. לחלודה, משתמשים ב-enum_values(). ב-Java, עדיין אין פתרון.

לא מומלץ: שימוש בערכים ממוספרים

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. נמנעים מקידומות וסיומות מיותרות

[-Wredundant-name] מומלץ להימנע מקידומות וסיומות מיותרות או שחוזרות על עצמן בקבועים ובספירות.

לא מומלץ: שימוש בקידומת מיותרת

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

מומלץ: מתן שם ישיר ל-enum

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor (תיאור קובץ)

[-Wfile-descriptor] מומלץ מאוד לא להשתמש ב-FileDescriptor כארגומנט או בערך המוחזר של שיטת ממשק AIDL. במיוחד, כאשר ה-AIDL מוטמע ב-Java, עלולה להיות דליפה של מתאר קובץ אם לא תטופלו בזהירות. בעיקרון, אם מקבלים FileDescriptor, צריך לסגור אותו באופן ידני כשלא משתמשים בו יותר.

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

במקום זאת, אפשר להשתמש בכלי ParcelFileDescriptor, שניתן לסגירה אוטומטית.

יחידות משתנים

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

דוגמאות

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

חותמות זמן צריכות לציין את הייחוס שלהן

חותמות הזמן (למעשה, כל היחידות!) חייבות לציין בבירור את היחידות ואת נקודות ההתייחסות שלהן.

דוגמאות

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;