AIDL ที่เสถียร

Android 10 เพิ่มการรองรับ Android Interface Definition Language (AIDL) ที่เสถียร ซึ่งเป็นวิธีใหม่ในการติดตาม application program interface (API)/application binary interface (ABI) ที่มาจากอินเทอร์เฟซ AIDL AIDL ที่เสถียรมีความแตกต่างที่สำคัญจาก AIDL ดังต่อไปนี้:

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

AIDL ที่มีโครงสร้างและมีเสถียรภาพ

โครงสร้าง AIDL หมายถึงประเภทที่กำหนดไว้ใน AIDL เท่านั้น ตัวอย่างเช่น การสำแดงแบบแบ่งส่วนได้ (แบบแบ่งส่วนแบบกำหนดเอง) ไม่ใช่ AIDL ที่มีโครงสร้าง พัสดุที่มีช่องที่กำหนดใน AIDL เรียกว่า พัสดุแบบมีโครงสร้าง

AIDL ที่เสถียร ต้องใช้ AIDL ที่มีโครงสร้างเพื่อให้ระบบบิลด์และคอมไพเลอร์สามารถเข้าใจได้ว่าการเปลี่ยนแปลงที่ทำกับการแบ่งส่วนนั้นเข้ากันได้แบบย้อนหลังหรือไม่ อย่างไรก็ตาม ไม่ใช่ว่าอินเทอร์เฟซที่มีโครงสร้างทั้งหมดจะเสถียร เพื่อให้มีเสถียรภาพ อินเทอร์เฟซต้องใช้เฉพาะประเภทที่มีโครงสร้าง และต้องใช้คุณลักษณะการกำหนดเวอร์ชันต่อไปนี้ด้วย ในทางกลับกัน อินเทอร์เฟซจะไม่เสถียรหากใช้ระบบบิลด์หลักเพื่อสร้างหรือหากตั้ง unstable:true

การกำหนดอินเทอร์เฟซ AIDL

