AIDL 스타일가이드

여기에 안내된 권장사항은 특히 API를 정의하거나 API 노출 영역과 상호 작용하는 데 AIDL을 사용하는 경우 인터페이스의 유연성에 중점을 두고 AIDL 인터페이스를 효율적으로 개발하기 위한 가이드로 기능합니다.

AIDL은 앱이 백그라운드 프로세스에서 다른 앱과 인터페이스하거나 시스템과 인터페이스해야 하는 경우에 API를 정의하는 데 사용할 수 있습니다. AIDL을 사용하여 앱의 프로그래밍 인터페이스를 개발하는 방법에 관한 자세한 내용은 Android 인터페이스 정의 언어(AIDL)를 참고하세요. 실제로 사용되는 AIDL의 예시를 보려면 HAL용 AIDL안정적 AIDL을 참고하세요.

버전 관리

AIDL API의 모든 하위 호환 스냅샷에는 대응되는 버전이 있습니다. 스냅샷을 작성하려면 m <module-name>-freeze-api를 실행합니다. API의 클라이언트 또는 서버가 릴리스될 때마다(예: 메인라인 트레인에서) 스냅샷을 찍고 새 버전을 만들어야 합니다. 시스템-벤더 API의 경우 연례 플랫폼 개정 과정에서 이 작업을 진행해야 합니다.

허용되는 변경 사항에 관한 자세한 내용은 버전 관리 인터페이스를 참고하세요.

API 디자인 가이드라인

일반

1. 모든 것 문서화

  • 모든 메서드의 시맨틱, 인수, 내장 예외 사용 방식, 서비스별 예외, 반환 값을 문서화합니다.
  • 모든 인터페이스의 시맨틱을 문서화합니다.
  • enum 및 상수의 시맨틱 의미를 문서화합니다.
  • 구현자가 명확하게 파악하기 어려운 내용이 있다면 모두 문서화합니다.
  • 도움이 될 만한 예제를 제공합니다.

2 표기법

형식에는 대문자 카멜 표기법을, 메서드, 필드, 인수에는 소문자 카멜 표기법을 사용합니다. 예를 들어, parcelable 형식의 경우 MyParcelable을, 인수의 경우 anArgument를 사용합니다. 약어 단어에는 약어 표기법을 사용합니다(NFC -> Nfc).

[-Wconst-name] enum 값과 상수는 각각 ENUM_VALUECONSTANT_NAME으로 사용합니다.

인터페이스

1. 이름 지정

[-Winterface-name] 인터페이스 이름은 I로 시작해야 합니다(예: IFoo).

2 id 기반 '객체'를 갖는 대규모 인터페이스 사용 지양

특정 API와 관련된 호출이 여러 개인 경우에는 대규모 인터페이스가 아닌 하위 인터페이스를 사용합니다. 이 방식에는 다음과 같은 이점이 있습니다. - 클라이언트/서버 코드를 보다 쉽게 이해할 수 있습니다. - 객체의 수명 주기가 단순해집니다. - 바인더의 수정 불가 특성을 활용할 수 있습니다.

권장되지 않음: id 기반 객체를 갖는 하나의 대규모 인터페이스

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

권장됨: 개별 하위 인터페이스

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. 일방향 메서드와 양방향 메서드를 혼합하여 사용하지 않기

[-Wmixed-oneway] 일방향 메서드와 일방향이 아닌 메서드를 혼합하여 사용하면 클라이언트와 서버가 스레딩 모델을 파악하는 데 어려움이 발생합니다. 구체적으로는, 특정 인터페이스의 클라이언트 코드를 읽을 때 각 메서드가 차단될지 여부를 일일이 살펴보아야 합니다.

4. 상태 코드 반환 지양

모든 AIDL 메서드에는 암시적 상태 반환 코드가 있으므로 메서드에서 상태 코드를 반환 값으로 사용하지 않아야 합니다. ServiceSpecificException 또는 EX_SERVICE_SPECIFIC을 참조하세요. 이러한 값은 관행적으로 AIDL 인터페이스에 상수로 정의되어 있습니다. 자세한 내용은 AIDL 백엔드의 오류 처리 섹션을 참고하세요.

5. 출력 매개변수로 배열을 사용하는 방식 지양

[-Wout-array] void foo(out String[] ret)와 같이 출력 매개변수로 배열을 사용하는 메서드는 클라이언트가 Java로 출력 배열 크기를 선언 및 할당해야 하기 때문에 서버에서 배열 출력의 크기를 선택할 수 없으므로 좋지 않은 방식입니다. 이 바람직하지 않은 동작은 Java에서 배열이 작동하는 방식(재할당되지 않음)으로 인해 발생합니다. 대신 String[] foo()와 같은 API를 사용하세요.

