מדריך סגנונות AIDL

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

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

גירסאות

כל תמונת מצב תואמת לאחור של API של AIDL מתאימה לגרסה. כדי לצלם תמונת מצב, הפעל 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 like IFoo .

2. הימנע מממשק גדול עם "אובייקטים" מבוססי זיהוי

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

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

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

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

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

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

6. הימנע מפרמטרים של inout

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

7. הימנע מ-out/inout @nullable פרמטרים שאינם מערך

[-Wout-nullable] מאחר ש-Java backend אינו מטפל בהערה @nullable בעוד ש-backends אחרים עושים זאת, out/inout @nullable T עלול להוביל להתנהגות לא עקבית בין backends. לדוגמה, קצה אחורי שאינם של Java יכולים להגדיר פרמטר out @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 backend עם @NdkOnlyStableParcelable . בדרך כלל, מדובר בחבילות ישנות וקיימות שלא ניתן לבנות בקלות.

קבועים ומינומים

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

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

2. Enums צריך להיות סטים סגורים.

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

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

מכיוון שסכמות מנוסחות, יש להימנע מערכים המציינים כמה ערכים קיימים. ב-C++, ניתן לעקוף את זה עם, enum_range<> . עבור Rust, השתמש 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;