คำจำกัดความของ aidl_interface มีลักษณะดังนี้:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name : ชื่อของโมดูลอินเทอร์เฟซ AIDL ที่ระบุอินเทอร์เฟซ AIDL โดยไม่ซ้ำกัน
  • srcs : รายการไฟล์ต้นฉบับ AIDL ที่ประกอบเป็นอินเทอร์เฟซ พาธสำหรับประเภท AIDL Foo ที่กำหนดในแพ็คเกจ com.acme ควรอยู่ที่ <base_path>/com/acme/Foo.aidl โดยที่ <base_path> อาจเป็นไดเร็กทอรีใดๆ ที่เกี่ยวข้องกับไดเร็กทอรีที่ Android.bp อยู่ ในตัวอย่างข้างต้น <base_path> คือ srcs/aidl
  • local_include_dir : พาธจากจุดเริ่มต้นของชื่อแพ็กเกจ มันสอดคล้องกับ <base_path> ที่อธิบายไว้ข้างต้น
  • imports : รายการโมดูล aidl_interface ที่ใช้ หากอินเทอร์เฟซ AIDL อันใดอันหนึ่งของคุณใช้อินเทอร์เฟซหรือแบ่งส่วนได้จาก aidl_interface อื่น ให้ใส่ชื่อของมันที่นี่ ซึ่งอาจเป็นชื่อเดี่ยวๆ เพื่ออ้างถึงเวอร์ชันล่าสุด หรือชื่อที่มีส่วนต่อท้ายเวอร์ชัน (เช่น -V1 ) เพื่ออ้างถึงเวอร์ชันเฉพาะ รองรับการระบุเวอร์ชันตั้งแต่ Android 12
  • versions : เวอร์ชันก่อนหน้าของอินเทอร์เฟซที่ตรึงไว้ภายใต้ api_dir เริ่มตั้งแต่ Android 11 versions จะถูกตรึงไว้ภายใต้ aidl_api/ name หากไม่มีอินเทอร์เฟซเวอร์ชันที่ตรึงไว้ ก็ไม่ควรระบุ และจะไม่มีการตรวจสอบความเข้ากันได้ ช่องนี้ถูกแทนที่ด้วย versions_with_info สำหรับเวอร์ชัน 13 และสูงกว่า
  • versions_with_info : รายการสิ่งอันดับ ซึ่งแต่ละรายการประกอบด้วยชื่อของเวอร์ชันที่แช่แข็ง และรายการที่มีการนำเข้าเวอร์ชันของโมดูล Aidl_interface อื่นๆ ที่ Aidl_interface เวอร์ชันนี้นำเข้า คำจำกัดความของเวอร์ชัน V ของอินเทอร์เฟซ AIDL IFACE อยู่ที่ aidl_api/ IFACE / V ช่องนี้เปิดตัวใน Android 13 และไม่ควรแก้ไขใน Android.bp โดยตรง ช่องนี้ถูกเพิ่มหรืออัปเดตโดยการเรียกใช้ *-update-api หรือ *-freeze-api นอกจากนี้ ช่อง versions จะถูกย้ายไปยัง versions_with_info โดยอัตโนมัติเมื่อผู้ใช้เรียกใช้ *-update-api หรือ *-freeze-api
  • stability : แฟล็กเผื่อเลือกสำหรับความเสถียรของอินเทอร์เฟซนี้ ปัจจุบันรองรับเฉพาะ "vintf" เท่านั้น หากไม่ได้ตั้งค่า สิ่งนี้จะสอดคล้องกับอินเทอร์เฟซที่มีเสถียรภาพภายในบริบทการคอมไพล์นี้ (ดังนั้นอินเทอร์เฟซที่โหลดที่นี่สามารถใช้ได้กับสิ่งที่คอมไพล์ร่วมกันเท่านั้น เช่น บน system.img) หากตั้งค่าเป็น "vintf" สิ่งนี้จะสอดคล้องกับคำมั่นสัญญาด้านความเสถียร: อินเทอร์เฟซจะต้องคงความเสถียรตราบเท่าที่มีการใช้งาน
  • gen_trace : แฟล็กเผื่อเลือกเพื่อเปิดหรือปิดการติดตาม ตั้งแต่ Android 14 เป็นต้นไป ค่าเริ่มต้นจะเป็น true สำหรับแบ็กเอนด์ cpp และ java
  • host_supported : แฟล็กเผื่อเลือกที่เมื่อตั้งค่าเป็น true จะทำให้ไลบรารีที่สร้างขึ้นพร้อมใช้งานสำหรับสภาพแวดล้อมโฮสต์
  • unstable : ธงทางเลือกที่ใช้เพื่อทำเครื่องหมายว่าอินเทอร์เฟซนี้ไม่จำเป็นต้องมีเสถียรภาพ เมื่อตั้งค่าเป็น true ระบบบิลด์จะไม่สร้างดัมพ์ API สำหรับอินเทอร์เฟซและไม่จำเป็นต้องอัปเดต
  • frozen : การตั้งค่าสถานะเผื่อเลือกที่เมื่อตั้งค่าเป็น true หมายความว่าอินเทอร์เฟซไม่มีการเปลี่ยนแปลงตั้งแต่เวอร์ชันก่อนหน้าของอินเทอร์เฟซ ซึ่งช่วยให้สามารถตรวจสอบเวลาในการสร้างได้มากขึ้น เมื่อตั้งค่าเป็น false หมายความว่าอินเทอร์เฟซอยู่ระหว่างการพัฒนาและมีการเปลี่ยนแปลงใหม่ ดังนั้นการรัน foo-freeze-api จะสร้างเวอร์ชันใหม่และเปลี่ยนค่าเป็น true โดยอัตโนมัติ เปิดตัวใน Android 14
  • backend.<type>.enabled : แฟล็กเหล่านี้จะสลับแต่ละแบ็กเอนด์ที่คอมไพเลอร์ AIDL สร้างโค้ดให้ ปัจจุบันรองรับแบ็กเอนด์สี่รายการ: Java, C++, NDK และ Rust แบ็กเอนด์ Java, C++ และ NDK ถูกเปิดใช้งานตามค่าเริ่มต้น หากไม่จำเป็นต้องใช้แบ็กเอนด์ทั้งสามนี้ จะต้องปิดใช้แบ็กเอนด์นั้นอย่างชัดเจน Rust ถูกปิดใช้งานตามค่าเริ่มต้น
  • backend.<type>.apex_available : รายการชื่อ APEX ที่ไลบรารี stub ที่สร้างขึ้นพร้อมใช้งาน
  • backend.[cpp|java].gen_log : แฟล็กเผื่อเลือกที่ควบคุมว่าจะสร้างโค้ดเพิ่มเติมสำหรับการรวบรวมข้อมูลเกี่ยวกับธุรกรรมหรือไม่
  • backend.[cpp|java].vndk.enabled : แฟล็กเผื่อเลือกเพื่อทำให้อินเทอร์เฟซนี้เป็นส่วนหนึ่งของ VNDK ค่าเริ่มต้นเป็น false
  • backend.[cpp|ndk].additional_shared_libraries : เปิดตัวใน Android 14 การตั้งค่าสถานะนี้จะเพิ่มการพึ่งพาให้กับไลบรารีดั้งเดิม แฟล็กนี้มีประโยชน์กับ ndk_header และ cpp_header
  • backend.java.sdk_version : แฟล็กทางเลือกสำหรับระบุเวอร์ชันของ SDK ที่ไลบรารี Java stub สร้างขึ้น ค่าเริ่มต้นคือ "system_current" ไม่ควรตั้งค่าเมื่อ backend.java.platform_apis เป็นจริง
  • backend.java.platform_apis : แฟล็กเผื่อเลือกที่ควรตั้งค่าเป็น true เมื่อไลบรารีที่สร้างขึ้นจำเป็นต้องสร้างโดยเทียบกับแพลตฟอร์ม API แทนที่จะเป็น SDK

