안정적 AIDL

Android 10에는 Android 인터페이스 정의 언어(AIDL) 인터페이스에서 제공하는 애플리케이션 프로그래밍 인터페이스(API)/Application Binary Interface(ABI)를 추적할 수 있는 새로운 방법인 안정적 AIDL을 위한 지원이 추가되었습니다. 안정적 AIDL은 다음과 같이 AIDL과 다른 주요 차이점을 지닙니다.

  • 인터페이스가 aidl_interfaces를 통해 빌드 시스템에서 정의됩니다.
  • 인터페이스에는 구조화된 데이터만 포함될 수 있습니다. 원하는 유형을 나타내는 parcelable은 AIDL 정의에 따라 자동으로 생성되며 마셜링과 마셜링 해제가 자동으로 이루어집니다.
  • 인터페이스는 안정적인 것으로 선언할 수 있습니다(이전 버전과 호환 가능). 이 경우 API는 AIDL 인터페이스에 인접한 파일을 통해 추적되고 버전이 지정됩니다.

구조화된 AIDL과 안정적 AIDL

구조화된 AIDL은 순전히 AIDL로만 정의된 유형을 가리킵니다. 예를 들어, parcelable 선언(맞춤 parcelable)은 구조화된 AIDL이 아닙니다. 필드가 AIDL로 정의된 parcelable은 구조화된 parcelable이라고 합니다.

안정적 AIDL의 경우 빌드 시스템과 컴파일러가 parcelable에 적용된 변경 사항이 하위 호환되는지 알 수 있으려면 구조화된 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 소스 파일의 목록입니다. com.acme 패키지에 정의된 AIDL 유형인 Foo의 경로는 <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의 인터페이스 또는 parcelable을 사용하는 경우 여기에 그 이름을 지정합니다. 그 이름 자체를 사용해 최신 버전을 지정할 수도 있고, 버전 접미사를 사용해(예: -V1) 특정 버전을 지정할 수도 있습니다. 버전 지정은 Android 12부터 지원됩니다.
  • versions: api_dir 아래에 고정된 이전 버전의 인터페이스로, Android 11부터 versionsaidl_api/name 아래에 고정됩니다. 고정된 버전의 인터페이스가 없는 경우 이 속성을 지정해서는 안 되며 호환성 검사도 이루어지지 않습니다. 이 필드는 13 이상에서 versions_with_info로 대체되었습니다.
  • versions_with_info: 튜플 목록이며, 각각 고정된 버전의 이름과 이 버전의 aidl_interface가 가져온 다른 aidl_interface 모듈의 버전 가져오기가 있는 목록이 포함되어 있습니다. AIDL 인터페이스 IFACE의 버전 V 정의는 aidl_api/IFACE/V에 있습니다. 이 필드는 Android 13에서 도입되었으며 Android.bp에서 직접 수정해서는 안 됩니다. 이 필드는 *-update-api 또는 *-freeze-api를 호출하여 추가하거나 업데이트합니다. 또한 사용자가 *-update-api 또는 *-freeze-api를 호출하면 versions 필드는 versions_with_info로 자동 이전됩니다.
  • stability: 이 인터페이스의 안정성 약속을 나타내는 선택적 플래그입니다. 현재는 "vintf"만 지원합니다. 설정되지 않은 경우 이 플래그는 이 컴파일 컨텍스트 내에서 안정성 있는 인터페이스에 대응합니다. 따라서 여기 로드된 인터페이스는 system.img에서 함께 컴파일된 것과 같이 함께 컴파일된 파일에만 사용될 수 있습니다. 이 플래그를 "vintf"로 설정하면 이는 안정성 약속에 해당합니다. 즉, 인터페이스가 사용되는 한 이 인터페이스는 안정적으로 유지되어야 합니다.
  • gen_trace: 트레이싱을 켜거나 끄는 선택적 플래그입니다. Android 14부터 기본값은 cppjava 백엔드의 경우 true입니다.
  • host_supported: true로 설정될 시 생성된 라이브러리를 호스트 환경에서 사용할 수 있도록 허용하는 선택적 플래그입니다.
  • unstable: 이 인터페이스가 안정적일 필요가 없음을 표시할 때 사용되는 선택적 플래그입니다. true로 설정되면 빌드 시스템에서 인터페이스 API 덤프를 만들지 않으며 업데이트하도록 요구하지 않습니다.
  • frozen: true로 설정할 경우 이전 버전의 인터페이스 이후 인터페이스에 변경사항이 없음을 나타내는 선택적 플래그입니다. 이를 통해 더 많은 빌드 시간 확인이 가능합니다. false로 설정하면 인터페이스가 개발 중이고 새로운 변경사항이 있음을 의미하므로 foo-freeze-api를 실행하면 새 버전이 생성되고 값이 자동으로 true로 변경됩니다. Android 14에서 도입되었습니다.
  • backend.<type>.enabled: 이 플래그는 AIDL 컴파일러가 코드를 생성하는 각 백엔드를 전환합니다. 현재 지원되는 4가지 백엔드는 Java, C++, NDK, Rust입니다. Java, C++, NDK 백엔드는 기본적으로 사용 설정됩니다. 이 3가지 중 필요하지 않은 백엔드는 명시적으로 사용 중지해야 합니다. Rust는 기본적으로 사용 중지되어 있습니다.
  • backend.<type>.apex_available: 생성된 스텁 라이브러리를 사용할 수 있는 APEX 이름의 목록입니다.
  • backend.[cpp|java].gen_log: 트랜잭션에 관한 정보 수집용 추가 코드를 생성할지를 제어하는 선택적 플래그입니다.
  • backend.[cpp|java].vndk.enabled: 이 인터페이스를 VNDK의 일부로 만드는 선택적 플래그입니다. 기본값은 false입니다.
  • backend.[cpp|ndk].additional_shared_libraries: Android 14에서 도입되었습니다. 이 플래그는 네이티브 라이브러리에 종속 항목을 추가하며 ndk_headercpp_header에서 유용합니다.
  • backend.java.sdk_version: Java 스텁 라이브러리가 빌드된 SDK 버전을 지정하기 위한 선택적 플래그입니다. 기본값은 "system_current"입니다. backend.java.platform_apis가 true일 때는 이 값을 설정하면 안 됩니다.
  • backend.java.platform_apis: 생성된 라이브러리가 SDK가 아닌 플랫폼 API를 기준으로 빌드되어야 하는 경우 true로 설정해야 하는 선택적 플래그입니다.

