คู่มือรูปแบบ AIDL

แนวทางปฏิบัติแนะนำที่ระบุไว้ที่นี่เป็นแนวทางในการพัฒนาอินเทอร์เฟซ AIDL อย่างมีประสิทธิภาพและโดยคำนึงถึงความยืดหยุ่นของอินเทอร์เฟซ โดยเฉพาะอย่างยิ่งเมื่อมีการใช้ AIDL เพื่อกำหนด API หรือโต้ตอบกับแพลตฟอร์ม API

AIDL สามารถใช้เพื่อกำหนด API เมื่อแอปต้องอินเทอร์เฟซกันในกระบวนการที่ทำงานอยู่เบื้องหลังหรือจำเป็นต้องเชื่อมต่อกับระบบ ดูข้อมูลเพิ่มเติมเกี่ยวกับการพัฒนาอินเทอร์เฟซการเขียนโปรแกรมในแอปด้วย AIDL ได้ที่ Android Interface Definition Language (AIDL) ดูตัวอย่างของ AIDL ในทางปฏิบัติได้ที่ AIDL สำหรับ HAL และ AIDL แบบคงที่

การกำหนดเวอร์ชัน

สแนปชอตที่เข้ากันได้แบบย้อนหลังทั้งหมดของ AIDL API จะสอดคล้องกับเวอร์ชันหนึ่งๆ หากต้องการถ่ายสแนปชอต ให้เรียกใช้ m <module-name>-freeze-api เมื่อใดก็ตามที่ไคลเอ็นต์หรือเซิร์ฟเวอร์ของ API เปิดตัว (ตัวอย่างเช่น ในรถไฟเมนไลน์) คุณจะต้องถ่ายสแนปชอตและสร้างเวอร์ชันใหม่ สำหรับ API ระหว่างระบบกับผู้ให้บริการ การเปลี่ยนแปลงนี้ควรเกิดขึ้นพร้อมกับการแก้ไขแพลตฟอร์มรายปี

โปรดดูรายละเอียดและข้อมูลเพิ่มเติมเกี่ยวกับประเภทของการเปลี่ยนแปลงที่อนุญาตที่อินเทอร์เฟซการกำหนดเวอร์ชัน

หลักเกณฑ์การออกแบบ API

ทั่วไป

1. จัดทำเอกสารสำหรับทุกอย่าง

  • บันทึกวิธีการทั้งหมดเกี่ยวกับความหมาย อาร์กิวเมนต์ การใช้ข้อยกเว้นในตัว ข้อยกเว้นเฉพาะบริการ และค่าที่ส่งกลับ
  • บันทึกอินเทอร์เฟซทั้งหมดสำหรับความหมายของอินเทอร์เฟซ
  • บันทึกความหมายทางอรรถศาสตร์ของ Enum และค่าคงที่
  • บันทึกสิ่งที่อาจไม่ชัดเจนแก่ผู้ติดตั้งใช้งาน
  • โปรดแสดงตัวอย่างในกรณีที่เกี่ยวข้อง

2. โครงยาง

ใช้ปลอกอูฐด้านบนสำหรับประเภทต่างๆ และใช้ท่ออูฐด้านล่างสำหรับวิธีการ ฟิลด์ และอาร์กิวเมนต์ เช่น MyParcelable สำหรับประเภทการแบ่งส่วนและ anArgument สำหรับอาร์กิวเมนต์ สำหรับตัวย่อ ให้พิจารณาตัวย่อเป็นคำ (NFC -> Nfc)

[-Wconst-name] ค่า Enum และค่าคงที่ควรเป็น ENUM_VALUE และ CONSTANT_NAME

อินเทอร์เฟซ

1. การตั้งชื่อ

[-Winterface-name] ชื่ออินเทอร์เฟซควรขึ้นต้นด้วย I เช่น 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. อย่าใช้การเชื่อมทางเดียวด้วยวิธีการ 2 ทาง

[-Wmixed-oneway] อย่าผสมวิธีแบบทางเดียวกับวิธีที่ไม่ใช่แบบทางเดียว เนื่องจากจะทำให้การทำความเข้าใจรูปแบบ Threading ซับซ้อนสำหรับไคลเอ็นต์และเซิร์ฟเวอร์ กล่าวอย่างเจาะจงคือ เมื่ออ่านโค้ดไคลเอ็นต์ของอินเทอร์เฟซหนึ่งๆ คุณต้องค้นหาแต่ละเมธอดว่าเมธอดนั้นจะบล็อกหรือไม่

4. หลีกเลี่ยงการส่งคืนรหัสสถานะ

เมธอดควรหลีกเลี่ยงรหัสสถานะเป็นค่าผลลัพธ์ เนื่องจากเมธอด AIDL ทั้งหมดมีโค้ดส่งคืนสถานะโดยนัย โปรดดู ServiceSpecificException หรือ EX_SERVICE_SPECIFIC ตามกฎแล้ว ค่าเหล่านี้จะกำหนดเป็นค่าคงที่ในอินเทอร์เฟซ AIDL ดูข้อมูลโดยละเอียดได้ในส่วนการจัดการข้อผิดพลาดของแบ็กเอนด์ AIDL

5. อาร์เรย์เป็นพารามิเตอร์เอาต์พุตที่ถือว่าเป็นอันตราย

[-Wout-array] เมธอดที่มีพารามิเตอร์เอาต์พุตของอาร์เรย์ เช่น void foo(out String[] ret) มักจะใช้ไม่ได้ เนื่องจากไคลเอ็นต์ต้องประกาศและจัดสรรขนาดอาร์เรย์เอาต์พุตใน Java ดังนั้นเซิร์ฟเวอร์จึงเลือกขนาดของเอาต์พุตอาร์เรย์ไม่ได้ ลักษณะการทำงานที่ไม่พึงประสงค์นี้เกิดขึ้นเนื่องจากการทำงานของอาร์เรย์ใน Java (ไม่สามารถจัดสรรใหม่ได้) แต่เลือกใช้ API เช่น String[] foo() แทน

6. หลีกเลี่ยงพารามิเตอร์เข้า

[-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. Bitfields ควรใช้ช่องคงที่

Bitfields ควรใช้ช่องคงที่ (เช่น const int FOO = 3; ในอินเทอร์เฟซ)

2. Enum ควรเป็นชุดแบบปิด

Enum ควรเป็นชุดแบบปิด หมายเหตุ: เฉพาะเจ้าของอินเทอร์เฟซเท่านั้นที่เพิ่มองค์ประกอบ enum ได้ หากผู้ขายหรือ OEM จำเป็นต้องขยายฟิลด์เหล่านี้ จำเป็นต้องมีกลไกอื่น คุณควรใช้ฟังก์ชันของผู้ให้บริการอัปสตรีมหากทำได้ อย่างไรก็ตาม ในบางกรณี ค่าของผู้ให้บริการที่กำหนดเองอาจได้รับอนุญาตผ่าน (แต่ผู้ให้บริการควรมีกลไกในการจัดทำเวอร์ชันนี้ไว้ หรืออาจเป็น AIDL เอง พวกเขาไม่ควรขัดแย้งกันเองได้ และค่าเหล่านี้ไม่ควรเปิดเผยกับแอปของบุคคลที่สาม)

3. หลีกเลี่ยงค่า เช่น "NUM_ELEMENTS"

เนื่องจาก Enum เป็นการกำหนดเวอร์ชัน ควรหลีกเลี่ยงค่าที่บ่งบอกจำนวนค่าที่มีอยู่ ใน 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;