สำหรับแต่ละเวอร์ชันและแบ็กเอนด์ที่เปิดใช้งาน ไลบรารีต้นขั้วจะถูกสร้างขึ้น สำหรับวิธีอ้างอิงเวอร์ชันเฉพาะของไลบรารี Stub สำหรับแบ็กเอนด์เฉพาะ โปรดดู กฎการตั้งชื่อโมดูล

การเขียนไฟล์ AIDL

อินเทอร์เฟซใน AIDL ที่เสถียรนั้นคล้ายคลึงกับอินเทอร์เฟซแบบดั้งเดิม โดยมีข้อยกเว้นว่าไม่อนุญาตให้ใช้การแยกส่วนแบบไม่มีโครงสร้าง (เพราะสิ่งเหล่านี้ไม่เสถียร! ดูที่ AIDL ที่มีโครงสร้างเทียบกับที่เสถียร ) ความแตกต่างหลักใน AIDL ที่เสถียรคือวิธีกำหนดการแบ่งแยกพัสดุ ก่อนหน้านี้ พัสดุถูก ประกาศส่งต่อ ; ใน AIDL ที่เสถียร (และมีโครงสร้าง) ฟิลด์พัสดุและตัวแปรจะถูกกำหนดอย่างชัดเจน

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

ปัจจุบันรองรับค่าเริ่มต้น (แต่ไม่จำเป็น) สำหรับ boolean , char , float , double , byte , int , long และ String ใน Android 12 ยังรองรับค่าเริ่มต้นสำหรับการแจงนับที่ผู้ใช้กำหนดด้วย เมื่อไม่ได้ระบุค่าเริ่มต้น ระบบจะใช้ค่าที่เหมือน 0 หรือค่าว่าง การแจงนับที่ไม่มีค่าเริ่มต้นจะเริ่มต้นเป็น 0 แม้ว่าจะไม่มีตัวแจงนับเป็นศูนย์ก็ตาม

การใช้ไลบรารีต้นขั้ว

หลังจากเพิ่มไลบรารี stub เป็นการพึ่งพาให้กับโมดูลของคุณแล้ว คุณสามารถรวมไลบรารีเหล่านั้นลงในไฟล์ของคุณได้ นี่คือตัวอย่างของไลบรารีต้นขั้วในระบบบิลด์ ( Android.mk สามารถใช้กับคำจำกัดความของโมดูลรุ่นเก่าได้):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

ตัวอย่างในภาษา C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

ตัวอย่างในภาษาจาวา:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

ตัวอย่างในสนิม:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

อินเทอร์เฟซการกำหนดเวอร์ชัน

การประกาศโมดูลด้วยชื่อ foo ยังสร้างเป้าหมายในระบบบิลด์ที่คุณสามารถใช้เพื่อจัดการ API ของโมดูล เมื่อสร้างแล้ว foo-freeze-api จะเพิ่มคำจำกัดความ API ใหม่ภายใต้ api_dir หรือ aidl_api/ name ขึ้นอยู่กับเวอร์ชันของ Android และเพิ่มไฟล์ .hash ซึ่งทั้งคู่แสดงถึงอินเทอร์เฟซเวอร์ชันที่ถูกตรึงใหม่ foo-freeze-api ยังอัปเดตคุณสมบัติ versions_with_info เพื่อสะท้อนถึงเวอร์ชันเพิ่มเติมและ imports สำหรับเวอร์ชันนั้น โดยพื้นฐานแล้ว imports ใน versions_with_info จะถูกคัดลอกจากช่อง imports แต่มีการระบุเวอร์ชันเสถียรล่าสุดใน imports ใน versions_with_info สำหรับการนำเข้าที่ไม่มีเวอร์ชันที่ชัดเจน เมื่อระบุคุณสมบัติ versions_with_info แล้ว ระบบบิลด์จะรันการตรวจสอบความเข้ากันได้ระหว่างเวอร์ชันที่ตรึงไว้และระหว่าง Top of Tree (ToT) และเวอร์ชันที่ตรึงล่าสุด

นอกจากนี้ คุณต้องจัดการคำจำกัดความ API ของเวอร์ชัน ToT เมื่อใดก็ตามที่อัปเดต API ให้รัน foo-update-api เพื่ออัปเดต aidl_api/ name /current ซึ่งมีคำจำกัดความ API ของเวอร์ชัน ToT

เพื่อรักษาความเสถียรของอินเทอร์เฟซ เจ้าของสามารถเพิ่มสิ่งใหม่:

  • วิธีการต่อท้ายอินเทอร์เฟซ (หรือวิธีการที่มีอนุกรมใหม่ที่กำหนดไว้อย่างชัดเจน)
  • องค์ประกอบต่อท้ายพัสดุ (ต้องเพิ่มค่าเริ่มต้นสำหรับแต่ละองค์ประกอบ)
  • ค่าคงที่
  • ใน Android 11 ผู้แจกแจง
  • ใน Android 12 ช่องต่อท้ายสหภาพ

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