버전과 사용 설정된 백엔드의 조합마다 스텁 라이브러리가 만들어집니다. 특정 백엔드의 특정 스텁 라이브러리 버전을 참조하는 방법은 모듈 이름 지정 규칙을 확인하세요.

AIDL 파일 쓰기

안정적 AIDL의 인터페이스는 구조화되지 않은 parcelable이 안정적이지 않아(구조화된 AIDL과 안정적 AIDL 참고) 사용할 수 없다는 점을 제외하면 기존 인터페이스와 유사합니다. 안정적 AIDL의 가장 큰 차이점은 parcelable이 정의되는 방식입니다. 이전에는 parcelable이 전방 선언되었습니다. 안정적(따라서 구조화된) AIDL에서는 parcelable 필드와 변수가 명시적으로 정의됩니다.

// 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 열거자가 없어도 0으로 초기화됩니다.

스텁 라이브러리 사용

스텁 라이브러리를 모듈에 종속 항목으로 추가한 후 이를 파일에 포함할 수 있습니다. 다음은 빌드 시스템의 스텁 라이브러리 예시입니다. 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

Java 예시:

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

Rust 예시:

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

버전 관리 인터페이스

이름이 foo인 모듈을 선언하면 빌드 시스템에 모듈의 API를 관리하는 데 사용할 수 있는 타겟도 생성됩니다. foo-freeze-api가 빌드되면 Android 버전에 따라 api_dir 또는 aidl_api/name 아래에 새로운 API 정의가 추가되고 .hash 파일이 추가되어 새로 고정된 인터페이스의 버전을 표시합니다. foo-freeze-api는 해당 버전의 추가 버전과 imports를 반영하도록 versions_with_info 속성도 업데이트합니다. 기본적으로 versions_with_infoimportsimports 필드에서 복사됩니다. 그러나 최신 안정화 버전은 명시적 버전이 없는 가져오기에 대해 versions_with_infoimports에 지정됩니다. versions_with_info 속성이 지정되면 빌드 시스템은 고정된 버전 간에 호환성 검사를 실행하며 ToT(Top of Tree)와 최신 고정 버전 간에도 검사를 실행합니다.

