หลักเกณฑ์เกี่ยวกับ AIDL API

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

คุณใช้ AIDL เพื่อกำหนด API ได้เมื่อแอปต้องเชื่อมต่อกับแอปอื่นใน กระบวนการเบื้องหลังหรือต้องเชื่อมต่อกับระบบ

ใช้ AIDL ที่เสถียรกับ @VintfStability สำหรับอินเทอร์เฟซ HAL และอนุญาตให้ไคลเอ็นต์ และเซิร์ฟเวอร์ได้รับการอัปเดตแยกกัน ซึ่งต้องมีความเข้ากันได้แบบย้อนหลังและ Structured Data

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

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

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

เมื่ออินเทอร์เฟซถูกตรึง (บันทึกไว้ในไดเรกทอรี aidl_api ที่มีการควบคุมเวอร์ชัน) จะต้องไม่แก้ไข คุณแก้ไขได้เฉพาะไดเรกทอรี current คุณสามารถเพิ่มเมธอดที่ส่วนท้ายของอินเทอร์เฟซ ฟิลด์ที่ส่วนท้ายของ Parcelable ตัวแจงนับใน Enum และสมาชิกใน Union ได้อย่างปลอดภัย

ไคลเอ็นต์ที่เรียกใช้เมธอดใหม่ในเซิร์ฟเวอร์รุ่นเก่าจะได้รับข้อผิดพลาด UNKNOWN_TRANSACTION ซึ่งไคลเอ็นต์ควรจัดการอย่างเหมาะสม

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

ทรัพยากร Dependency ของบิลด์

โมดูล Android ไม่สามารถใช้ไลบรารีที่สร้างขึ้นจาก aidl_interface หลายเวอร์ชันที่แตกต่างกัน ไลบรารีเวอร์ชันต่างๆ จะกำหนดประเภทเดียวกันในเนมสเปซเดียวกัน aidl ระบบบิลด์ ของ Android จะระบุปัญหานี้และแสดงข้อผิดพลาดพร้อมกราฟทรัพยากร Dependency แต่ละรายการ ที่สิ้นสุดในไลบรารีเวอร์ชันที่ไม่ตรงกัน

ซึ่งอาจทำให้การอัปเดตอินเทอร์เฟซทั่วไปเวอร์ชันหนึ่งทำได้ยากเมื่อ โมดูลมีทรัพยากร Dependency จำนวนมากที่มีทรัพยากร Dependency ของตัวเอง

นักพัฒนาซอฟต์แวร์สามารถใช้ aidl_interface_defaults เพื่อประกาศการขึ้นต่อกันของอินเทอร์เฟซที่แชร์กับอินเทอร์เฟซอื่นๆ เพื่อไม่ให้ต้องอัปเดตทั้งหมดแยกกัน

เราขอแนะนำให้ใช้โมดูล *_defaults (เช่น rust_defaults, cc_defaults, java_defaults) เพื่อจัดระเบียบ การอ้างอิงในไลบรารีที่สร้างขึ้น โดยทั่วไปแล้ว จะมีค่าเริ่มต้นสำหรับอินเทอร์เฟซเวอร์ชัน latest รวมถึงค่าเริ่มต้นสำหรับเวอร์ชันก่อนหน้า หากยังมีการใช้งานอยู่

นักพัฒนาแอปสามารถใช้ aidl_interface_defaults เพื่อประกาศการอ้างอิงของอินเทอร์เฟซที่แชร์กับอินเทอร์เฟซอื่นๆ เพื่อไม่ให้ต้องอัปเดตทั้งหมดแยกกัน

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

ทั่วไป

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

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

2. เคส

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

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

3. หลีกเลี่ยงการกำหนดให้ต้องมีความรู้ระดับโลก

