Bạn có thể sử dụng định dạng tệp APEX để đóng gói và cài đặt các mô-đun hệ điều hành Android cấp thấp. Điều này cho phép tạo và cài đặt độc lập các thành phần như dịch vụ và thư viện gốc, các phương thức triển khai HAL, chương trình cơ sở, tệp cấu hình, v.v.
Hệ thống bản dựng sẽ tự động cài đặt APEX của nhà cung cấp trong phân vùng /vendor
và apexd
sẽ kích hoạt APEX này trong thời gian chạy giống như APEX trong các phân vùng khác.
Trường hợp sử dụng
Phân chia hình ảnh của nhà cung cấp thành các phần
APEX tạo điều kiện thuận lợi cho việc nhóm và mô-đun hoá tự nhiên các hoạt động triển khai tính năng trên hình ảnh của nhà cung cấp.
Khi hình ảnh nhà cung cấp được tạo thành từ sự kết hợp của các APEX nhà cung cấp được tạo độc lập, nhà sản xuất thiết bị có thể dễ dàng chọn các cách triển khai cụ thể của nhà cung cấp mà họ muốn trên thiết bị của mình. Nhà sản xuất thậm chí có thể tạo một APEX nhà cung cấp mới nếu không có APEX nào được cung cấp phù hợp với nhu cầu của họ hoặc họ có một phần cứng tuỳ chỉnh hoàn toàn mới.
Ví dụ: OEM có thể chọn tạo thiết bị của họ bằng APEX triển khai Wi-Fi AOSP, APEX triển khai Bluetooth SoC và APEX triển khai điện thoại OEM tuỳ chỉnh.
Nếu không có APEX của nhà cung cấp, việc triển khai với quá nhiều mối phụ thuộc giữa các thành phần của nhà cung cấp sẽ đòi hỏi sự phối hợp và theo dõi cẩn thận. Bằng cách bao bọc tất cả các thành phần (bao gồm cả tệp cấu hình và các thư viện bổ sung) trong APEX có giao diện được xác định rõ ràng tại bất kỳ thời điểm nào giao tiếp giữa các tính năng, các thành phần khác nhau sẽ trở nên có thể thay thế cho nhau.
Vòng lặp của nhà phát triển
APEX của nhà cung cấp giúp nhà phát triển lặp lại nhanh hơn trong khi phát triển các mô-đun của nhà cung cấp bằng cách kết hợp toàn bộ quá trình triển khai tính năng (chẳng hạn như HAL Wi-Fi) bên trong APEX của nhà cung cấp. Sau đó, nhà phát triển có thể tạo và đẩy riêng APEX của nhà cung cấp để kiểm thử các thay đổi, thay vì tạo lại toàn bộ hình ảnh của nhà cung cấp.
Việc này giúp đơn giản hoá và đẩy nhanh chu kỳ lặp lại của nhà phát triển đối với những nhà phát triển chủ yếu làm việc trong một khu vực tính năng và chỉ muốn lặp lại trên khu vực tính năng đó.
Việc nhóm tự nhiên một vùng tính năng vào một APEX cũng giúp đơn giản hoá quy trình tạo, đẩy và kiểm thử các thay đổi cho vùng tính năng đó. Ví dụ: việc cài đặt lại APEX sẽ tự động cập nhật mọi thư viện hoặc tệp cấu hình đi kèm mà APEX bao gồm.
Việc nhóm một vùng tính năng vào APEX cũng giúp đơn giản hoá việc gỡ lỗi hoặc quay lại khi phát hiện thấy hành vi không tốt của thiết bị. Ví dụ: nếu dịch vụ điện thoại hoạt động kém trong một bản dựng mới, thì nhà phát triển có thể thử cài đặt APEX triển khai dịch vụ điện thoại cũ hơn trên một thiết bị (mà không cần flash một bản dựng đầy đủ) và xem liệu hành vi tốt có được khôi phục hay không.
Ví dụ về quy trình công việc:
# Build the entire device and flash. OR, obtain an already-flashed device.
source build/envsetup.sh && lunch oem_device-userdebug
m
fastboot flashall -w
# Test the device.
... testing ...
# Check previous behavior using a vendor APEX from one week ago, downloaded from
# your continuous integration build.
... download command ...
adb install <path to downloaded APEX>
adb reboot
... testing ...
# Edit and rebuild just the APEX to change and test behavior.
... edit APEX source contents ...
m <apex module name>
adb install out/<path to built APEX>
adb reboot
... testing ...
Ví dụ
Thông tin cơ bản
Hãy xem trang Định dạng tệp APEX chính để biết thông tin chung về APEX, bao gồm cả các yêu cầu đối với thiết bị, thông tin chi tiết về định dạng tệp và các bước cài đặt.
Trong Android.bp
, việc đặt thuộc tính vendor: true
sẽ biến mô-đun APEX thành APEX của nhà cung cấp.
apex {
..
vendor: true,
..
}
Tệp nhị phân và thư viện dùng chung
APEX bao gồm các phần phụ thuộc bắc cầu bên trong tải trọng APEX, trừ phi chúng có các giao diện ổn định.
Các giao diện gốc ổn định cho các phần phụ thuộc APEX của nhà cung cấp bao gồm cc_library
với stubs
và các thư viện LLNDK. Các phần phụ thuộc này sẽ không được đưa vào quy trình đóng gói và các phần phụ thuộc sẽ được ghi lại trong tệp kê khai APEX. Tệp kê khai được linkerconfig
xử lý để các phần phụ thuộc gốc bên ngoài có sẵn trong thời gian chạy.
Trong đoạn mã sau, APEX chứa cả tệp nhị phân (my_service
) và các phần phụ thuộc không ổn định (tệp *.so
).
apex {
..
vendor: true,
binaries: ["my_service"],
..
}
Trong đoạn mã sau, APEX chứa thư viện dùng chung my_standalone_lib
và mọi phần phụ thuộc không ổn định của thư viện đó (như mô tả ở trên).
apex {
..
vendor: true,
native_shared_libs: ["my_standalone_lib"],
..
}
Giảm kích thước APEX
APEX có thể lớn hơn vì nó đi kèm với các phần phụ thuộc không ổn định. Bạn nên sử dụng liên kết tĩnh. Các thư viện phổ biến như libc++.so
và libbase.so
có thể được liên kết tĩnh với các tệp nhị phân HAL. Tạo một phần phụ thuộc để cung cấp giao diện ổn định cũng là một lựa chọn khác. Phần phụ thuộc sẽ không được đi kèm trong APEX.
Triển khai HAL
Để xác định một phương thức triển khai HAL, hãy cung cấp các tệp nhị phân và thư viện tương ứng bên trong một APEX của nhà cung cấp tương tự như các ví dụ sau:
Để đóng gói hoàn toàn việc triển khai HAL, APEX cũng phải chỉ định mọi đoạn VINTF và tập lệnh khởi động có liên quan.
Mảnh VINTF
Các mảnh VINTF có thể được phân phát từ một APEX của nhà cung cấp khi các mảnh nằm trong etc/vintf
của APEX.
Sử dụng thuộc tính prebuilts
để nhúng các mảnh VINTF vào APEX.
apex {
..
vendor: true,
prebuilts: ["fragment.xml"],
..
}
prebuilt_etc {
name: "fragment.xml",
src: "fragment.xml",
sub_dir: "vintf",
}
API truy vấn
Khi các mảnh VINTF được thêm vào APEX, hãy sử dụng API libbinder_ndk
để nhận các mối liên kết của giao diện HAL và tên APEX.
AServiceManager_isUpdatableViaApex("com.android.foo.IFoo/default")
:true
nếu phiên bản HAL được xác định trong APEX.AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...)
: lấy tên APEX xác định thực thể HAL.AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...)
: sử dụng thuộc tính này để mở HAL truyền qua.
Tập lệnh khởi động
APEX có thể bao gồm các tập lệnh khởi động theo hai cách: (A) một tệp văn bản được tạo sẵn trong tải trọng APEX hoặc (B) một tập lệnh khởi động thông thường trong /vendor/etc
. Bạn có thể đặt cả hai cho cùng một APEX.
Tập lệnh khởi động trong APEX:
prebuilt_etc {
name: "myinit.rc",
src: "myinit.rc"
}
apex {
..
vendor: true,
prebuilts: ["myinit.rc"],
..
}
Các tập lệnh khởi động trong APEX của nhà cung cấp có thể có các định nghĩa service
và chỉ thị on <property or event>
.
Đảm bảo rằng định nghĩa service
chỉ đến một tệp nhị phân trong cùng một APEX.
Ví dụ: APEX com.android.foo
có thể xác định một dịch vụ có tên là foo-service
.
on foo-service /apex/com.android.foo/bin/foo
...
Hãy cẩn thận khi sử dụng chỉ thị on
. Vì các tập lệnh khởi động trong APEX được phân tích cú pháp và thực thi sau khi APEX được kích hoạt, nên bạn không thể sử dụng một số sự kiện hoặc thuộc tính. Sử dụng apex.all.ready=true
để kích hoạt các thao tác càng sớm càng tốt.
Bootstrap APEX có thể sử dụng on init
, nhưng không thể sử dụng on early-init
.
Chương trình cơ sở
Ví dụ:
Nhúng chương trình cơ sở vào APEX của nhà cung cấp bằng loại mô-đun prebuilt_firmware
, như sau.
prebuilt_firmware {
name: "my.bin",
src: "path_to_prebuilt_firmware",
vendor: true,
}
apex {
..
vendor: true,
prebuilts: ["my.bin"], // installed inside APEX as /etc/firmware/my.bin
..
}
Các mô-đun prebuilt_firmware
được cài đặt trong thư mục <apex name>/etc/firmware
của APEX. ueventd
quét các thư mục /apex/*/etc/firmware
để tìm các mô-đun chương trình cơ sở.
file_contexts
của APEX phải gắn nhãn đúng cách cho mọi mục tải trọng của chương trình cơ sở để đảm bảo rằng ueventd
có thể truy cập vào các tệp này trong thời gian chạy; thông thường, nhãn vendor_file
là đủ. Ví dụ:
(/.*)? u:object_r:vendor_file:s0
Mô-đun kernel
Nhúng các mô-đun kernel vào APEX của nhà cung cấp dưới dạng các mô-đun dựng sẵn, như sau.
prebuilt_etc {
name: "my.ko",
src: "my.ko",
vendor: true,
sub_dir: "modules"
}
apex {
..
vendor: true,
prebuilts: ["my.ko"], // installed inside APEX as /etc/modules/my.ko
..
}
file_contexts
của APEX phải gắn nhãn đúng cách cho mọi mục tải trọng mô-đun hạt nhân. Ví dụ:
/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0
Bạn phải cài đặt rõ ràng các mô-đun kernel. Tập lệnh init ví dụ sau đây trong phân vùng nhà cung cấp cho thấy quá trình cài đặt thông qua insmod
:
my_init.rc
:
on early-boot
insmod /apex/myapex/etc/modules/my.ko
..
Lớp phủ tài nguyên trong thời gian chạy
Ví dụ:
Nhúng lớp phủ tài nguyên thời gian chạy vào APEX của nhà cung cấp bằng cách sử dụng thuộc tính rros
.
runtime_resource_overlay {
name: "my_rro",
soc_specific: true,
}
apex {
..
vendor: true,
rros: ["my_rro"], // installed inside APEX as /overlay/my_rro.apk
..
}
Các tệp cấu hình khác
APEX của nhà cung cấp hỗ trợ nhiều tệp cấu hình khác thường có trên phân vùng của nhà cung cấp dưới dạng các bản dựng sẵn bên trong APEX của nhà cung cấp và nhiều tệp khác đang được thêm vào.
Ví dụ:
- Tệp XML khai báo tính năng
- Các XML tính năng cảm biến dưới dạng các bản dựng sẵn trong APEX HAL nhà cung cấp cảm biến
- Tệp cấu hình đầu vào
- Cấu hình màn hình cảm ứng dưới dạng các bản dựng sẵn trong APEX chỉ có cấu hình của nhà cung cấp
Khởi động APEX của nhà cung cấp
Một số dịch vụ HAL như keymint
phải có sẵn trước khi APEX được kích hoạt. Những HAL này thường đặt early_hal
trong định nghĩa dịch vụ của chúng trong tập lệnh init. Một ví dụ khác là lớp animation
thường được bắt đầu sớm hơn sự kiện post-fs-data
. Khi dịch vụ HAL ban đầu như vậy được đóng gói trong APEX của nhà cung cấp, hãy tạo "vendorBootstrap": true
apex trong Tệp kê khai APEX để dịch vụ này có thể được kích hoạt sớm hơn. Xin lưu ý rằng bạn chỉ có thể kích hoạt APEX khởi động từ vị trí được tạo sẵn như /vendor/apex
, chứ không phải từ /data/apex
.
Thuộc tính hệ thống
Sau đây là các thuộc tính hệ thống mà khung đọc để hỗ trợ APEX của nhà cung cấp:
input_device.config_file.apex=<apex name>
– khi được đặt, các tệp cấu hình đầu vào (*.idc
,*.kl
và*.kcm
) sẽ được tìm kiếm trong thư mục/etc/usr
của APEX.ro.vulkan.apex=<apex name>
– khi được đặt, trình điều khiển Vulkan sẽ được tải từ APEX. Vì trình điều khiển Vulkan được các HAL ban đầu sử dụng, hãy tạo Bootstrap APEX APEX và định cấu hình không gian tên trình liên kết đó để có thể nhìn thấy.
Đặt các thuộc tính hệ thống trong tập lệnh khởi động bằng lệnh setprop
.
Các tính năng bổ sung
Lựa chọn APEX khi khởi động
Ví dụ:
Bạn có thể tuỳ ý kích hoạt APEX của nhà cung cấp trong quá trình khởi động.
Nếu bạn chỉ định tên tệp bằng cách sử dụng thuộc tính hệ thống ro.vendor.apex.<apex name>
, thì chỉ APEX khớp với tên tệp mới được kích hoạt cho <apex name>
cụ thể.
APEX có <apex name>
sẽ bị bỏ qua (không được kích hoạt) nếu bạn đặt thuộc tính hệ thống này thành none
. Bạn có thể dùng tính năng này để cài đặt nhiều bản sao của một APEX có cùng tên. Nếu có nhiều phiên bản của cùng một APEX, thì các phiên bản đó phải dùng chung một khoá.
Ví dụ về các trường hợp sử dụng:
- Cài đặt 3 phiên bản APEX của nhà cung cấp HAL Wi-Fi: Các nhóm QA có thể chạy kiểm thử thủ công hoặc tự động bằng một phiên bản, sau đó khởi động lại vào một phiên bản khác và chạy lại các kiểm thử, rồi so sánh kết quả cuối cùng.
- Cài đặt 2 phiên bản APEX của nhà cung cấp HAL camera, hiện tại và thử nghiệm: Người dùng nội bộ có thể sử dụng phiên bản thử nghiệm mà không cần tải xuống và cài đặt thêm tệp, nhờ đó họ có thể dễ dàng chuyển đổi trở lại.
Trong quá trình khởi động, apexd
sẽ tìm kiếm các sysprop theo một định dạng cụ thể để kích hoạt phiên bản APEX phù hợp.
Định dạng hợp lệ cho khoá thuộc tính là:
- Bootconfig
- Dùng để đặt giá trị mặc định trong
BoardConfig.mk
. androidboot.vendor.apex.<apex name>
- Dùng để đặt giá trị mặc định trong
- Sysprop liên tục
- Dùng để thay đổi giá trị mặc định, được đặt trên một thiết bị đã khởi động.
- Ghi đè giá trị bootconfig (nếu có).
persist.vendor.apex.<apex name>
Giá trị của thuộc tính này phải là tên tệp của APEX cần được kích hoạt hoặc none
để tắt APEX.
// Default version.
apex {
name: "com.oem.camera.hal.my_apex_default",
vendor: true,
..
}
// Non-default version.
apex {
name: "com.oem.camera.hal.my_apex_experimental",
vendor: true,
..
}
Bạn cũng nên định cấu hình phiên bản mặc định bằng bootconfig trong BoardConfig.mk
:
# Example for APEX "com.oem.camera.hal" with the default above:
BOARD_BOOTCONFIG += \
androidboot.vendor.apex.com.oem.camera.hal=com.oem.camera.hal.my_apex_default
Sau khi thiết bị khởi động, hãy thay đổi phiên bản đã kích hoạt bằng cách đặt sysprop liên tục:
$ adb root;
$ adb shell setprop \
persist.vendor.apex.com.oem.camera.hal \
com.oem.camera.hal.my_apex_experimental;
$ adb reboot;
Nếu thiết bị hỗ trợ việc cập nhật bootconfig sau khi flash (chẳng hạn như thông qua các lệnh fastboot
oem
), thì việc thay đổi thuộc tính bootconfig cho APEX được cài đặt nhiều lần cũng sẽ thay đổi phiên bản được kích hoạt khi khởi động.
Đối với các thiết bị tham chiếu ảo dựa trên Cuttlefish, bạn có thể sử dụng lệnh --extra_bootconfig_args
để đặt trực tiếp thuộc tính bootconfig trong khi khởi chạy. Ví dụ:
launch_cvd --noresume \
--extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";