또한 ToT 버전의 API 정의를 관리해야 합니다. API가 업데이트될 때마다 foo-update-api를 실행하여 ToT 버전의 API 정의를 포함하는 aidl_api/name/current 코드를 업데이트합니다.

인터페이스의 안정성을 유지하기 위해 소유자는 다음을 새로 추가할 수 있습니다.

  • 인터페이스 끝에 메서드(또는 명시적으로 새 일련번호가 정의된 메서드)
  • parcelable 끝에 요소(각 요소의 기본값을 추가해야 함)
  • 상수 값
  • 열거자(Android 11의 경우)
  • union의 끝부분까지의 필드(Android 12의 경우)

다른 작업은 허용되지 않으며 다른 누구도 인터페이스를 수정할 수 없습니다(그렇지 않으면 소유자가 변경한 사항과 충돌 위험이 있음).

모든 인터페이스가 출시를 위해 고정되었는지 테스트하려면 다음 환경 변수를 설정하여 빌드할 수 있습니다.

  • AIDL_FROZEN_REL=true m ... - owner: 필드가 지정되지 않은 모든 안정적인 AIDL 인터페이스가 고정되어야 합니다.
  • AIDL_FROZEN_OWNERS="aosp test" - owner: 필드가 'aosp' 또는 'test'로 지정된 상태로 모든 안정적인 AIDL 인터페이스가 고정되어야 합니다.

가져오기의 안정성

인터페이스의 고정된 버전에 관한 가져오기 버전을 업데이트하면 안정적 AIDL 레이어에서 이전 버전과 호환됩니다. 그러나 이를 업데이트하려면 이전 버전의 인터페이스를 사용하는 모든 서버와 클라이언트를 업데이트해야 하며, 일부 애플리케이션은 여러 버전 유형을 혼합할 때 혼동될 수 있습니다. 이는 유형 전용 또는 일반 패키지의 경우 대개 안전한데, 그 이유는 IPC 트랜잭션의 알 수 없는 유형을 처리하도록 코드가 작성되어야 하기 때문입니다.

Android 플랫폼 코드에서 android.hardware.graphics.common은 이러한 유형의 버전 업그레이드에 관한 가장 큰 예입니다.

버전이 지정된 인터페이스 사용

인터페이스 메서드

런타임 시에 이전 서버에서 새 메서드를 호출하려고 하면 새 클라이언트에서 백엔드에 따라 오류 또는 예외가 발생합니다.

  • cpp 백엔드의 경우 ::android::UNKNOWN_TRANSACTION이 발생합니다.
  • ndk 백엔드의 경우 STATUS_UNKNOWN_TRANSACTION이 발생합니다.
  • java 백엔드의 경우 API가 구현되지 않았다는 메시지와 함께 android.os.RemoteException이 발생합니다.

이를 처리하는 전략은 버전 쿼리하기기본값 사용하기를 참고하세요.

Parcelable

새 필드가 parcelable에 추가되면 이전 클라이언트 및 서버에서 이를 삭제합니다. 새 클라이언트 및 서버에서 이전 parcelable을 수신하면 새 필드의 기본값이 자동으로 채워집니다. 즉, parcelable의 모든 새 필드에 기본값을 지정해야 합니다.

클라이언트는 서버가 새 필드가 정의된 버전을 구현한다는 사실을 알고 있지 않은 이상 서버가 새 필드를 사용할 것이라고 예상하지 않아야 합니다(버전 쿼리하기 참고).

열거형 및 상수

마찬가지로, 이후에 더 추가될 수 있으므로 클라이언트 및 서버는 인식할 수 없는 상수 값과 열거자를 적절하게 거부하거나 무시해야 합니다. 예를 들어, 서버는 알지 못하는 열거자를 수신한 경우 취소해서는 안 됩니다. 서버는 이 열거자를 무시하거나 이 구현에서 해당 열거자가 지원되지 않는다는 사실을 클라이언트가 알 수 있도록 무언가를 반환해야 합니다.

Union

