แนวทางปฏิบัติแนะนำที่ระบุไว้ที่นี่ใช้เป็นแนวทางในการพัฒนาอินเทอร์เฟซ 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. หลีกเลี่ยงอินเทอร์เฟซขนาดใหญ่ที่มี "ออบเจ็กต์" ตามรหัส
แนะนำให้ใช้อินเทอร์เฟซย่อยเมื่อมีคำเรียกจำนวนมากที่เกี่ยวข้องกับ 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] อย่าผสมวิธีการแบบช่องทางเดียวเข้ากับวิธีการที่ไม่ใช่แบบช่องทางเดียว เนื่องจากจะทำให้ไคลเอ็นต์และเซิร์ฟเวอร์เข้าใจรูปแบบการแยกชุดข้อความได้ยาก กล่าวโดยละเอียดคือ เมื่ออ่านโค้ดไคลเอ็นต์ของอินเทอร์เฟซหนึ่งๆ คุณต้องค้นหาว่าแต่ละเมธอดจะบล็อกหรือไม่
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. กรณีที่ควรใช้
ใช้ Structured Parcelable ในกรณีที่คุณมีข้อมูลหลายประเภทที่จะส่ง
หรือเมื่อคุณมีประเภทข้อมูลเดียวแต่คาดว่าจะต้องขยายประเภทข้อมูลในอนาคต เช่น อย่าใช้ String username
ใช้ Parcelable ที่ขยายได้ เช่น ต่อไปนี้
parcelable User {
String username;
}
เพื่อให้คุณขยายเวลาได้ในอนาคต ดังนี้
parcelable User {
String username;
int id;
}
2. ระบุค่าเริ่มต้นอย่างชัดเจน
[-Wexplicit-default, -Wenum-explicit-default] ระบุค่าเริ่มต้นที่ชัดเจนสำหรับช่อง
รายการที่แบ่งออกเป็นส่วนๆ โดยไม่ได้เป็นโครงสร้าง
1. กรณีที่ควรใช้
รายการที่แบ่งออกเป็นส่วนๆ ที่ไม่มีโครงสร้างพร้อมใช้งานใน Java ด้วย @JavaOnlyStableParcelable
และในแบ็กเอนด์ NDK ด้วย @NdkOnlyStableParcelable
โดยปกติแล้ว รายการเหล่านี้จะเป็นพาร์เซลที่มีอยู่และเก่า ซึ่งไม่สามารถจัดโครงสร้างได้
ค่าคงที่และ Enum
1. ฟิลด์บิตควรใช้ฟิลด์คงที่
ฟิลด์บิตควรใช้ฟิลด์คงที่ (เช่น const int FOO = 3;
ในอินเตอร์เฟซ)
2. Enums ควรเป็นชุดแบบปิด
Enums ควรเป็นชุดแบบปิด หมายเหตุ: เฉพาะเจ้าของอินเทอร์เฟซเท่านั้นที่จะเพิ่มองค์ประกอบ enum ได้ หากผู้ให้บริการหรือ OEM ต้องการขยายช่องเหล่านี้ จะต้องมีกลไกอื่น คุณควรใช้ฟังก์ชันการทำงานของผู้ให้บริการที่ส่งผ่านข้อมูลขึ้นต้นทุกครั้งที่เป็นไปได้ อย่างไรก็ตาม ในบางกรณี ระบบอาจอนุญาตให้ใช้ค่าที่กำหนดเองของผู้ให้บริการ (แม้ว่าผู้ให้บริการควรมีกลไกในการกำหนดเวอร์ชันของค่านี้ ซึ่งอาจเป็น AIDL เอง ค่าเหล่านี้ไม่ควรขัดแย้งกัน และไม่ควรแสดงในแอปของบุคคลที่สาม)
3. หลีกเลี่ยงค่าอย่างเช่น "NUM_ELEMENTS"
เนื่องจาก Enum มีเวอร์ชัน คุณจึงควรหลีกเลี่ยงค่าที่ระบุจํานวนค่าที่มีอยู่ ใน 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 อาจทำให้เกิดการรั่วไหลของ File Descriptor เว้นแต่จะได้รับการจัดการอย่างระมัดระวัง โดยพื้นฐานแล้ว หากคุณยอมรับ 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;