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 ที่ประกอบเป็นอินเทอร์เฟซ พาธสำหรับประเภท AIDLFoo
ที่กำหนดในแพ็คเกจ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 11versions
จะถูกตรึงไว้ภายใต้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 ที่เสถียร
ระบุการขึ้นต่อกันทั้งหมดของอินเทอร์เฟซของคุณ สำหรับทุกแพ็คเกจที่อินเทอร์เฟซขึ้นอยู่กับ ให้พิจารณาว่าแพ็คเกจนั้นถูกกำหนดไว้ใน AIDL ที่เสถียรหรือไม่ หากไม่ได้กำหนดไว้ จะต้องแปลงแพ็กเกจ
แปลงพัสดุทั้งหมดในอินเทอร์เฟซของคุณให้เป็นพัสดุที่เสถียร (ไฟล์อินเทอร์เฟซเองยังคงไม่เปลี่ยนแปลง) ทำได้โดยแสดงโครงสร้างโดยตรงในไฟล์ AIDL คลาสการจัดการจะต้องถูกเขียนใหม่เพื่อใช้ประเภทใหม่เหล่านี้ ซึ่งสามารถทำได้ก่อนที่คุณจะสร้างแพ็คเกจ
aidl_interface
(ด้านล่าง)สร้างแพ็คเกจ
aidl_interface
(ตามที่อธิบายไว้ข้างต้น) ที่มีชื่อของโมดูลของคุณ การขึ้นต่อกัน และข้อมูลอื่น ๆ ที่คุณต้องการ เพื่อให้มีความเสถียร (ไม่ใช่แค่มีโครงสร้าง) จำเป็นต้องมีเวอร์ชันด้วย สำหรับข้อมูลเพิ่มเติม โปรดดูที่ อินเตอร์เฟสการกำหนดเวอร์ชัน