แนวทางปฏิบัติแนะนำที่ระบุไว้ที่นี่เป็นแนวทางในการพัฒนาอินเทอร์เฟซ AIDL อย่างมีประสิทธิภาพและคำนึงถึงความยืดหยุ่นของอินเทอร์เฟซ โดยเฉพาะ เมื่อใช้ AIDL เพื่อกำหนด API หรือโต้ตอบกับพื้นผิว API
คุณใช้ AIDL เพื่อกำหนด API ได้เมื่อแอปต้องเชื่อมต่อกับแอปอื่นใน กระบวนการเบื้องหลังหรือต้องเชื่อมต่อกับระบบ ดูข้อมูลเพิ่มเติม เกี่ยวกับการพัฒนาอินเทอร์เฟซการเขียนโปรแกรมในแอปด้วย AIDL ได้ที่ ภาษาที่ใช้สื่อสารข้อมูลระหว่างคอมโพเนนต์ของ Android (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. หลีกเลี่ยงอินเทอร์เฟซขนาดใหญ่ที่มี "ออบเจ็กต์" ที่อิงตามรหัส
ควรใช้ Subinterface เมื่อมีการเรียกที่เกี่ยวข้องกับ API ที่เฉพาะเจาะจงจำนวนมาก ซึ่งมีประโยชน์ดังนี้
- ทำให้โค้ดฝั่งไคลเอ็นต์หรือเซิร์ฟเวอร์เข้าใจง่ายขึ้น
- ทำให้อายุการใช้งานของออบเจ็กต์ง่ายขึ้น
- ใช้ประโยชน์จากความจริงที่ว่า Binder ปลอมแปลงไม่ได้
ไม่แนะนำ: อินเทอร์เฟซขนาดใหญ่รายการเดียว ที่มีออบเจ็กต์ตามรหัส
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] อย่าใช้วิธีการแบบทางเดียวร่วมกับวิธีการแบบไม่ทางเดียว เนื่องจากจะทำให้ไคลเอ็นต์และเซิร์ฟเวอร์เข้าใจรูปแบบการทำงานแบบเธรดได้ยาก กล่าวคือ เมื่ออ่านโค้ดฝั่งไคลเอ็นต์ของอินเทอร์เฟซหนึ่งๆ คุณจะต้อง ค้นหาว่าแต่ละเมธอดจะบล็อกหรือไม่
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
out เป็น null (ใน C++ ให้ตั้งค่าเป็น std::nullopt
) แต่ไคลเอ็นต์ Java
จะอ่านค่าเป็น null ไม่ได้
Parcelable ที่มีโครงสร้าง
1. กรณีที่ควรใช้
ใช้ Parcelable ที่มีโครงสร้างในกรณีที่คุณมีข้อมูลหลายประเภทที่จะส่ง
หรือเมื่อคุณมีข้อมูลประเภทเดียว แต่คาดว่าในอนาคตจะต้องขยายข้อมูล เช่น อย่าใช้ String username
ใช้
ExtendableParcelable เช่น รายการต่อไปนี้
parcelable User {
String username;
}
เพื่อให้คุณขยายการใช้งานได้ในอนาคต ดังนี้
parcelable User {
String username;
int id;
}
2. ระบุค่าเริ่มต้นอย่างชัดเจน
[-Wexplicit-default, -Wenum-explicit-default] ระบุค่าเริ่มต้นที่ชัดเจนสำหรับ ฟิลด์
Parcelable ที่ไม่มีโครงสร้าง
1. กรณีที่ควรใช้
Parcelable ที่ไม่มีโครงสร้างพร้อมใช้งานใน Java ด้วย
@JavaOnlyStableParcelable
และในแบ็กเอนด์ NDK ด้วย
@NdkOnlyStableParcelable
โดยปกติแล้วจะเป็น Parcelable เก่าและที่มีอยู่
ซึ่งจัดโครงสร้างไม่ได้
ค่าคงที่และ Enum
1. ฟิลด์บิตควรใช้ฟิลด์ค่าคงที่
ฟิลด์บิตควรใช้ฟิลด์ค่าคงที่ (เช่น 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;