Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

AIDL 백엔드

AIDL 백엔드는 스텁 코드 생성 대상입니다. AIDL 파일은 항상 정해진 런타임 동안 특정 언어로 사용합니다. 상황에 따라 다른 AIDL 백엔드를 사용해야 합니다.

AIDL에는 다음 백엔드가 있습니다.

백엔드 언어 API 노출 영역 빌드 시스템
자바 자바 SDK/SystemApi(안정적*) 모두
NDK C++ libbinder_ndk(안정적*) aidl_interface
CPP C++ libbinder(안정적이지 않음) 모두
Rust Rust libbinder_rs(안정적이지 않음) aidl_interface
  • 이러한 API 노출 영역은 안정적이지만 서비스 관리용 API와 같은 많은 API는 대부분 내부 플랫폼용으로 예약되어 있으며 앱에서 사용할 수 없습니다. 앱에서 AIDL을 사용하는 방법에 관한 자세한 내용은 개발자 문서를 참고하세요.
  • Rust 백엔드는 Android 12에서 도입되었고 NDK 백엔드는 Android 10부터 사용할 수 있습니다.
  • Rust 크레이트는 libbinder_ndk에 기반합니다. APEX 사용자는 시스템 측의 다른 사용자와 동일한 방식으로 바인더 크레이트를 사용합니다. Rust 부분은 APEX에 번들로 포함되어 번들 안으로 전송됩니다. 이는 시스템 파티션의 libbinder_ndk.so에 따라 다릅니다.

빌드 시스템

백엔드에 따라 AIDL을 스텁 코드로 컴파일하는 방법에는 두 가지가 있습니다. 빌드 시스템에 관한 자세한 내용은 Soong 모듈 참조를 참고하세요.

핵심 빌드 시스템

모든 cc_ 또는 java_ Android.bp 모듈(또는 두 코드의 Android.mk에 해당하는 모듈)에서 .aidl 파일은 소스 파일로 지정할 수 있습니다. 이 경우 NDK 백엔드가 아닌 AIDL의 자바/CPP 백엔드가 사용되며 해당하는 AIDL 파일을 사용하는 클래스가 자동으로 모듈에 추가됩니다. 빌드 시스템 모듈에 있는 AIDL 파일의 루트 경로를 빌드 시스템에 알려주는 local_include_dirs와 같은 옵션은 aidl: 그룹 아래의 빌드 시스템 모듈에서 지정할 수 있습니다. Rust 백엔드는 Rust에서만 사용할 수 있습니다. rust_ 모듈은 AIDL 파일이 소스 파일로 지정되지 않는다는 점에서 다르게 처리됩니다. 대신 aidl_interface 모듈은 연결될 수 있는 <aidl_interface name>-rust라는 rustlib를 생성합니다. 자세한 내용은 Rust AIDL 예를 참고하세요.

aidl_interface

안정적 AIDL을 참고하세요. 이 빌드 시스템에 사용되는 유형은 구조화되어야 합니다. 즉, AIDL로 직접 표현되어야 합니다. 맞춤 parcelable은 사용할 수 없습니다.

유형

aidl 컴파일러를 유형의 참조 구현으로 간주할 수 있습니다. 인터페이스를 만들 때는 aidl --lang=<backend> ...를 호출하여 결과 인터페이스 파일을 확인합니다. aidl_interface 모듈을 사용하면 out/soong/.intermediates/<path to module>/에서 출력을 확인할 수 있습니다.

자바/AIDL 유형 C++ 유형 NDK 유형 Rust 유형
boolean bool bool bool
byte int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string String
android.os.Parcelable android::Parcelable 해당 사항 없음 해당 사항 없음
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> In: &T
Out: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 In: &[u8]
Out: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 In: &[T]4
Out: Vec<T>
FileDescriptor android::base::unique_fd 해당 사항 없음 binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
interface type (T) android::sp<T> std::shared_ptr<T> binder::Strong
parcelable type (T) T T T
union type (T)5 T T T
T[N]6 std::array<T, N> std::array<T, N> [T; N]

1. Android 12 이상에서는 호환성을 위해 byte 배열에서 int8_t 대신 uint8_t를 사용합니다.

2. C++ 백엔드는 List<T>를 지원합니다. 여기서 TString이나 IBinder, ParcelFileDescriptor, parcelable 중 하나입니다. Android T(AOSP 실험용) 이상에서는 T에 인터페이스 유형을 포함하여 비원시 유형 중 어느 것이든 올 수 있습니다(배열 제외). AOSP에서는 T[]와 같은 배열 유형을 사용하도록 권장합니다. 모든 백엔드에서 작동하기 때문입니다.