수신기가 오래되어 필드에 관해 알지 못하는 경우 새 필드가 있는 union을 전송하려고 하면 작업이 실패합니다. 구현에서는 새 필드가 있는 union이 표시되지 않습니다. 단방향 트랜잭션인 경우 실패는 무시됩니다. 그 밖의 경우에는 오류가 BAD_VALUE(C++ 또는 NDK 백엔드) 또는 IllegalArgumentException(Java 백엔드)입니다. 클라이언트가 새 필드로 설정된 union을 이전 서버로 전송하거나 이전 클라이언트가 새 서버에서 union을 수신하는 경우 이 오류가 수신됩니다.

플래그 기반 개발

개발 중인(고정되지 않은) 인터페이스는 출시 기기에서 사용할 수 없는데 그 이유는 이전 버전과의 호환성이 보장되지 않기 때문입니다.

AIDL은 코드가 고정되지 않은 최신 버전에 대해 작성되고 출시 기기에서 계속 사용되도록 이러한 고정되지 않은 인터페이스 라이브러리의 런타임 대체를 지원합니다. 클라이언트의 하위 호환 동작은 기존 동작과 비슷하며 대체를 사용하면 구현도 이러한 동작을 따라야 합니다. 버전이 지정된 인터페이스 사용을 참고하세요.

AIDL 빌드 플래그

이 동작을 제어하는 플래그는 build/release/build_flags.bzl에 정의된 RELEASE_AIDL_USE_UNFROZEN입니다. true는 고정되지 않은 버전의 인터페이스가 런타임에 사용되는 것을 의미하며 false는 고정되지 않은 버전의 라이브러리가 모두 마지막 고정 버전처럼 동작하는 것을 의미합니다. 로컬 개발의 경우 플래그를 true로 재정의할 수 있지만 출시 전에 false로 되돌려야 합니다. 일반적으로 개발은 플래그가 true로 설정된 구성으로 실행됩니다.

호환성 매트릭스 및 매니페스트

공급업체 인터페이스 객체(VINTF 객체)는 예상되는 버전과 공급업체 인터페이스 양쪽에 제공되는 버전을 정의합니다.

Cuttlefish 이외의 기기는 대부분 인터페이스가 고정된 후에만 최신 호환성 매트릭스를 타겟팅하므로 RELEASE_AIDL_USE_UNFROZEN에 기반한 AIDL 라이브러리에 차이가 없습니다.

파트너 소유 인터페이스는 개발 중에 기기에서 타겟팅하는 기기별 또는 제품별 호환성 매트릭스에 추가됩니다. 따라서 고정되지 않은 새 버전의 인터페이스가 호환성 매트릭스에 추가되면 이전 고정 버전은 RELEASE_AIDL_USE_UNFROZEN=false로 유지되어야 합니다. 다양한 RELEASE_AIDL_USE_UNFROZEN 구성에 여러 호환성 매트릭스 파일을 사용하거나 모든 구성에서 사용되는 단일 호환성 매트릭스 파일에 두 버전을 모두 허용하여 이를 처리할 수 있습니다.

예를 들어 고정되지 않은 버전 4를 추가할 때는 <version>3-4</version>를 사용합니다.

버전 4가 고정되면 호환성 매트릭스에서 버전 3을 삭제할 수 있습니다. RELEASE_AIDL_USE_UNFROZENfalse일 때 고정 버전 4를 사용하기 때문입니다.

HAL 클라이언트 변경사항

HAL 클라이언트 코드는 지원되는 이전의 각 고정 버전과 하위 호환되어야 합니다. RELEASE_AIDL_USE_UNFROZENfalse이면 서비스는 항상 마지막 고정 버전 이하처럼 표시됩니다. 예를 들어 고정되지 않은 새 메서드를 호출하면 UNKNOWN_TRANSACTION이 반환되거나 새 parcelable 필드에 기본값이 있습니다. Android 프레임워크 클라이언트는 추가 이전 버전과 호환되어야 하지만 이는 공급업체 클라이언트와 파트너 소유 인터페이스 클라이언트의 새로운 세부정보입니다.

HAL 구현 변경사항

HAL 개발과 플래그 기반 개발의 가장 큰 차이점은 RELEASE_AIDL_USE_UNFROZENfalse일 때 작동하기 위해 HAL 구현이 마지막 고정 버전과 하위 호환되어야 한다는 요구사항입니다. 구현과 기기 코드에서 하위 호환성을 고려하는 것이 새로 권장됩니다. 버전이 지정된 인터페이스 사용을 참고하세요.

