안정적인 AIDL

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

Android 10은 AIDL 인터페이스에서 제공하는 애플리케이션 프로그램 인터페이스(API)/애플리케이션 바이너리 인터페이스(ABI)를 추적하는 새로운 방법인 안정적인 Android 인터페이스 정의 언어(AIDL)에 대한 지원을 추가합니다. 안정적인 AIDL은 AIDL과 다음과 같은 주요 차이점이 있습니다.

  • 인터페이스는 aidl_interfaces 를 사용하여 빌드 시스템에서 정의됩니다.
  • 인터페이스는 구조화된 데이터만 포함할 수 있습니다. 원하는 유형을 나타내는 Parcelable은 AIDL 정의에 따라 자동으로 생성되며 자동으로 마샬링 및 언마샬링됩니다.
  • 인터페이스는 안정적(역방향 호환)으로 선언될 수 있습니다. 이런 일이 발생하면 AIDL 인터페이스 옆에 있는 파일에서 API를 추적하고 버전을 지정합니다.

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: ["ohter-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 의 소포를 사용하는 경우 여기에 이름을 입력하세요. 이것은 최신 버전을 나타내는 자체 이름이거나 특정 버전을 나타내는 버전 접미사(예: -V1 )가 있는 이름일 수 있습니다. 버전 지정은 Android 12부터 지원되었습니다.
  • versions : api_dir 아래에 고정된 인터페이스의 이전 버전, Android 11부터 versionsaidl_api/ name 아래에 고정됩니다. 고정된 버전의 인터페이스가 없는 경우 이를 지정하면 안 되며 호환성 검사도 수행되지 않습니다. 이 필드는 13 이상 versions_with_info 의 경우 version_with_info로 대체되었습니다.
  • version_with_info : 튜플 목록으로, 각각 고정된 versions_with_info 의 이름과 이 버전의 aidl_interface가 가져온 다른 aidl_interface 모듈의 버전 가져오기 목록이 포함되어 있습니다. AIDL 인터페이스 IFACE의 버전 V 정의는 aidl_api/ IFACE / V 에 있습니다. 이 필드는 Android 13에서 도입되었으며 Android.bp에서 직접 수정할 수 없습니다. *-update-api 또는 *-freeze-api 를 호출하여 필드를 추가하거나 업데이트합니다. 또한 versions 필드는 사용자가 *-update-api 또는 *-freeze-api versions_with_info 호출할 때 Versions_with_info로 자동 마이그레이션됩니다.
  • stability : 이 인터페이스의 안정성 약속에 대한 선택적 플래그입니다. 현재 "vintf" 만 지원합니다. 이것이 설정되지 않은 경우, 이것은 이 컴파일 컨텍스트 내에서 안정성이 있는 인터페이스에 해당합니다(따라서 여기에 로드된 인터페이스는 예를 들어 system.img에서 함께 컴파일된 항목에만 사용할 수 있습니다). 이것이 "vintf" 로 설정되면 안정성 약속에 해당합니다. 인터페이스는 사용되는 동안 안정적으로 유지되어야 합니다.
  • gen_trace : 추적을 켜거나 끄는 선택적 플래그입니다. 기본값은 false 입니다.
  • host_supported : true 로 설정하면 생성된 라이브러리를 호스트 환경에서 사용할 수 있도록 하는 선택적 플래그입니다.
  • unstable : 이 인터페이스가 안정적일 필요가 없음을 표시하는 데 사용되는 선택적 플래그입니다. 이것이 true 로 설정되면 빌드 시스템은 인터페이스에 대한 API 덤프를 생성하지 않으며 업데이트를 요구하지도 않습니다.
  • backend.<type>.enabled : 이 플래그는 AIDL 컴파일러가 코드를 생성하는 각 백엔드를 토글합니다. 현재 Java, C++, NDK 및 Rust의 네 가지 백엔드가 지원됩니다. Java, C++ 및 NDK 백엔드는 기본적으로 활성화되어 있습니다. 이 세 가지 백엔드 중 하나라도 필요하지 않으면 명시적으로 비활성화해야 합니다. Rust는 기본적으로 비활성화되어 있습니다.
  • backend.<type>.apex_available : 생성된 스텁 라이브러리를 사용할 수 있는 APEX 이름 목록입니다.
  • backend.[cpp|java].gen_log : 트랜잭션에 대한 정보를 수집하기 위해 추가 코드를 생성할지 여부를 제어하는 ​​선택적 플래그입니다.
  • backend.[cpp|java].vndk.enabled : 이 인터페이스를 VNDK의 일부로 만드는 선택적 플래그입니다. 기본값은 false 입니다.
  • backend.java.platform_apis : Java 스텁 라이브러리가 플랫폼의 프라이빗 API에 대해 빌드되는지 여부를 제어하는 ​​선택적 플래그입니다. stability"vintf" 로 설정된 경우 "true" 로 설정해야 합니다.
  • backend.java.sdk_version : Java 스텁 라이브러리가 빌드되는 SDK의 버전을 지정하기 위한 선택적 플래그입니다. 기본값은 "system_current" 입니다. backend.java.platform_apis 가 true인 경우 설정하면 안 됩니다.
  • backend.java.platform_apis : 생성된 라이브러리가 SDK가 아닌 플랫폼 API에 대해 빌드해야 하는 경우 true 로 설정되어야 하는 선택적 플래그입니다.

버전과 활성화된 백엔드의 각 조합에 대해 스텁 라이브러리가 생성됩니다. 특정 백엔드에 대한 스텁 라이브러리의 특정 버전을 참조하는 방법은 모듈 명명 ​​규칙 을 참조하십시오.

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 , longString 에 대해 지원되지만 필수는 아닙니다. Android 12에서는 사용자 정의 열거의 기본값도 지원됩니다. 기본값을 지정하지 않으면 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: ...,
    rust_libs: ["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

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 는 또한 version_with_info 속성을 업데이트하여 추가 versions_with_info 및 해당 버전에 대한 imports 를 반영합니다. 기본적으로 versions_with_infoimportsimports 필드에서 복사됩니다. 그러나 최신 안정 버전은 명시적 versions_with_info 이 없는 imports 오기에 대해 version_with_info의 가져오기에 지정됩니다. Versions_with_info 속성이 지정되면 빌드 시스템은 고정된 versions_with_info 간에 호환성 검사를 실행하고 ToT(Top of Tree)와 최신 고정된 버전 간에도 실행합니다.

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

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

  • 인터페이스 끝까지의 메소드(또는 명시적으로 정의된 새 직렬이 있는 메소드)
  • Parcelable의 끝에 있는 요소(각 요소에 대해 기본값을 추가해야 함)
  • 상수 값
  • Android 11에서 열거자는
  • Android 12에서 공용체 끝까지의 필드

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

릴리스를 위해 모든 인터페이스가 동결되었는지 테스트하려면 다음 환경 변수 세트를 사용하여 빌드할 수 있습니다.

  • AIDL_FROZEN_REL=true m ... - 빌드하려면 owner: 필드가 지정되지 않은 고정된 모든 안정적인 AIDL 인터페이스가 필요합니다.
  • AIDL_FROZEN_OWNERS="aosp test" - 빌드하려면 "aosp" 또는 "test"로 지정된 owner: 필드와 함께 고정된 모든 안정적인 AIDL 인터페이스가 필요합니다.

수입의 안정성

고정된 버전의 인터페이스에 대한 가져오기 버전 업데이트는 안정적인 AIDL 계층에서 이전 버전과 호환됩니다. 그러나 이를 업데이트하려면 이전 버전의 인터페이스를 사용하는 모든 서버와 클라이언트를 업데이트해야 하며 일부 응용 프로그램은 다른 버전의 유형을 혼합할 때 혼동될 수 있습니다. 일반적으로 유형 전용 또는 일반 패키지의 경우 IPC 트랜잭션에서 알 수 없는 유형을 처리하기 위해 코드를 이미 작성해야 하므로 안전합니다.

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

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

인터페이스 방법

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

  • cpp 백엔드는 ::android::UNKNOWN_TRANSACTION 을 가져옵니다.
  • ndk 백엔드는 STATUS_UNKNOWN_TRANSACTION 을 얻습니다.
  • java 백엔드는 API가 구현되지 않았다는 메시지와 함께 android.os.RemoteException 을 가져옵니다.

이를 처리하기 위한 전략은 버전 쿼리기본값 사용을 참조하세요.

소포

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

클라이언트는 서버가 정의된 필드가 있는 버전을 구현하고 있다는 것을 알지 못하는 한 서버가 새 필드를 사용할 것으로 기대해서는 안 됩니다( 버전 쿼리 참조).

열거형 및 상수

마찬가지로 클라이언트와 서버는 나중에 더 많이 추가될 수 있으므로 인식되지 않는 상수 값과 열거자를 적절하게 거부하거나 무시해야 합니다. 예를 들어, 서버는 알지 못하는 열거자를 수신할 때 중단되어서는 안 됩니다. 이를 무시하거나 클라이언트가 이 구현에서 지원되지 않는다는 것을 알 수 있도록 무언가를 반환해야 합니다.

노조

수신자가 오래되어 필드에 대해 알지 못하는 경우 새 필드가 포함된 공용체를 보내려는 시도는 실패합니다. 구현 시 새 필드와의 합집합을 볼 수 없습니다. 단방향 트랜잭션인 경우 실패가 무시됩니다. 그렇지 않으면 오류는 BAD_VALUE (C++ 또는 NDK 백엔드의 경우) 또는 IllegalArgumentException (Java 백엔드의 경우)입니다. 클라이언트가 새 필드에 대한 유니온 세트를 이전 서버로 보내거나 이전 클라이언트가 새 서버에서 유니온을 수신하는 경우 오류가 수신됩니다.

모듈 명명 ​​규칙

Android 11에서는 사용 설정된 버전과 백엔드의 각 조합에 대해 스텁 라이브러리 모듈이 자동으로 생성됩니다. 연결을 위해 특정 스텁 라이브러리 모듈을 참조하려면 aidl_interface 모듈의 이름을 사용하지 말고 스텁 라이브러리 모듈의 이름인 ifacename - version - backend 를 사용하십시오. 여기서

  • ifacename : aidl_interface 모듈의 이름
  • version 은 다음 중 하나입니다.
    • 고정 버전의 V version-number
    • V latest-frozen-version-number + 1 for the tip-of-tree(아직 동결) 버전
  • backend 는 다음 중 하나입니다.
    • Java 백엔드용 java ,
    • C++ 백엔드용 cpp ,
    • 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)

안드로이드 11과 비교했을 때,

  • 최신 안정 버전을 가리키는 foo- foo- backendfoo- V2 - backend 가 됩니다.
  • ToT 버전을 참조한 foo-unstable- backend 는 foo- 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 컴파일러는 안정적인 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();

ndk_platform ndk 백엔드의 예:

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 getInterfaceVersion()getInterfaceHash() 를 구현해야 합니다(복사/붙여넣기 실수를 방지하기 위해 IFoo 대신 super 가 사용됨 @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 등)가 클라이언트와 서버 간에 공유되기 때문입니다(예: 클래스가 부팅 클래스 경로에 있을 수 있음). 클래스가 공유되면 서버는 이전 버전의 인터페이스로 구축되었을 수도 있지만 최신 버전의 클래스에 대해 링크됩니다. 이 메타 인터페이스가 공유 클래스에서 구현되면 항상 최신 버전을 반환합니다. 그러나 위와 같이 메서드를 구현하면 인터페이스의 버전 번호가 서버 코드에 포함되므로( 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

자바의 예:

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 패키지(위에서 설명한 대로)를 만듭니다. 구조화뿐만 아니라 안정화하려면 버전도 지정해야 합니다. 자세한 내용은 버전 관리 인터페이스 를 참조하세요.