3. NDK 백엔드는 List<T>를 지원합니다. 여기서 TString이나 ParcelFileDescriptor, parcelable 중 하나입니다. Android T(AOSP 실험용) 이상에서는 T에 비원시 유형 중 어느 것이든 올 수 있습니다(배열 제외).

4. Rust 코드에서는 유형이 입력(인수)인지 출력(반환된 값)인지에 따라 다르게 전달됩니다.

5. Union 유형은 Android 12 이상에서 지원됩니다.

6. Android T(AOSP 실험용) 이상에서는 고정 크기 배열이 지원됩니다. 고정 크기 배열은 차원을 여러 개 가질 수 있습니다(예: int[3][4]). 자바 백엔드에서 고정 크기 배열은 배열 유형으로 표시됩니다.

방향(in/out/inout)

함수의 인수 유형을 지정할 때 in이나 out, inout으로 지정할 수 있습니다. 이 인수는 IPC 호출을 위해 전달되는 방향 정보를 제어합니다. in은 기본 방향이며 호출자에서 피호출자로 데이터가 전달되었음을 나타냅니다. out은 데이터가 피호출자에서 호출자로 전달되었음을 의미합니다. inout은 이 둘의 조합입니다. 하지만, Android팀은 인수 지정자 inout을 사용하지 않는 것을 권장합니다. 버전이 지정된 인터페이스 및 이전 피호출자와 함께 inout을 사용하면 호출자에만 있는 추가 필드는 기본값으로 재설정됩니다. Rust와 관련하여 일반 inout 유형이 &mut Vec<T>를 수신하고 목록 inout 유형이 &mut Vec<T>를 수신합니다.

UTF8/UTF16

CPP 백엔드를 사용하면 문자열을 utf-8이나 utf-16 중에서 선택할 수 있습니다. 문자열을 utf-8로 자동 변환하려면 AIDL에서 문자열을 @utf8InCpp String으로 선언하세요. NDK 및 Rust 백엔드는 항상 utf-8 문자열을 사용합니다. utf8InCpp 주석에 관한 자세한 내용은 AIDL의 주석을 참고하세요.

null 허용 여부

자바 백엔드에서 null일 수 있는 유형에 @nullable로 주석을 달아 null 값을 CPP 및 NDK 백엔드에 노출할 수 있습니다. Rust 백엔드에서 이러한 @nullable 유형은 Option<T>로 노출됩니다. 네이티브 서버는 기본적으로 null 값을 거부합니다. 유일한 예외는 interfaceIBinder 유형으로, NDK 읽기 및 CPP/NDK 쓰기의 경우 항상 null일 수 있습니다. nullable 주석에 관한 자세한 내용은 AIDL의 주석을 참고하세요.

맞춤 parcelable

핵심 빌드 시스템의 C++ 및 자바 백엔드에서 parcelable을 선언할 수 있습니다. 이 parcelable은 C++ 또는 자바의 타겟 백엔드에서 수동으로 구현됩니다.

    package my.package;
    parcelable Foo;

또는 다음의 C++ 헤더 선언을 사용합니다.

    package my.package;
    parcelable Foo cpp_header "my/package/Foo.h";

그런 다음 이 parcelable을 AIDL 파일의 유형으로 사용할 수 있지만 AIDL에서 parcelable이 생성되지는 않습니다.

Rust는 맞춤 parcelable을 지원하지 않습니다.

기본값

구조화된 parcelable은 이러한 유형의 원시, String, 배열에 관한 필드별 기본값을 선언할 수 있습니다.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

자바 백엔드에서 기본값이 누락되면 필드는 원시 유형의 경우 0 값으로, 원시가 아닌 유형의 경우 null로 초기화됩니다.

다른 백엔드에서는 기본값이 정의되지 않은 경우 필드가 기본 초기화 값으로 초기화됩니다. 예를 들어 C++ 백엔드에서 String 필드는 빈 문자열로 초기화되고 List<T> 필드는 빈 vector<T>로 초기화됩니다. @nullable 필드는 null 값 필드로 초기화됩니다.

오류 처리

Android OS에는 오류를 보고할 때 사용할 서비스 오류 유형이 내장되어 있습니다. 이러한 오류 유형은 바인더에 사용되며, 바인더 인터페이스를 구현하는 모든 서비스에도 사용할 수 있습니다. 이러한 사용은 AIDL 정의에 잘 설명되어 있으며 사용자 정의 상태나 반환 유형이 필요하지 않습니다.