API ไม่ควรถือว่านักพัฒนาซอฟต์แวร์มีความรู้ในระดับโลกเกี่ยวกับฐานของโค้ดทั้งหมดหรือความเชี่ยวชาญเฉพาะด้าน เมื่อต้องจัดการกับตัวระบุเฉพาะโดเมน (เช่น ชื่อ รหัส หรือแฮนเดิลของอุปกรณ์) ให้ทำดังนี้

  • ระบุอย่างชัดเจนและบันทึกว่าตัวระบุเหล่านี้มาจากที่ใดและมีรูปแบบอย่างไร หาก ทั้ง 2 ฝั่งของอินเทอร์เฟซจำเป็นต้องทราบ
  • หรือจะใช้ตัวระบุเฉพาะอินเทอร์เฟซ (เช่น ออบเจ็กต์ Binder หรือโทเค็นที่กำหนดเอง) และให้ฝั่งใดฝั่งหนึ่งจัดการการแมปกับค่าพื้นฐานก็ได้ ซึ่งจะช่วยลดการชนกันและหลีกเลี่ยงการกำหนดให้ผู้ใช้ต้องทำความเข้าใจ รายละเอียดการติดตั้งใช้งานนอกพื้นที่ของตน

4. ข้อมูลทั้งหมดมีโครงสร้างและเข้ากันได้กับเวอร์ชันก่อนหน้า

ข้อมูลที่ไม่มีโครงสร้าง เช่น string, byte[] และหน่วยความจำที่ใช้ร่วมกัน ต้องมีรูปแบบที่เสถียรสำหรับเนื้อหา หรือต้องไม่โปร่งใสต่ออินเทอร์เฟซด้านใดด้านหนึ่ง

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

ในทำนองเดียวกัน อย่าแปลงออบเจ็กต์เป็นbyte[]หรือหน่วยความจำที่ใช้ร่วมกัน เว้นแต่ออบเจ็กต์นั้นจะ เสถียรและเข้ากันได้แบบย้อนหลัง ในบางกรณี คุณสามารถใช้@FixedSize คำอธิบายประกอบสำหรับการแชร์ Parcelable และ Union ในหน่วยความจำที่ใช้ร่วมกันและคิวข้อความด่วน ได้

อินเทอร์เฟซ

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 หากต้องการการหน่วงเวลาที่กำหนดเองหรือข้อมูลข้อผิดพลาดที่ไม่ซ้ำกันควบคู่ไปกับ ข้อผิดพลาด นั่นเป็นเวลาเดียวที่ออบเจ็กต์การตอบกลับที่กำหนดเองควรแสดงถึง ข้อผิดพลาด ดูข้อมูลเพิ่มเติมได้ที่ การจัดการข้อผิดพลาด

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 ไม่ได้

8. ใช้คำขอและการตอบกลับที่ไม่ซ้ำกัน

จัดกลุ่มพารามิเตอร์ที่จำเป็นทั้งหมดไว้ในอินพุตเดียว parcelable สร้าง Parcelable สำหรับคำขอและการตอบกลับโดยเฉพาะสำหรับเมธอดอินเทอร์เฟซทุกรายการ แทนที่จะส่งค่าดั้งเดิม (เช่น ใช้ ComputeResponse compute(in ComputeRequest request) แทนการส่งตัวแปร แยกกัน) ซึ่งจะช่วยให้เพิ่มอาร์กิวเมนต์ใหม่ได้ในภายหลังโดยไม่ต้องเปลี่ยน ลายเซ็นฟังก์ชัน เราขอแนะนำให้ใช้รูปแบบนี้อย่างยิ่งในกรณีที่คาดว่าจะมีการเพิ่มพารามิเตอร์มากขึ้นในอนาคต หรือหากเมธอดมีพารามิเตอร์มากกว่า 4 รายการอยู่แล้ว

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

หากไม่ได้สร้างเมธอดโดยใช้รูปแบบนี้ คุณสามารถเปลี่ยนไปใช้รูปแบบนี้ได้ โดยสร้างเมธอดใหม่ที่มีพัสดุที่ส่งผ่านได้สำหรับคำขอและการตอบกลับ และ เลิกใช้งานเมธอดเก่า เช่น

void foo(int a, int b, int c); // original version, but deprecated in favor of the next version
void fooV2(in MyArg arg); // new version having int a, b, c, and d.