하위 호환성 고려사항은 일반적으로 클라이언트 및 서버 그리고 프레임워크 코드 및 공급업체 코드에서 동일합니다. 그러나 이제 동일한 소스 코드를 사용하는 두 버전(고정되지 않은 최신 버전)을 효과적으로 구현하고 있으므로 미세한 차이점은 알고 있어야 합니다.

예: 인터페이스에 고정 버전 세 개가 있습니다. 인터페이스는 새 메서드로 업데이트됩니다. 클라이언트와 서비스는 모두 새 버전 4 라이브러리를 사용하도록 업데이트됩니다. V4 라이브러리는 고정되지 않은 버전의 인터페이스에 기반하므로 RELEASE_AIDL_USE_UNFROZENfalse일 때 마지막 고정 버전(버전 3)처럼 동작하여 새 메서드를 사용하지 못하게 합니다.

인터페이스가 고정되면 모든 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();
  }
}

RELEASE_AIDL_USE_UNFROZENfalse이고 서비스가 전송하려는 새 필드의 값이 프로세스 도중에 삭제되는 경우 기존 유형(parcelable, enum, union)의 새 필드는 존재하지 않거나 기본값을 포함할 수 있습니다.

고정되지 않은 이 버전에 추가된 새 유형은 인터페이스를 통해 전송하거나 수신할 수 없습니다.

구현은 RELEASE_AIDL_USE_UNFROZENfalse이면 어떤 클라이언트에서도 새 메서드 호출을 가져오지 않습니다.

이전 버전이 아닌 도입된 버전에서만 새 열거자를 사용하도록 주의하세요.

일반적으로 원격 인터페이스에서 사용 중인 버전을 확인하는 데는 foo->getInterfaceVersion()을 사용합니다. 그러나 플래그 기반 버전 관리 지원을 통해 두 가지 버전을 구현하므로 현재 인터페이스의 버전을 가져오는 것이 좋습니다. 이 작업은 this->getInterfaceVersion() 또는 my_ver의 다른 메서드와 같이 현재 객체의 인터페이스 버전을 가져오면 됩니다. 자세한 내용은 원격 객체의 인터페이스 버전 쿼리를 참고하세요.

새로운 VINTF 안정적 인터페이스

새 AIDL 인터페이스 패키지가 추가되면 마지막 고정 버전이 없으므로 RELEASE_AIDL_USE_UNFROZENfalse일 때 대체할 동작이 없습니다. 이러한 인터페이스는 사용하지 마세요. RELEASE_AIDL_USE_UNFROZENfalse일 때 서비스 관리자는 서비스가 인터페이스를 등록하도록 허용하지 않으므로 클라이언트는 이를 찾을 수 없습니다.

기기 makefile의 RELEASE_AIDL_USE_UNFROZEN 플래그 값에 따라 조건부로 서비스를 추가할 수 있습니다.

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

서비스가 더 큰 프로세스의 일부라서 기기에 조건부로 추가할 수 없으면 서비스가 IServiceManager::isDeclared()를 사용하여 선언되었는지 확인할 수 있습니다. 선언되었지만 등록에 실패한 경우 프로세스를 중단합니다. 선언되지 않은 경우 등록에 실패할 것으로 예상됩니다.

개발 도구 Cuttlefish

매년 VINTF가 고정된 후 프레임워크 호환성 매트릭스(FCM) target-level과 Cuttlefish PRODUCT_SHIPPING_API_LEVEL이 조정되므로 내년 버전으로 출시되는 기기를 반영합니다. 출시 기기가 테스트되고 내년 버전의 새 요구사항을 충족할 수 있도록 target-levelPRODUCT_SHIPPING_API_LEVEL을 조정합니다.

RELEASE_AIDL_USE_UNFROZENtrue이면 Cuttlefish가 향후 Android 버전 개발에 사용됩니다. 이는 내년 Android 버전의 FCM 수준과 PRODUCT_SHIPPING_API_LEVEL을 타겟팅하므로 다음 버전의 공급업체 소프트웨어 요구사항(VSR)을 충족해야 합니다.