AIDL 함수가 오류를 보고할 때 함수는 출력 매개변수를 초기화하거나 수정할 수 없습니다. 특히 트랜잭션 자체 처리 중에 발생하는 것이 아니라 unparcel 중에 오류가 발생하면 출력 매개변수가 수정될 수 있습니다. 일반적으로 AIDL 함수에서 오류가 발생할 때 반환 값(일부 백엔드에서 out 매개변수처럼 작동함)은 물론 모든 inoutout 매개변수가 무기한 상태에 있는 것으로 간주되어야 합니다.

내장된 오류 유형에 포함되지 않는 추가 오류 값이 AIDL 인터페이스에 필요한 경우 사용자가 정의한 서비스별 오류 값을 포함할 수 있는 특정 서비스별 내장 오류를 사용할 수도 있습니다. 이러한 서비스별 오류는 일반적으로 AIDL 인터페이스에서 const int 또는 int 지원 enum으로 정의되며 바인더에서 파싱하지 않습니다.

자바에서 오류는 android.os.RemoteException 같은 예외에 매핑됩니다. 서비스별 예외의 경우 자바는 사용자 정의 오류와 함께 android.os.ServiceSpecificException을 사용합니다.

Android의 네이티브 코드는 예외를 사용하지 않습니다. CPP 백엔드는 android::binder::Status를 사용합니다. NDK 백엔드는 ndk::ScopedAStatus를 사용합니다. AIDL에서 생성된 모든 메서드는 이 중 하나를 반환하여 메서드의 상태를 나타냅니다. Rust 백엔드는 NDK와 같은 예외 코드 값을 사용하지만 사용자에게 전달하기 전에 네이티브 Rust 오류(StatusCode, ExceptionCode)로 변환합니다. 서비스별 오류의 경우 반환된 Status 또는 ScopedAStatus는 사용자 정의 오류와 함께 EX_SERVICE_SPECIFIC을 사용합니다.

내장된 오류 유형은 다음 파일에서 확인할 수 있습니다.

백엔드 정의
자바 android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

다양한 백엔드 사용

이 안내는 Android 플랫폼 코드에만 적용됩니다. 이 예에서는 정의된 유형인 my.package.IFoo를 사용합니다. Rust 백엔드 사용 방법에 관한 안내는 Android Rust 패턴 페이지에서 Rust AIDL 예를 참고하세요.

유형 가져오기

정의된 유형이 인터페이스이든, parcelable 또는 유니온이든 상관없이 다음을 통해 유형을 가져올 수 있습니다. 자바의 경우:

    import my.package.IFoo;

또는 CPP 백엔드의 경우:

    #include <my/package/IFoo.h>

또는 NDK 백엔드의 경우(추가 aidl 네임스페이스에 유의):

    #include <aidl/my/package/IFoo.h>

또는 Rust 백엔드의 경우:

    use my_package::aidl::my::package::IFoo;

자바에 중첩된 유형을 가져올 수 있지만 CPP/NDK 백엔드에 관련 루트 유형의 헤더를 포함해야 합니다. 예를 들어 my/package/IFoo.aidl에 정의된 중첩 유형 Bar(IFoo는 파일의 루트 유형임)를 가져올 경우 CPP 백엔드에 <my/package/IFoo.h>(또는 NDK 백엔드의 경우 <aidl/my/package/IFoo.h>)를 포함해야 합니다.

서비스 구현

서비스를 구현하려면 네이티브 스텁 클래스에서 상속해야 합니다. 네이티브 스텁 클래스는 바인더 드라이버에서 명령어를 읽고 개발자가 구현하는 메서드를 실행합니다. 다음과 같은 AIDL 파일이 있다고 가정해 보겠습니다.

    package my.package;
    interface IFoo {
        int doFoo();
    }

다음과 같이 네이티브 스텁 클래스에서 확장해야 합니다. 자바의 경우:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

CPP 백엔드의 경우:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

NDK 백엔드의 경우(추가 aidl 네임스페이스에 유의):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

Rust 백엔드의 경우:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

서비스 등록 및 가져오기

Android 플랫폼의 서비스는 일반적으로 servicemanager 프로세스로 등록됩니다. 아래 API 외에도 일부 API는 서비스를 확인합니다(서비스를 사용할 수 없으면 즉시 반환됨). 자세한 내용은 해당하는 servicemanager 인터페이스를 확인하세요. 서비스 등록 및 가져오기는 Android 플랫폼에 컴파일하는 경우에만 실행할 수 있습니다.

자바의 경우:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

CPP 백엔드의 경우:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // getting
    status_t err = getService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

NDK 백엔드의 경우(추가 aidl 네임스페이스에 유의):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));

Rust 백엔드의 경우:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