เพื่อทดสอบว่าอินเทอร์เฟซทั้งหมดถูกระงับเพื่อรีลีส คุณสามารถสร้างด้วยชุดตัวแปรสภาพแวดล้อมต่อไปนี้:

  • AIDL_FROZEN_REL=true m ... - บิลด์ต้องการให้อินเทอร์เฟซ AIDL ที่เสถียรทั้งหมดถูกแช่แข็ง ซึ่งไม่ได้ระบุฟิลด์ owner: :
  • AIDL_FROZEN_OWNERS="aosp test" - บิลด์ต้องการให้อินเทอร์เฟซ AIDL ที่เสถียรทั้งหมดถูกแช่แข็งโดย owner: ฟิลด์ที่ระบุเป็น "aosp" หรือ "test"

ความมั่นคงของการนำเข้า

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

ในรหัสแพลตฟอร์ม Android android.hardware.graphics.common เป็นตัวอย่างที่ใหญ่ที่สุดของการอัปเกรดเวอร์ชันประเภทนี้

การใช้อินเทอร์เฟซที่มีเวอร์ชัน

วิธีการเชื่อมต่อ

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

  • แบ็กเอนด์ cpp ได้รับ ::android::UNKNOWN_TRANSACTION
  • แบ็กเอนด์ ndk ได้รับ STATUS_UNKNOWN_TRANSACTION
  • แบ็กเอนด์ java ได้รับ android.os.RemoteException พร้อมข้อความแจ้งว่า API ไม่ได้ถูกนำมาใช้

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

พัสดุ

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

ลูกค้าไม่ควรคาดหวังให้เซิร์ฟเวอร์ใช้ฟิลด์ใหม่ เว้นแต่จะรู้ว่าเซิร์ฟเวอร์กำลังใช้เวอร์ชันที่มีฟิลด์กำหนดไว้ (ดู เวอร์ชันการสืบค้น )

แจงนับและค่าคงที่

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

สหภาพแรงงาน

การพยายามส่งสหภาพกับฟิลด์ใหม่จะล้มเหลวหากผู้รับเก่าและไม่รู้เกี่ยวกับฟิลด์นั้น การนำไปปฏิบัติจะไม่มีวันเห็นการรวมตัวกับเขตข้อมูลใหม่ ความล้มเหลวจะถูกละเว้นหากเป็นธุรกรรมแบบทางเดียว มิฉะนั้นข้อผิดพลาดจะเป็น BAD_VALUE (สำหรับแบ็กเอนด์ C++ หรือ NDK) หรือ IllegalArgumentException (สำหรับแบ็กเอนด์ Java) ได้รับข้อผิดพลาดหากไคลเอ็นต์ส่งชุดสหภาพไปยังฟิลด์ใหม่ไปยังเซิร์ฟเวอร์เก่า หรือเมื่อเป็นไคลเอ็นต์เก่าที่ได้รับชุดสหภาพจากเซิร์ฟเวอร์ใหม่

การพัฒนาตามธง

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

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

แฟล็กการสร้าง AIDL

แฟล็กที่ควบคุมลักษณะการทำงานนี้คือ RELEASE_AIDL_USE_UNFROZEN ที่กำหนดไว้ใน build/release/build_flags.bzl true หมายความว่ามีการใช้อินเทอร์เฟซเวอร์ชันที่ไม่ถูกตรึง ณ รันไทม์ และ false หมายความว่าไลบรารีของเวอร์ชันที่ไม่ถูกตรึงทั้งหมดจะทำงานเหมือนเวอร์ชันที่ตรึงครั้งล่าสุด คุณสามารถแทนที่แฟล็กเป็น true สำหรับการพัฒนาในเครื่องได้ แต่ต้องเปลี่ยนกลับเป็น false ก่อนที่จะเผยแพร่ โดยทั่วไปการพัฒนาจะเสร็จสิ้นด้วยการกำหนดค่าที่มีการตั้งค่าสถานะเป็น true

เมทริกซ์ความเข้ากันได้และรายการ

ออบเจ็กต์อินเทอร์เฟซของผู้ขาย (ออบเจ็กต์ VINTF) กำหนดเวอร์ชันที่คาดหวัง และเวอร์ชันใดที่ให้ไว้ที่ด้านใดด้านหนึ่งของอินเทอร์เฟซของผู้ขาย

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

เมทริกซ์