RELEASE_AIDL_USE_UNFROZENfalse이면 Cuttlefish에는 출시 기기를 반영하는 이전 target-levelPRODUCT_SHIPPING_API_LEVEL이 있습니다. Android 14 및 이전 버전에서는 FCM target-level, 배송 API 수준 또는 다음 버전을 타겟팅하는 기타 코드의 변경사항을 선택하지 않는 다양한 Git 브랜치를 통해 이러한 차별화가 이루어집니다.

모듈 이름 지정 규칙

Android 11에서는 버전과 사용 설정된 백엔드의 조합마다 스텁 라이브러리 모듈이 자동으로 생성됩니다. 연결을 위해 특정 스텁 라이브러리 모듈을 참조하려면 aidl_interface 모듈 이름을 사용하는 대신 ifacename-version-backend인 스텁 라이브러리 모듈 이름을 사용합니다. 각각의 의미는 다음과 같습니다.

  • ifacename: aidl_interface 모듈의 이름입니다.
  • version은 다음 중 하나입니다.
    • Vversion-number: 고정된 버전
    • Vlatest-frozen-version-number + 1: 아직 고정되지 않은 기타 버전
  • backend는 다음 중 하나입니다.
    • java: Java 백엔드
    • cpp: C++ 백엔드
    • ndk 또는 ndk_platform: NDK 백엔드. 전자는 앱용이고 후자는 플랫폼용입니다.
    • rust는 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-backendfoo-V2-backend가 됩니다.
  • ToT 버전을 참조한 foo-unstable-backendfoo-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 컴파일러는 안정적인 AIDL 인터페이스를 위한 unstable 버전 모듈이나 버전이 지정되지 않은 모듈을 만들지 않습니다. 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();

ndkndk_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()를 다음과 같이 구현해야 합니다(MUST). 복사/붙여넣기 실수를 방지하기 위해 IFoo 대신 super가 사용됩니다. javac 구성에 따라 경고를 사용 중지하려면 @SuppressWarnings("static") 주석이 필요할 수 있습니다.

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

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

이는 생성된 클래스(IFoo, IFoo.Stub 등)가 클라이언트와 서버 간에 공유되기 때문입니다(예: 클래스가 부팅 클래스 경로에 있을 수 있음). 클래스가 공유될 때 서버는 이전 버전의 인터페이스로 빌드된 경우라도 클래스의 최신 버전과도 연동됩니다. 이 메타 인터페이스는 공유된 클래스에 구현될 경우 항상 최신 버전을 반환합니다. 그러나 위와 같이 메서드를 구현하면 (IFoo.VERSION은 참조 시 인라인된 static final int이므로) 인터페이스의 버전 번호가 서버의 코드에 삽입되고, 이에 따라 메서드는 서버가 빌드될 때 사용된 정확한 버전을 반환할 수 있습니다.

이전 인터페이스 처리

클라이언트가 최신 버전의 AIDL 인터페이스로 업데이트되었지만 서버는 이전 AIDL 인터페이스를 사용하고 있을 수 있습니다. 이런 경우 이전 인터페이스의 메서드를 호출하면 UNKNOWN_TRANSACTION이 반환됩니다.

안정적 AIDL을 사용하면 클라이언트는 더 큰 통제권을 갖습니다. 클라이언트 측에서 AIDL 인터페이스의 기본 구현을 설정할 수 있습니다. 기본 구현 메서드는 (메서드가 이전 버전의 인터페이스로 빌드되었으므로) 메서드가 원격 측에서 구현되지 않은 경우에만 호출됩니다. 기본값은 전역으로 설정되므로 잠재적으로 공유된 컨텍스트에서 사용해서는 안 됩니다.

Android 13 및 이후 버전의 C++ 예:

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

Java 예시:

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. 인터페이스의 모든 parcelable을 안정적 parcelable로 변환합니다(인터페이스 파일 자체는 변경되지 않은 상태로 둘 수 있음). AIDL 파일에서 구조를 직접 표현하여 이를 처리합니다. 이러한 새 유형을 사용하려면 관리 클래스를 다시 작성해야 합니다. 아래에서 aidl_interface 패키지를 생성하기 전에 이 작업을 할 수 있습니다.

  3. (위에서 설명한 대로) 모듈 이름, 종속 항목 및 필요한 기타 정보가 포함된 aidl_interface 패키지를 생성합니다. 구조화하는 것뿐만 아니라 안정화하려면 버전이 지정되어야 합니다. 자세한 내용은 버전 관리 인터페이스를 참조하세요.