공급업체 APEX

APEX 파일 형식을 사용하여 하위 수준의 Android OS 모듈을 패키징하고 설치할 수 있습니다. APEX 파일 형식을 사용하면 네이티브 서비스 및 라이브러리, HAL 구현, 펌웨어, 구성 파일과 같은 구성요소를 독립적으로 빌드하고 설치할 수 있습니다.

공급업체 APEX는 빌드 시스템에 의해 /vendor 파티션에 자동으로 설치되며, 다른 파티션에 있는 APEX와 동일하게 apexd에 의해 런타임에 활성화됩니다.

사용 사례

공급업체 이미지의 모듈화

APEX는 기능 구현을 공급업체 이미지에 자연스럽게 번들화하고 모듈화합니다.

공급업체 이미지가 독립적으로 빌드된 공급업체 APEX의 조합으로 빌드되면 기기 제조업체는 기기에 맞는 특정 공급업체 구현을 쉽게 고르고 선택할 수 있습니다. 제조업체는 공급된 APEX 중 필요한 APEX가 없거나 새로운 맞춤 하드웨어가 있으면 새 공급업체 APEX를 만들 수 있습니다.

예를 들어, OEM은 AOSP Wi-Fi 구현 APEX, SoC 블루투스 구현 APEX, 맞춤 OEM 전화 통신 구현 APEX로 기기를 구성하도록 선택할 수 있습니다.

공급업체 APEX가 없으면 공급업체 구성요소 간에 많은 종속 항목을 사용하여 구현해야 하므로 주의 깊게 조정하고 추적해야 합니다. 교차 기능 통신의 모든 지점에서 명확하게 정의된 인터페이스로 APEX의 모든 구성요소(구성 파일 및 추가 라이브러리 포함)를 래핑하여 다양한 구성요소를 서로 교체하며 사용할 수 있게 되었습니다.

개발자의 반복 작업

공급업체 APEX를 사용하면 전체 기능 구현(예: Wi-Fi HAL)을 번들로 묶어 공급업체 APEX 내에서 공급업체 모듈을 개발하는 동안 개발자가 더 빠르게 반복 작업을 할 수 있습니다. 그런 다음 개발자는 전체 공급업체 이미지를 다시 빌드하지 않고 공급업체 APEX를 빌드하고 개별적으로 푸시하여 변경사항을 테스트할 수 있습니다.

이렇게 하면 한 가지 기능 영역에서 주로 작업하고 해당 기능 영역에서만 반복하려고 하는 개발자의 개발자 반복 주기를 단순화하고 속도를 높일 수 있습니다.

기능 영역을 APEX에 번들로 묶으면 빌드, 푸시, 기능 영역의 변경사항 테스트 절차도 간소화할 수 있습니다. 예를 들어, APEX를 재설치하면 APEX에 포함된 모든 번들 라이브러리 또는 구성 파일이 자동으로 업데이트됩니다.

또한 기능 영역을 APEX에 번들로 묶으면 잘못된 기기 동작이 관찰될 때 간단하게 디버깅하거나 되돌릴 수 있습니다. 예를 들어, 전화 통신이 새 빌드에서 올바르게 작동하지 않으면 개발자는 (전체 빌드를 플래시할 필요 없이) 기기에 기존 전화 통신 구현 APEX를 설치하여 올바르게 동작하는지 확인해 볼 수 있습니다.

워크플로 예

# 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 ...

기본 사항

일반 APEX 정보(기기 요구사항, 파일 형식 세부정보, 설치 단계 등)는 기본 APEX 파일 형식 페이지를 참고하세요.

Android.bp에서 vendor: true 속성을 설정하여 APEX 모듈을 공급업체 APEX로 만듭니다.

apex {
  ..
  vendor: true,
  ..
}

바이너리 및 공유 라이브러리

APEX는 안정적인 인터페이스가 없는 한 APEX 페이로드 내에 전이 종속 항목을 포함합니다.

공급업체 APEX 종속 항목의 안정적인 네이티브 인터페이스에는 stubs 및 LLNDK 라이브러리가 있는 cc_library가 포함됩니다. 이러한 종속 항목은 패키징에서 제외되며 종속 항목은 APEX 매니페스트에 기록됩니다. 매니페스트는 linkerconfig를 통해 처리되므로 외부 네이티브 종속 항목은 런타임에 사용할 수 있습니다.

다음 스니펫에서 APEX는 바이너리 (my_service)와 안정적이지 않은 종속 항목 (*.so 파일)을 모두 포함합니다.

apex {
  ..
  vendor: true,
  binaries: ["my_service"],
  ..
}

다음 스니펫에서 APEX는 공유 라이브러리 my_standalone_lib과 안정적이지 않은 종속 항목을 포함합니다 (위의 설명 참고).

