AIDL 백엔드

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

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

백엔드 언어 API 노출 영역 빌드 시스템
Java Java 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의 Java/CPP 백엔드가 사용되며 해당하는 AIDL 파일을 사용하는 클래스가 자동으로 모듈에 추가됩니다. 빌드 시스템 모듈에 있는 AIDL 파일의 루트 경로를 빌드 시스템에 알려주는 local_include_dirs와 같은 옵션은 aidl: 그룹 아래의 빌드 시스템 모듈에서 지정할 수 있습니다. Rust 백엔드는 Rust에서만 사용할 수 있습니다. rust_ 모듈은 AIDL 파일이 소스 파일로 지정되지 않는다는 점에서 다르게 처리됩니다. 대신 aidl_interface 모듈은 연결될 수 있는 <aidl_interface name>-rust라는 rustlib를 생성합니다. 자세한 내용은 Rust AIDL 예를 참고하세요.

aidl_interface

이 빌드 시스템에 사용되는 유형은 구조화되어야 합니다. 구조화되기 위해 parcelable은 직접 필드를 포함해야 하고 타겟 언어에 직접 정의된 유형 선언이어서는 안 됩니다. 구조화된 AIDL과 안정적 AIDL의 차이에 대해 알아보려면 구조화된 AIDL과 안정적 AIDL 비교를 참고하세요.

유형

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

Java/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 13 이상에서 T는 인터페이스 유형을 포함하여 원시 유형이 아닌 유형(배열 제외)이 될 수 있습니다. AOSP에서는 T[]와 같은 배열 유형을 사용하도록 권장합니다. 모든 백엔드에서 작동하기 때문입니다.

3. NDK 백엔드는 List<T>를 지원합니다. 여기서 TString이나 ParcelFileDescriptor, parcelable 중 하나입니다. Android 13 이상에서 T는 원시 유형이 아닌 유형(배열 제외)이 될 수 있습니다.

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

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

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

방향(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 허용 여부

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

맞춤 parcelable

맞춤 parcelable은 타겟 백엔드에서 수동으로 구현되는 parcelable입니다. 변경할 수 없는 기존 맞춤 parcelable의 다른 언어에 지원을 추가하려는 경우에만 맞춤 parcelable을 사용하세요.

AIDL이 알 수 있도록 맞춤 parcelable을 선언하려면 AIDL parcelable 선언은 다음과 같아야 합니다.

    package my.pack.age;
    parcelable Foo;

이는 기본적으로 Java parcelable을 선언하며 여기서 my.pack.age.FooParcelable 인터페이스를 구현하는 Java 클래스입니다.

AIDL에서 맞춤 CPP 백엔드 parcelable을 선언하려면 cpp_header를 사용하세요.

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

my/pack/age/Foo.h에서의 C++ 구현은 다음과 같습니다.

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

AIDL에서 맞춤 NDK parcelable을 선언하려면 ndk_header를 사용하세요.

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

android/pack/age/Foo.h에서의 NDK 구현은 다음과 같습니다.

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

그런 다음 이 parcelable을 AIDL 파일의 유형으로 사용할 수 있지만 AIDL에서 parcelable이 생성되지는 않습니다. union에서 사용할 수 있도록 CPP/NDK 백엔드 맞춤 parcelable에 <== 연산자를 제공합니다.

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

기본값

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

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

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

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

오류 처리

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

오류가 있는 출력 매개변수

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

사용할 오류 값

내장된 여러 오류 값은 모든 AIDL 인터페이스에서 사용할 수 있지만, 일부는 특수한 방식으로 처리됩니다. 예를 들어 EX_UNSUPPORTED_OPERATIONEX_ILLEGAL_ARGUMENT는 오류 조건을 설명할 때 사용할 수 있지만, EX_TRANSACTION_FAILED는 사용하면 안 됩니다. 이 오류 값은 기본 인프라에서 특별하게 취급하기 때문입니다. 내장된 값에 관한 자세한 내용은 백엔드별 정의를 확인하세요.

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

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

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

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

백엔드 정의
Java 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 또는 union이든 상관없이 다음을 통해 유형을 가져올 수 있습니다. Java의 경우:

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;

Java에 중첩된 유형을 가져올 수 있지만 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();
    }

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

    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(())
        }
    }