อินเทอร์เฟซที่เป็นของพันธมิตรจะถูกเพิ่มลงในเมทริกซ์ความเข้ากันได้เฉพาะอุปกรณ์หรือเฉพาะผลิตภัณฑ์ที่อุปกรณ์กำหนดเป้าหมายในระหว่างการพัฒนา ดังนั้นเมื่อมีการเพิ่มเวอร์ชันใหม่ของอินเทอร์เฟซที่ไม่ถูกตรึงลงในเมทริกซ์ความเข้ากันได้ เวอร์ชันที่ตรึงก่อนหน้านี้จะต้องคงไว้สำหรับ RELEASE_AIDL_USE_UNFROZEN=false คุณสามารถจัดการสิ่งนี้ได้โดยใช้ไฟล์เมทริกซ์ความเข้ากันได้ที่แตกต่างกันสำหรับการกำหนดค่า RELEASE_AIDL_USE_UNFROZEN ที่แตกต่างกัน หรืออนุญาตให้ทั้งสองเวอร์ชันอยู่ในไฟล์เมทริกซ์ความเข้ากันได้ไฟล์เดียวที่ใช้ในการกำหนดค่าทั้งหมด

ตัวอย่างเช่น เมื่อเพิ่มเวอร์ชันที่ไม่มีการตรึง 4 ให้ใช้ <version>3-4</version>

เมื่อเวอร์ชัน 4 ถูกแช่แข็ง คุณสามารถลบเวอร์ชัน 3 ออกจากเมทริกซ์ความเข้ากันได้ได้ เนื่องจากเวอร์ชันที่แช่แข็ง 4 จะถูกใช้เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false

ประจักษ์

ใน Android 15 (ทดลอง AOSP) มีการเปลี่ยนแปลงใน libvintf เพื่อแก้ไขไฟล์ Manifest ณ เวลาบิลด์ตามค่าของ RELEASE_AIDL_USE_UNFROZEN

รายการและส่วนรายการประกาศเวอร์ชันของอินเทอร์เฟซที่บริการนำไปใช้ เมื่อใช้อินเทอร์เฟซเวอร์ชันล่าสุดที่ไม่มีการแช่แข็ง รายการจะต้องได้รับการอัปเดตเพื่อแสดงเวอร์ชันใหม่นี้ เมื่อ RELEASE_AIDL_USE_UNFROZEN=false รายการรายการจะถูกปรับโดย libvintf เพื่อสะท้อนถึงการเปลี่ยนแปลงในไลบรารี AIDL ที่สร้างขึ้น เวอร์ชันนี้ได้รับการแก้ไขจากเวอร์ชันที่ไม่ถูกแช่แข็ง N เป็นเวอร์ชันที่ตรึงไว้ล่าสุด N - 1 ดังนั้น ผู้ใช้จึงไม่จำเป็นต้องจัดการรายการหรือส่วนรายการหลายรายการสำหรับแต่ละบริการของตน

การเปลี่ยนแปลงไคลเอนต์ HAL

รหัสไคลเอ็นต์ HAL จะต้องเข้ากันได้แบบย้อนหลังกับเวอร์ชันแช่แข็งที่รองรับก่อนหน้านี้ เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false บริการจะดูเหมือนเวอร์ชันที่ตรึงไว้ล่าสุดหรือเก่ากว่าเสมอ (เช่น การเรียกใช้เมธอด unfrozen ใหม่จะส่งคืน UNKNOWN_TRANSACTION หรือช่อง parcelable ใหม่จะมีค่าเริ่มต้น) ไคลเอนต์เฟรมเวิร์ก Android จำเป็นต้องเข้ากันได้แบบย้อนหลังกับเวอร์ชันก่อนหน้าเพิ่มเติม แต่นี่เป็นรายละเอียดใหม่สำหรับลูกค้าของผู้จำหน่ายและไคลเอนต์ของอินเทอร์เฟซที่พันธมิตรเป็นเจ้าของ

การเปลี่ยนแปลงการใช้งาน HAL

ความแตกต่างที่ใหญ่ที่สุดในการพัฒนา HAL กับการพัฒนาแบบอิงแฟล็กคือข้อกำหนดสำหรับการใช้งาน HAL เพื่อให้สามารถเข้ากันได้แบบย้อนหลังกับเวอร์ชันที่ตรึงไว้ล่าสุด เพื่อที่จะทำงานได้เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false การพิจารณาความเข้ากันได้แบบย้อนหลังในการใช้งานและโค้ดอุปกรณ์ถือเป็นแบบฝึกหัดใหม่ โปรดดู ที่การใช้อินเทอร์เฟซที่มีเวอร์ชัน

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

ตัวอย่าง: อินเทอร์เฟซมีเวอร์ชันแช่แข็งสามเวอร์ชัน อินเทอร์เฟซได้รับการอัปเดตด้วยวิธีใหม่ ทั้งไคลเอนต์และบริการได้รับการอัปเดตเพื่อใช้ไลบรารีเวอร์ชัน 4 ใหม่ เนื่องจากไลบรารี V4 ขึ้นอยู่กับอินเทอร์เฟซเวอร์ชันที่ไม่มีการตรึง จึงทำงานเหมือนกับเวอร์ชันที่ตรึงล่าสุด เวอร์ชัน 3 เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false และป้องกันการใช้วิธีการใหม่

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