apex {
  ..
  vendor: true,
  native_shared_libs: ["my_standalone_lib"],
  ..
}

APEX 축소

APEX는 안정적이지 않은 종속 항목을 번들로 묶기 때문에 크기가 커질 수 있습니다. 정적 연결을 사용하는 것이 좋습니다. libc++.solibbase.so와 같은 공통 라이브러리는 HAL 바이너리에 정적으로 연결할 수 있습니다. 안정적인 인터페이스를 제공하기 위해 종속 항목을 만드는 것도 또 다른 방법입니다. 종속 항목은 APEX에 번들로 묶이지 않습니다.

HAL 구현

HAL 구현을 정의하려면 다음 예와 비슷하게 공급업체 APEX 내의 바이너리 및 라이브러리 중 대응하는 바이너리와 라이브러리를 제공합니다.

HAL 구현을 완전히 캡슐화하려면 APEX는 관련 VINTF 프래그먼트와 init 스크립트도 지정해야 합니다.

VINTF 프래그먼트

VINTF 프래그먼트는 프래그먼트가 APEX의 etc/vintf에 위치한 경우 공급업체 APEX에서 제공할 수 있습니다.

prebuilts 속성을 사용하여 APEX에 VINTF 프래그먼트를 삽입하세요.

apex {
  ..
  vendor: true,
  prebuilts: ["fragment.xml"],
  ..
}

prebuilt_etc {
  name: "fragment.xml",
  src: "fragment.xml",
  sub_dir: "vintf",
}

쿼리 API

VINTF 프래그먼트가 APEX에 추가되면 libbinder_ndk API를 사용하여 HAL 인터페이스와 APEX 이름의 매핑을 가져옵니다.

  • AServiceManager_isUpdatableViaApex("com.android.foo.IFoo/default") : HAL 인스턴스가 APEX에 정의된 경우 true입니다.
  • AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...) : HAL 인스턴스를 정의하는 APEX 이름을 가져옵니다.
  • AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...) : 패스 스루 HAL을 열 때 사용합니다.

Init 스크립트

APEX는 두 가지 방법으로 init 스크립트를 포함할 수 있습니다. (A) APEX 페이로드 내에 사전 빌드된 텍스트 파일 또는 (B) /vendor/etc에 있는 일반 init 스크립트를 사용하면 됩니다. 동일한 APEX에 둘 다 설정할 수 있습니다.

APEX의 init 스크립트는 다음과 같습니다.

prebuilt_etc {
  name: "myinit.rc",
  src: "myinit.rc"
}

apex {
  ..
  vendor: true,
  prebuilts: ["myinit.rc"],
  ..
}

공급업체 APEX의 init 스크립트에는 service 정의와 on <property or event> 지시어가 포함될 수 있습니다.

service 정의가 동일한 APEX의 바이너리를 가리키는지 확인합니다. 예를 들어 com.android.foo APEX는 foo-service라는 서비스를 정의할 수 있습니다.

on foo-service /apex/com.android.foo/bin/foo
  ...

on 지시어를 사용할 때는 주의하세요. APEX의 init 스크립트는 APEX가 활성화된 에 파싱되고 실행되므로 일부 이벤트 또는 속성은 사용할 수 없습니다. apex.all.ready=true를 사용하여 가급적 조기에 작업을 트리거하세요. 부트스트랩 APEXon init를 사용할 수 있지만 on early-init는 사용할 수 없습니다.

펌웨어

예:

다음과 같이 prebuilt_firmware 모듈 유형을 사용하여 공급업체 APEX에 펌웨어를 삽입합니다.

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
  ..
}

