แนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในที่นี้ทำหน้าที่เป็นแนวทางในการพัฒนาอินเทอร์เฟซ 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;