เมื่อเรียกวิธีการโทรกลับ คุณต้องจัดการกรณีและปัญหาอย่างสง่างามเมื่อส่งคืน UNKNOWN_TRANSACTION ไคลเอนต์อาจใช้การโทรกลับสองเวอร์ชันที่แตกต่างกันตามการกำหนดค่าการวางจำหน่าย ดังนั้นคุณจึงไม่สามารถสันนิษฐานได้ว่าไคลเอนต์จะส่งเวอร์ชันใหม่ล่าสุด และวิธีการใหม่อาจส่งคืนสิ่งนี้ ซึ่งคล้ายกับวิธีที่ไคลเอ็นต์ AIDL มีความเสถียรในการรักษาความเข้ากันได้แบบย้อนหลังกับเซิร์ฟเวอร์ที่อธิบายไว้ใน การใช้อินเทอร์เฟซที่มีเวอร์ชัน

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

ฟิลด์ใหม่ในประเภทที่มีอยู่ ( parcelable , enum , union ) อาจไม่มีอยู่หรือมีค่าเริ่มต้นเมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false และค่าของฟิลด์ใหม่ที่บริการพยายามส่งจะถูกละทิ้งระหว่างที่ออกจากกระบวนการ

ประเภทใหม่ที่เพิ่มในเวอร์ชันที่ไม่มีการตรึงนี้ไม่สามารถส่งหรือรับผ่านอินเทอร์เฟซได้

การใช้งานไม่เคยได้รับการร้องขอวิธีการใหม่จากไคลเอนต์ใด ๆ เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false

โปรดใช้ความระมัดระวังในการใช้ตัวแจงนับใหม่เฉพาะกับเวอร์ชันที่นำมาใช้เท่านั้น ไม่ใช่เวอร์ชันก่อนหน้า

โดยปกติ คุณจะใช้ foo->getInterfaceVersion() เพื่อดูว่าอินเทอร์เฟซระยะไกลใช้เวอร์ชันใด อย่างไรก็ตาม ด้วยการสนับสนุนการกำหนดเวอร์ชันตามแฟล็ก คุณกำลังใช้งานสองเวอร์ชันที่แตกต่างกัน ดังนั้นคุณอาจต้องการรับเวอร์ชันของอินเทอร์เฟซปัจจุบัน คุณสามารถทำได้โดยรับเวอร์ชันอินเทอร์เฟซของออบเจ็กต์ปัจจุบัน เช่น this->getInterfaceVersion() หรือวิธีอื่นสำหรับ my_ver โปรดดู ที่การสืบค้นเวอร์ชันอินเทอร์เฟซของออบเจ็กต์ระยะไกล สำหรับข้อมูลเพิ่มเติม

อินเทอร์เฟซที่เสถียรของ VINTF ใหม่

เมื่อมีการเพิ่มแพ็คเกจอินเทอร์เฟซ AIDL ใหม่ จะไม่มีเวอร์ชันที่ตรึงไว้ล่าสุด ดังนั้นจึงไม่มีพฤติกรรมที่จะถอยกลับไปเมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false อย่าใช้อินเทอร์เฟซเหล่านี้ เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false ผู้จัดการฝ่ายบริการจะไม่อนุญาตให้บริการลงทะเบียนอินเทอร์เฟซ และไคลเอ็นต์จะไม่พบอินเทอร์เฟซดังกล่าว

คุณสามารถเพิ่มบริการตามเงื่อนไขตามค่าของแฟล็ก RELEASE_AIDL_USE_UNFROZEN ใน makefile ของอุปกรณ์:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

หากบริการเป็นส่วนหนึ่งของกระบวนการที่ใหญ่กว่า ดังนั้นคุณไม่สามารถเพิ่มลงในอุปกรณ์ตามเงื่อนไขได้ คุณสามารถตรวจสอบเพื่อดูว่าบริการได้รับการประกาศด้วย IServiceManager::isDeclared() หากมีการประกาศและลงทะเบียนไม่สำเร็จ ให้ยกเลิกกระบวนการนี้ หากไม่ได้รับการประกาศ ก็คาดว่าจะไม่สามารถลงทะเบียนได้

ปลาหมึกเป็นเครื่องมือในการพัฒนา

ทุกๆ ปีหลังจากที่ VINTF ถูกแช่แข็ง เราจะปรับ target-level เมทริกซ์ความเข้ากันได้ของเฟรมเวิร์ก (FCM) และ PRODUCT_SHIPPING_API_LEVEL ของ Cuttlefish เพื่อให้สะท้อนถึงอุปกรณ์ที่เปิดตัวพร้อมกับการเปิดตัวในปีหน้า เราปรับ target-level และ PRODUCT_SHIPPING_API_LEVEL เพื่อให้แน่ใจว่ามีอุปกรณ์เปิดตัวบางตัวที่ได้รับการทดสอบและตรงตามข้อกำหนดใหม่สำหรับการเปิดตัวในปีหน้า

เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น true Cuttlefish จะถูกนำมาใช้เพื่อการพัฒนา Android ในอนาคต โดยกำหนดเป้าหมายระดับ FCM ของ Android ในปีหน้าและ PRODUCT_SHIPPING_API_LEVEL โดยกำหนดให้ต้องเป็นไปตามข้อกำหนดซอฟต์แวร์ผู้จำหน่าย (VSR) ของรุ่นถัดไป

เมื่อ RELEASE_AIDL_USE_UNFROZEN เป็น false ปลาหมึกจะมี target-level ก่อนหน้าและ PRODUCT_SHIPPING_API_LEVEL เพื่อสะท้อนถึงอุปกรณ์สำหรับปล่อย ใน Android 14 และต่ำกว่า การสร้างความแตกต่างนี้จะสำเร็จได้ด้วยสาขา Git ต่างๆ ที่ไม่รับการเปลี่ยนแปลงเป็น target-level FCM ระดับ API การจัดส่ง หรือโค้ดอื่นใดที่กำหนดเป้าหมายไปที่รุ่นถัดไป

กฎการตั้งชื่อโมดูล

ใน Android 11 สำหรับแต่ละเวอร์ชันและแบ็กเอนด์ที่เปิดใช้งานร่วมกัน โมดูลไลบรารี stub จะถูกสร้างขึ้นโดยอัตโนมัติ หากต้องการอ้างถึงโมดูลไลบรารี stub เฉพาะสำหรับการลิงก์ อย่าใช้ชื่อของโมดูล aidl_interface แต่เป็นชื่อของโมดูลไลบรารี Stub ซึ่งก็คือ ifacename - version - backend โดยที่

  • ifacename : ชื่อของโมดูล aidl_interface
  • version ใดรุ่นหนึ่งเป็นของ
    • V version-number สำหรับเวอร์ชันแช่แข็ง
    • V latest-frozen-version-number + 1 สำหรับเวอร์ชันปลายต้นไม้ (ที่ยังไม่ถูกแช่แข็ง)
  • backend เป็นอย่างใดอย่างหนึ่งของ
    • java สำหรับแบ็กเอนด์ Java
    • cpp สำหรับแบ็กเอนด์ C ++
    • ndk หรือ ndk_platform สำหรับแบ็กเอนด์ NDK แบบแรกมีไว้สำหรับแอป และแบบหลังมีไว้สำหรับการใช้งานแพลตฟอร์ม
    • rust สำหรับแบ็กเอนด์ของสนิม

สมมติว่ามีโมดูลชื่อ foo และเวอร์ชันล่าสุดคือ 2 และรองรับทั้ง NDK และ C++ ในกรณีนี้ AIDL จะสร้างโมดูลเหล่านี้:

  • ขึ้นอยู่กับเวอร์ชัน 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • ขึ้นอยู่กับเวอร์ชัน 2 (เวอร์ชันเสถียรล่าสุด)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • ขึ้นอยู่กับเวอร์ชัน ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

เมื่อเทียบกับ Android 11

  • foo- backend ซึ่งอ้างถึงเวอร์ชันเสถียรล่าสุดจะกลายเป็น foo- V2 - backend
  • foo-unstable- backend ซึ่งอ้างถึงเวอร์ชัน ToT จะกลายเป็น foo- V3 - backend

ชื่อไฟล์เอาต์พุตจะเหมือนกับชื่อโมดูลเสมอ

  • อ้างอิงจากเวอร์ชัน 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • ขึ้นอยู่กับเวอร์ชัน 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • อิงตามเวอร์ชัน ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

โปรดทราบว่าคอมไพเลอร์ AIDL จะไม่สร้างโมดูลเวอร์ชัน unstable หรือโมดูลที่ไม่ใช่เวอร์ชันสำหรับอินเทอร์เฟซ AIDL ที่เสถียร ใน Android 12 ชื่อโมดูลที่สร้างจากอินเทอร์เฟซ AIDL ที่เสถียรจะรวมเวอร์ชันไว้ด้วยเสมอ

วิธีการอินเทอร์เฟซเมตาใหม่

Android 10 เพิ่มวิธีอินเทอร์เฟซเมตาหลายวิธีสำหรับ AIDL ที่เสถียร

กำลังสอบถามเวอร์ชันอินเทอร์เฟซของวัตถุระยะไกล

ไคลเอนต์สามารถสอบถามเวอร์ชันและแฮชของอินเทอร์เฟซที่วัตถุระยะไกลใช้งาน และเปรียบเทียบค่าที่ส่งคืนกับค่าของอินเทอร์เฟซที่ไคลเอ็นต์ใช้อยู่