Parcelable ที่มีโครงสร้าง

1. กรณีที่ควรใช้

ใช้ Parcelable ที่มีโครงสร้างในกรณีที่คุณมีข้อมูลหลายประเภทที่จะส่ง

หรือเมื่อคุณมีข้อมูลประเภทเดียว แต่คาดว่าคุณจะต้องขยายข้อมูลนั้นในอนาคต เช่น อย่าใช้ String username ใช้ Parcelable ที่ขยายได้ เช่น รายการต่อไปนี้

parcelable User {
    String username;
}

เพื่อให้คุณขยายการใช้งานได้ในอนาคต ดังนี้

parcelable User {
    String username;
    int id;
}

2. ระบุค่าเริ่มต้นอย่างชัดเจน

[-Wexplicit-default, -Wenum-explicit-default] ระบุค่าเริ่มต้นที่ชัดเจนสำหรับ ฟิลด์ เมื่อเพิ่มฟิลด์ใหม่ลงใน Parcelable ไคลเอ็นต์และเซิร์ฟเวอร์เก่าจะทิ้งฟิลด์เหล่านั้น แต่ค่าเริ่มต้นจะกรอกข้อมูลโดยอัตโนมัติสำหรับไคลเอ็นต์และเซิร์ฟเวอร์ใหม่

3. ใช้ ParcelableHolder สำหรับส่วนขยายของผู้ให้บริการ

หากคุณกำหนด parcelable ของ AOSP ที่ผู้ติดตั้งใช้งานอุปกรณ์ต้องขยาย ให้ฝังอินสแตนซ์ของ ParcelableHolder ในออบเจ็กต์ ซึ่งทำหน้าที่เป็น จุดขยายโดยไม่ทำให้เกิดข้อขัดแย้งในการผสาน ซึ่งคล้ายกับส่วนขยายอินเทอร์เฟซที่แนบ แต่ช่วยให้ผู้ใช้สามารถรวม parcelable ที่เป็นกรรมสิทธิ์ของตนเองไว้กับ parcelable ที่มีอยู่ โดยไม่ต้องสร้างอินเทอร์เฟซและประเภทของตนเอง

4. โครงสร้างข้อมูล

  • ใช้อาร์เรย์หรือ List ของ Parcelable เพื่อแสดงแผนที่ เนื่องจาก AIDL ไม่รองรับประเภท Map โดยเนทีฟซึ่งแปลได้อย่างปลอดภัยในแบ็กเอนด์เนทีฟทั้งหมด (เช่น FeatureToScoreEntry[])
  • ใช้อาร์เรย์ของออบเจ็กต์ parcelable สำหรับฟิลด์ที่ซ้ำกันแทนที่จะใช้อาร์เรย์ของ ค่าดั้งเดิม เพื่อป้องกันไม่ให้ต้องใช้อาร์เรย์แบบขนานในอนาคต
  • ใช้parcelableออบเจ็กต์ที่มีการพิมพ์อย่างเข้มงวดแทนสตริงที่ซีเรียลหรือ JSON ผ่าน IPC
  • ใช้ Enum แทนบูลีนสำหรับสถานะเพื่อรองรับการขยายในอนาคต สำหรับ บิตมาสก์ ให้ใช้ประเภท const int แทนประเภท enum เพื่อหลีกเลี่ยง การแคสต์ที่ซับซ้อนในแบ็กเอนด์บางรายการ

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;

การทำงานพร้อมกันและการดำเนินการแบบไม่พร้อมกัน

จัดการการดำเนินการที่ใช้เวลานานด้วยอินเทอร์เฟซแบบอะซิงโครนัส (oneway) เพื่อหลีกเลี่ยงการบล็อก

หากบริการไม่เชื่อถือไคลเอ็นต์ การเรียกกลับใดๆ ที่ได้รับจากไคลเอ็นต์ควรเป็นอินเทอร์เฟซ oneway ซึ่งจะป้องกันไม่ให้ไคลเอ็นต์บล็อกบริการอย่างไม่มีกำหนด

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