prebuilt_firmware 모듈은 APEX의 <apex name>/etc/firmware 디렉터리에 설치됩니다. ueventd/apex/*/etc/firmware 디렉터리를 스캔하여 펌웨어 모듈을 찾습니다.

APEX의 file_contexts는 런타임에 ueventd에서 이러한 파일에 액세스할 수 있도록 모든 펌웨어 페이로드 항목에 올바르게 라벨을 지정해야 합니다. 일반적으로 vendor_file 라벨이면 충분합니다. 예:

(/.*)? u:object_r:vendor_file:s0

커널 모듈

다음과 같이 커널 모듈을 사전 빌드된 모듈로 공급업체 APEX에 삽입합니다.

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
  ..
}

APEX의 file_contexts는 모든 커널 모듈 페이로드 항목에 올바르게 라벨을 지정해야 합니다. 예:

/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0

커널 모듈은 명시적으로 설치해야 합니다. 다음의 공급업체 파티션 init 스크립트 예는 insmod를 사용하여 설치하는 방법을 보여줍니다.

my_init.rc:

on early-boot
  insmod /apex/myapex/etc/modules/my.ko
  ..

런타임 리소스 오버레이

예:

rros 속성을 사용하여 공급업체 APEX에 런타임 리소스 오버레이를 삽입합니다.

runtime_resource_overlay {
    name: "my_rro",
    soc_specific: true,
}


apex {
  ..
  vendor: true,
  rros: ["my_rro"],  // installed inside APEX as /overlay/my_rro.apk
  ..
}

기타 구성 파일

공급업체 APEX는 구성 파일이 사전에 공급업체 APEX 내에 빌드되어 있으므로 일반적으로 공급업체 파티션에서 볼 수 있는 다양한 구성 파일을 지원하며 이러한 구성 파일은 계속 추가되고 있습니다.

예:

부트스트랩 공급업체 APEX

APEX가 활성화되기 전에 keymint와 같은 일부 HAL 서비스를 사용할 수 있어야 합니다. 이러한 HAL은 일반적으로 init 스크립트의 서비스 정의에서 early_hal를 설정합니다. 또 다른 예는 일반적으로 post-fs-data 이벤트보다 먼저 시작되는 animation 클래스입니다. 이러한 초기 HAL 서비스가 공급업체 APEX에 패키징된 경우 더 일찍 활성화할 수 있도록 APEX 매니페스트에서 apex "vendorBootstrap": true를 만듭니다. 부트스트랩 APEX는 /data/apex이 아닌 /vendor/apex와 같은 사전 빌드된 위치에서만 활성화할 수 있습니다.

시스템 속성

다음은 프레임워크가 공급업체 APEX를 지원하기 위해 읽는 시스템 속성입니다.

  • input_device.config_file.apex=<apex name> - 설정하면 입력 구성 파일 (*.idc, *.kl, *.kcm)이 APEX의 /etc/usr 디렉터리에서 검색됩니다.
  • ro.vulkan.apex=<apex name> - 설정하면 Vulkan 드라이버가 APEX에서 로드됩니다. Vulkan 드라이버는 초기 HAL에서 사용되므로 APEX를 부트스트랩 APEX로 만들고 해당 링커 네임스페이스를 표시되도록 구성합니다.

setprop 명령어를 사용하여 init 스크립트에서 시스템 속성을 설정합니다.

추가 개발 기능

부팅 시 APEX 선택

예:

개발자는 같은 APEX 이름과 키를 공유하는 공급업체 APEX의 여러 버전을 설치할 수 있고 그런 다음 영구 sysprop를 사용하여 부팅할 때마다 활성화할 버전을 선택할 수도 있습니다. 특정 개발자 사용 사례의 경우, 이렇게 하는 것이 adb install을 사용하여 APEX의 새 사본을 설치하는 것보다 간단할 수 있습니다.

사용 사례 예:

  • Wi-Fi HAL 공급업체 APEX의 3가지 버전 설치: QA팀은 한 버전을 사용하여 수동 테스트 또는 자동 테스트를 실행한 후 다른 버전으로 재부팅하고 테스트를 다시 실행한 후 최종 결과를 비교할 수 있습니다.
  • 카메라 HAL 공급업체 APEX의 두 가지 버전, currentexperimental 설치: Google 내부 테스터는 추가로 파일을 다운로드 및 설치하지 않고도 시험용 버전을 사용할 수 있으므로 쉽게 두 버전 간에 전환할 수 있습니다.

부팅하는 동안 apexd는 올바른 APEX 버전을 활성화하기 위해 특정 형식에 따라 sysprop를 찾습니다.

속성 키의 예상 형식은 다음과 같습니다.

  • Bootconfig
    • BoardConfig.mk에 기본값을 설정하는 데 사용됩니다.
    • androidboot.vendor.apex.<apex name>
  • 영구 sysprop
    • 기본값을 변경하는 데 사용되며 이미 부팅된 기기에 설정됩니다.
    • bootconfig 값이 있는 경우 해당 값을 재정의합니다.
    • persist.vendor.apex.<apex name>

속성 값은 활성화되어야 하는 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,
  ..
}

또한, 기본 버전은 BoardConfig.mk의 bootconfig를 사용하여 구성해야 합니다.

# 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

기기가 부팅되면 영구 sysprop를 설정하여 활성화된 버전을 변경합니다.

$ adb root;
$ adb shell setprop \
    persist.vendor.apex.com.oem.camera.hal \
    com.oem.camera.hal.my_apex_experimental;
$ adb reboot;

기기가 플래시 후 bootconfig 업데이트를 지원하는 경우(fastboot oem 명령어 사용) 다중 설치된 APEX의 bootconfig 속성이 변경되면 부팅 시 활성화된 버전도 변경됩니다.

Cuttlefish 기반의 가상 참조 기기의 경우 --extra_bootconfig_args 명령어를 사용하여 실행하는 동안 bootconfig 속성을 직접 설정할 수 있습니다. 예:

launch_cvd --noresume \
  --extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";