ตัวอย่างกับแบ็กเอนด์ cpp :

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

ตัวอย่างกับแบ็กเอนด์ ndk (และ ndk_platform ):

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

ตัวอย่างกับแบ็กเอนด์ java :

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

สำหรับภาษา Java ฝั่งรีโมตต้องใช้ getInterfaceVersion() และ getInterfaceHash() ดังต่อไปนี้ ( ใช้ super แทน IFoo เพื่อหลีกเลี่ยงข้อผิดพลาดในการคัดลอก/วาง อาจจำเป็นต้องใช้คำอธิบายประกอบ @SuppressWarnings("static") เพื่อปิดใช้งานคำเตือน ขึ้นอยู่กับ การกำหนดค่า javac ):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

นี่เป็นเพราะคลาสที่สร้างขึ้น ( IFoo , IFoo.Stub ฯลฯ ) ถูกใช้ร่วมกันระหว่างไคลเอนต์และเซิร์ฟเวอร์ (ตัวอย่างเช่น คลาสสามารถอยู่ใน boot classpath) เมื่อมีการแชร์คลาส เซิร์ฟเวอร์จะเชื่อมโยงกับคลาสเวอร์ชันใหม่ล่าสุด แม้ว่ามันอาจจะถูกสร้างขึ้นด้วยอินเทอร์เฟซเวอร์ชันเก่าก็ตาม หากใช้อินเทอร์เฟซเมตานี้ในคลาสที่แชร์ ก็จะส่งคืนเวอร์ชันใหม่ล่าสุดเสมอ อย่างไรก็ตาม ด้วยการใช้วิธีการข้างต้น หมายเลขเวอร์ชันของอินเทอร์เฟซจะถูกฝังอยู่ในโค้ดของเซิร์ฟเวอร์ (เนื่องจาก IFoo.VERSION เป็น static final int ที่แทรกอยู่ในบรรทัดเมื่อมีการอ้างอิง) ดังนั้นวิธีการจึงสามารถส่งคืนเวอร์ชันที่แน่นอนที่เซิร์ฟเวอร์ถูกสร้างขึ้น กับ.

การจัดการกับอินเทอร์เฟซรุ่นเก่า

อาจเป็นไปได้ว่าไคลเอนต์ได้รับการอัปเดตด้วยอินเทอร์เฟซ AIDL เวอร์ชันใหม่กว่า แต่เซิร์ฟเวอร์กำลังใช้อินเทอร์เฟซ AIDL เก่า ในกรณีเช่นนี้ การเรียกใช้เมธอดบนอินเทอร์เฟซเก่าจะส่งคืน UNKNOWN_TRANSACTION

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

ตัวอย่างใน C ++ ใน Android 13 และใหม่กว่า:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

ตัวอย่างในภาษาจาวา:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process


foo.anAddedMethod(...);

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

การแปลง AIDL ที่มีอยู่เป็น AIDL ที่มีโครงสร้าง/เสถียร

หากคุณมีอินเทอร์เฟซ AIDL และรหัสที่ใช้อยู่ ใช้ขั้นตอนต่อไปนี้เพื่อแปลงอินเทอร์เฟซเป็นอินเทอร์เฟซ AIDL ที่เสถียร

  1. ระบุการขึ้นต่อกันทั้งหมดของอินเทอร์เฟซของคุณ สำหรับทุกแพ็คเกจที่อินเทอร์เฟซขึ้นอยู่กับ ให้พิจารณาว่าแพ็คเกจนั้นถูกกำหนดไว้ใน AIDL ที่เสถียรหรือไม่ หากไม่ได้กำหนดไว้ จะต้องแปลงแพ็กเกจ

  2. แปลงพัสดุทั้งหมดในอินเทอร์เฟซของคุณให้เป็นพัสดุที่เสถียร (ไฟล์อินเทอร์เฟซเองยังคงไม่เปลี่ยนแปลง) ทำได้โดยแสดงโครงสร้างโดยตรงในไฟล์ AIDL คลาสการจัดการจะต้องถูกเขียนใหม่เพื่อใช้ประเภทใหม่เหล่านี้ ซึ่งสามารถทำได้ก่อนที่คุณจะสร้างแพ็คเกจ aidl_interface (ด้านล่าง)

  3. สร้างแพ็คเกจ aidl_interface (ตามที่อธิบายไว้ข้างต้น) ที่มีชื่อของโมดูลของคุณ การขึ้นต่อกัน และข้อมูลอื่น ๆ ที่คุณต้องการ เพื่อให้มีความเสถียร (ไม่ใช่แค่มีโครงสร้าง) จำเป็นต้องมีเวอร์ชันด้วย สำหรับข้อมูลเพิ่มเติม โปรดดูที่ อินเตอร์เฟสการกำหนดเวอร์ชัน