바인더를 호스팅하는 서비스가 종료될 때 알림을 받도록 요청할 수 있습니다. 이렇게 하면 콜백 프록시 유출을 방지하거나 오류를 복구하는 데 도움이 됩니다. 바인더 프록시 객체에서 이러한 호출을 실행합니다.

  • 자바에서는 android.os.IBinder::linkToDeath를 사용합니다.
  • CPP 백엔드에서는 android::IBinder::linkToDeath를 사용합니다.
  • NDK 백엔드에서는 AIBinder_linkToDeath를 사용합니다.
  • Rust 백엔드에서는 DeathRecipient 객체를 만들고 my_binder.link_to_death(&mut my_death_recipient)를 호출합니다. DeathRecipient가 콜백을 소유하므로 알림을 수신하려면 이 객체를 활성 상태로 유지해야 합니다.

서비스 관련 버그 신고 및 디버깅 API

버그 신고가 실행되면(예: adb bugreport 사용) 시스템 전체에서 다양한 문제를 디버깅하는 데 도움이 될 정보를 수집합니다. AIDL 서비스의 경우 버그 신고는 서비스 관리자로 등록된 모든 서비스에서 바이너리 dumpsys를 사용하여 정보를 버그 신고에 덤프합니다. 명령줄에서 dumpsys를 사용하여 dumpsys SERVICE [ARGS]가 포함된 서비스에서 정보를 가져오는 방법도 있습니다. C++ 및 자바 백엔드에서는 addService의 추가 인수를 사용하여 서비스가 덤프되는 순서를 제어할 수 있습니다. dumpsys --pid SERVICE를 사용하여 디버깅 중에 서비스의 PID를 가져올 수도 있습니다.

서비스에 맞춤 출력을 추가하려면 AIDL 파일에 정의된 다른 IPC 메서드를 구현하는 것처럼 서버 객체의 dump 메서드를 재정의하면 됩니다. 이렇게 할 때 앱 권한 android.permission.DUMP로의 덤프 또는 특정 UID로의 덤프를 제한해야 합니다.

자바 백엔드의 경우:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

CPP 백엔드의 경우:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

NDK 백엔드의 경우:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

Rust 백엔드에서는 인터페이스를 구현할 때 기본값을 허용하지 않고 다음을 지정합니다.

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

동적으로 인터페이스 설명어 가져오기

인터페이스 설명자는 인터페이스 유형을 식별합니다. 디버깅할 때 또는 알 수 없는 바인더가 있을 때 유용합니다.

자바에서는 다음과 같은 코드로 인터페이스 설명자를 가져올 수 있습니다.

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

CPP 백엔드의 경우:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

NDK 및 Rust 백엔드에서는 이 기능을 지원하지 않습니다.

정적으로 인터페이스 설명어 가져오기

@VintfStability 서비스를 등록할 때와 같이 인터페이스 설명자가 무엇인지 정적으로 알아내야 하는 경우가 있습니다. 다음과 같은 코드를 추가하여 설명자를 가져올 수 있습니다. 자바의 경우:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

CPP 백엔드의 경우:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

NDK 백엔드의 경우(추가 aidl 네임스페이스에 유의):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

Rust 백엔드의 경우:

    aidl::my::package::BnFoo::get_descriptor()

열거형 범위

네이티브 백엔드에서는 열거형에 사용 가능한 값을 반복할 수 있습니다. 코드 크기 고려사항으로 인해 이 기능은 현재 자바에서 지원되지 않습니다.

AIDL에 정의된 열거형 MyEnum의 경우 반복은 다음과 같이 제공됩니다.

CPP 백엔드의 경우:

    ::android::enum_range<MyEnum>()

NDK 백엔드의 경우:

   ::ndk::enum_range<MyEnum>()

Rust 백엔드의 경우:

    MyEnum::enum_range()

스레드 관리

한 프로세스의 모든 libbinder 인스턴스는 스레드 풀 1개를 유지합니다. 대부분의 사용 사례에서 스레드 풀은 단 1개여야 하고 모든 백엔드에서 이를 공유합니다. 단, 공급업체 코드에서 /dev/vndbinder와 통신하기 위해 또 다른 libbinder 사본을 로드할 수 있는 경우는 예외입니다. 이 경우 별도의 바인더 노드에 있으므로 스레드 풀이 공유되지 않습니다.

자바 백엔드의 경우 스레드 풀이 이미 시작되었으므로 다음과 같이 크기만 커질 수 있습니다.

    BinderInternal.setMaxThreads(<new larger value>);

CPP 백엔드의 경우 다음 작업을 실행할 수 있습니다.

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

마찬가지로 NDK 백엔드의 경우 다음 작업을 실행할 수 있습니다.

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

Rust 백엔드의 경우:

    binder::ProcessState::start_thread_pool();
    binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
    binder::ProcessState::join_thread_pool();