6. inout 매개변수 지양

[-Winout-parameter] inout 매개변수를 사용하면 in 매개변수도 out 매개변수처럼 보이기 때문에 클라이언트의 혼동을 유발할 수 있습니다.

7. 배열이 아닌 out/inout @nullable 매개변수 지양

[-Wout-nullable] Java 백엔드는 다른 백엔드와 달리 @nullable 주석을 처리하지 않기 때문에 out/inout @nullable T를 사용하면 여러 백엔드에서 일관되지 않은 동작이 발생할 수 있습니다. 예를 들어, Java가 아닌 백엔드는 out @nullable 매개변수를 null로 설정할 수 있지만(C++에서는 std::nullopt로 설정하는 등) Java 클라이언트를 이를 null로 읽을 수 없습니다.

구조화된 parcelable

1. 사용하기 적합한 경우

전송해야 하는 데이터 형식이 여러 개인 경우에는 구조화된 parcelable을 사용합니다.

또는 지금 당장은 데이터 형식이 하나이지만 앞으로 더 늘어날 것으로 예상되는 경우에도 구조화된 parcelable을 사용하세요. 예를 들어, String username을 사용하는 대신 다음과 같이 확장 가능한 parcelable을 사용하세요.

parcelable User {
    String username;
}

이렇게 하면 추후에 다음과 같이 확장할 수 있습니다.

parcelable User {
    String username;
    int id;
}

2 명시적으로 기본값 제공

[-Wexplicit-default, -Wenum-explicit-default] 필드에는 명시적인 기본값을 제공합니다.

구조화되지 않은 parcelable

1. 사용하기 적합한 경우

구조화되지 않은 parcelable은 현재 Java에서는 @JavaOnlyStableParcelable을 통해, NDK 백엔드에서는 @NdkOnlyStableParcelable을 통해 사용할 수 있습니다. 일반적으로 이들은 오래된 기존 parcelable로, 쉽게 구조화되지 않습니다.

상수와 enum

1. 비트필드에는 상수 필드 사용

비트필드에는 상수 필드를 사용해야 합니다(예: 인터페이스의 const int FOO = 3;).

2 enum은 폐쇄형 세트여야 함

enum은 폐쇄형 세트여야 합니다. 참고: enum 요소는 인터페이스 소유자만 추가할 수 있습니다. 벤더 또는 OEM이 이러한 필드를 확장해야 하는 경우 다른 메커니즘이 필요합니다. 가능한 경우 항상 벤더 기능을 업스트리밍하는 방식을 사용하세요. 단, 경우에 따라 맞춤 벤더 값이 허용될 수도 있습니다(이때 벤더는 이를 또는 AIDL 자체를 버전 관리하는 메커니즘을 보유해야 하며, 서로 상충하지 않아야 하고, 이러한 값을 서드 파티 앱에 노출해서는 안 됩니다).

3. 'NUM_ELEMENTS'와 같은 값 지양

enum에는 버전이 부여되므로 몇 개의 값이 있는지를 나타내는 값은 사용하지 않아야 합니다. C++에서는 enum_range<>를 사용하여 이 제한을 우회할 수 있습니다. Rust에서는 enum_values()를 사용하세요. Java에는 아직 해결 방법이 없습니다.

권장되지 않음: 숫자가 지정된 값 사용

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. 불필요한 접두사와 접미사 사용 지양

[-Wredundant-name] 상수와 열거자에 불필요하거나 반복적인 접두사와 접미사를 사용하지 않습니다.

권장되지 않음: 불필요한 접두사 사용

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

권장됨: enum에 직접 이름 지정

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] FileDescriptor를 AIDL 인터페이스의 인수 또는 반환 값으로 사용하는 방식은 지양해야 합니다. 특히 Java에서 AIDL을 구현하는 경우에 이렇게 하면 주의 깊게 처리하지 않는 한 파일 설명자가 누출될 수 있습니다. FileDescriptor를 받는 경우에는 더 이상 사용하지 않을 경우 수동으로 종료해야 합니다.

네이티브 백엔드의 경우에는 FileDescriptor가 자동으로 종료되는 unique_fd에 매핑되므로 괜찮습니다. 단, 백엔드 언어로 무엇을 사용하든, FileDescriptor를 사용하면 추후 백엔드 언어를 변경하기가 어려워지므로 사용하지 않는 것이 좋습니다.

대신 자동으로 종료되는 ParcelFileDescriptor를 사용하세요.

변수 단위

참고 문서를 보지 않고도 단위를 알아볼 수 있도록 이름에 변수 단위를 포함합니다.

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

타임스탬프는 참조를 나타내야 함

타임스탬프(뿐 아니라 모든 단위)는 단위와 참조 지점을 명확히 나타내야 합니다.

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;