또는 비동기 Rust의 경우:

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

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

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

서비스 등록 및 가져오기

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

Java의 경우:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("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);
    // return if service is started now
    status_t err = checkService<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
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("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(ndk::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()
}

단일 스레드 런타임을 사용하는 비동기 Rust 백엔드에서:

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

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks will run on this thread.
    std::future::pending().await
}

다른 옵션과 다른 한 가지 중요한 차이점은 비동기 Rust와 단일 스레드 런타임을 사용할 때 join_thread_pool을 호출하지 않는다는 것입니다. 생성된 작업을 실행할 수 있는 스레드를 Tokio에 제공해야 하기 때문입니다. 이 예에서는 기본 스레드가 이 목적에 부합합니다. tokio::spawn을 사용하여 생성된 작업은 기본 스레드에서 실행됩니다.

다중 스레드 런타임을 사용하는 비동기 Rust 백엔드에서:

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

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

다중 스레드 Tokio 런타임을 사용하면 생성된 작업이 기본 스레드에서 실행되지 않습니다. 따라서 기본 스레드가 유휴 상태로 있지 않도록 기본 스레드에서 join_thread_pool을 호출하는 것이 좋습니다. 호출을 block_in_place에 래핑하여 비동기 컨텍스트를 종료해야 합니다.

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

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

호출자 정보

커널 바인더 호출을 수신하면 호출자 정보를 여러 API에서 사용할 수 있습니다. PID(프로세스 ID)는 트랜잭션을 전송하는 프로세스의 Linux 프로세스 ID를 나타냅니다. UID(사용자 ID)는 Linux 사용자 ID를 나타냅니다. 일방향 통화를 수신할 때 호출 PID는 0입니다. 바인더 트랜잭션 컨텍스트 외부에 있을 때 이러한 함수는 현재 프로세스의 PID 및 UID를 반환합니다.

Java 백엔드의 경우:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

CPP 백엔드의 경우:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

NDK 백엔드의 경우:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

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

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

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

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

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

Java 백엔드의 경우:

    @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<()>

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

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

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

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

CPP 백엔드의 경우:

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

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

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

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

    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()

열거형 범위

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

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

CPP 백엔드의 경우:

    ::android::enum_range<MyEnum>()

NDK 백엔드의 경우:

   ::ndk::enum_range<MyEnum>()

Rust 백엔드의 경우:

    MyEnum::enum_values()

스레드 관리

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

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

    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();

비동기 Rust 백엔드를 사용하면 바인더와 Tokio라는 두 가지 스레드 풀이 필요합니다. 즉, 비동기 Rust를 사용하는 애플리케이션은 특히 join_thread_pool 사용에 있어서 특별히 고려해야 할 사항이 있습니다. 자세한 내용은 서비스 등록에 관한 섹션을 참고하세요.

예약된 이름

C++, Java, Rust는 일부 이름을 키워드로 사용하거나 언어별로 사용하기 위해 예약합니다. AIDL은 언어 규칙에 따라 제한을 적용하지 않지만, 예약된 이름과 일치하는 필드 또는 유형 이름을 사용하면 C++ 또는 Java 컴파일에 실패할 수 있습니다. Rust의 경우 필드 또는 유형은 r# 접두어로 액세스할 수 있는 '원시 식별자' 문법을 사용하여 이름이 바뀝니다.

사람이 이해하기 쉽지 않은 결합이나 명백한 컴파일 실패를 방지하기 위해 가능하면 AIDL 정의에 예약된 이름을 사용하지 않는 것이 좋습니다.

AIDL 정의에 이미 예약된 이름이 있는 경우 프로토콜 호환성을 유지하면서 필드 이름을 안전하게 바꿀 수 있습니다. 계속 빌드하려면 코드를 업데이트해야 할 수도 있지만 이미 빌드된 프로그램은 계속 상호 운용됩니다.

피해야 할 이름: * C++ 키워드 * Java 키워드 * Rust 키워드