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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ทั่วไป

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

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

2. ขอบหน้าต่าง

ใช้การเขียนแบบ Camel Case ตัวพิมพ์ใหญ่สำหรับประเภท และการเขียนแบบ Camel Case ตัวพิมพ์เล็กสำหรับเมธอด ฟิลด์ และอาร์กิวเมนต์ เช่น 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. หลีกเลี่ยงอินเทอร์เฟซขนาดใหญ่ที่มี "ออบเจ็กต์" ที่อิงตามรหัส

แนะนำให้ใช้อินเทอร์เฟซย่อยเมื่อมีการเรียกใช้จำนวนมากที่เกี่ยวข้องกับ 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. หลีกเลี่ยงพารามิเตอร์ inout

[-Winout-parameter] This can confuse clients because even in parameters look like out parameters.

7. หลีกเลี่ยงพารามิเตอร์ที่ไม่ใช่อาร์เรย์ @nullable แบบ out และ inout

[-Wout-nullable] เนื่องจากแบ็กเอนด์ Java ไม่จัดการคำอธิบายประกอบ @nullable ขณะที่แบ็กเอนด์อื่นๆ จัดการ out/inout @nullable T อาจทำให้เกิดลักษณะการทำงานที่ไม่สอดคล้องกันในแบ็กเอนด์ต่างๆ เช่น แบ็กเอนด์ที่ไม่ใช่ Java สามารถตั้งค่าพารามิเตอร์ @nullable เป็น null ได้ (ใน C++ ให้ตั้งค่าเป็น std::nullopt) แต่ไคลเอ็นต์ Java จะอ่านค่าเป็น null ไม่ได้

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

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

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

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

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

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

1. Bitfield ควรใช้ช่องค่าคงที่

บิตฟิลด์ควรใช้ฟิลด์ค่าคงที่ (เช่น 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) เพื่อหลีกเลี่ยงการบล็อก

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

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