คู่มือสไตล์ 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. บันทึกทุกอย่าง

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

2. ปลอก

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

[-Wconst-name] ค่าแจงนับและค่าคงที่ควรเป็น 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] อย่าผสม oneway กับวิธีการที่ไม่ใช่ 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 ไม่จัดการคำอธิบายประกอบ @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 โดยปกติแล้ว พัสดุเหล่านี้เป็นพัสดุเก่าและที่มีอยู่แล้วซึ่งไม่สามารถจัดโครงสร้างได้ง่าย

ค่าคงที่และ Enums

1. Bitfields ควรใช้ฟิลด์คงที่

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

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

Enum ควรเป็นชุดปิด หมายเหตุ: มีเพียงเจ้าของอินเทอร์เฟซเท่านั้นที่สามารถเพิ่มองค์ประกอบ enum ได้ หากผู้จำหน่ายหรือ OEM จำเป็นต้องขยายสาขาเหล่านี้ จำเป็นต้องมีกลไกอื่น เมื่อใดก็ตามที่เป็นไปได้ ควรเลือกใช้ฟังก์ชันการทำงานของผู้ขายแบบอัปสตรีม อย่างไรก็ตาม ในบางกรณี ค่าของผู้จำหน่ายแบบกำหนดเองอาจได้รับอนุญาตผ่าน (แม้ว่า ผู้จำหน่ายควรมีกลไกในเวอร์ชันนี้ บางทีอาจเป็น 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 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;