Android API 가이드라인

이 페이지는 개발자가 API 검토에서 API Council이 시행하는 일반적인 원칙을 이해할 수 있도록 안내하기 위한 것입니다.

API를 작성할 때 이러한 가이드라인을 따르는 것 외에도 개발자는 이러한 규칙 중 다수를 API에 대해 실행하는 검사에서 인코딩하는 API Lint 도구를 실행해야 합니다.

이것은 해당 Lint 도구에서 준수하는 규칙에 관한 가이드와 더불어 높은 정확도로 해당 도구에 코딩할 수 없는 규칙에 관한 일반적인 조언이라고 생각하면 됩니다.

API Lint 도구

API Lint는 Metalava 정적 분석 도구에 통합되어 CI의 유효성 검사 중에 자동으로 실행됩니다. m checkapi를 사용하여 로컬 플랫폼 체크아웃에서 수동으로 실행하거나 ./gradlew :path:to:project:checkApi를 사용하여 로컬 AndroidX 체크아웃에서 실행할 수 있습니다.

API 규칙

Android 플랫폼과 많은 Jetpack 라이브러리는 이 가이드라인이 만들어지기 전에 존재했으며 이 페이지의 뒷부분에 명시된 정책은 Android 생태계의 요구사항을 충족하기 위해 지속적으로 발전하고 있습니다.

따라서 일부 기존 API는 가이드라인을 따르지 않을 수 있습니다. 다른 경우에는 새로운 API가 가이드라인을 엄격하게 준수하기보다는 기존 API와 일관성을 유지하는 것이 앱 개발자에게 더 나은 사용자 환경을 제공할 수 있습니다.

판단을 내리고 해결해야 하는 API에 관한 어려운 질문이나 업데이트해야 하는 가이드라인이 있는 경우 API Council에 문의하세요.

API 기본사항

이 카테고리는 Android API의 핵심 측면과 관련이 있습니다.

모든 API를 구현해야 합니다.

API의 잠재고객 (예: 공개 또는 @SystemApi)과 관계없이 API로 병합되거나 노출될 때는 모든 API 서페이스를 구현해야 합니다. 나중에 구현될 API 스텁을 병합하지 마세요.

구현이 없는 API 노출 영역에는 여러 문제가 있습니다.

  • 적절하거나 완전한 표면이 노출되었다고 보장할 수 없습니다. 클라이언트가 API를 테스트하거나 사용하기 전에는 클라이언트가 기능을 사용할 수 있는 적절한 API를 보유하고 있는지 확인할 방법이 없습니다.
  • 구현이 없는 API는 개발자 프리뷰에서 테스트할 수 없습니다.
  • 구현이 없는 API는 CTS에서 테스트할 수 없습니다.

모든 API를 테스트해야 합니다.

이는 플랫폼 CTS 요구사항, AndroidX 정책, API를 구현해야 한다는 일반적인 아이디어와 일치합니다.

API 노출 영역을 테스트하면 API 노출 영역을 사용할 수 있고 예상 사용 사례를 해결했다는 기본 보장이 제공됩니다. 존재 여부 테스트만으로는 충분하지 않습니다. API 자체의 동작을 테스트해야 합니다.

새 API를 추가하는 변경사항에는 동일한 CL 또는 Gerrit 주제에 상응하는 테스트가 포함되어야 합니다.

API는 테스트 가능해야 합니다. '앱 개발자가 API를 사용하는 코드를 어떻게 테스트하나요?'라는 질문에 대답할 수 있어야 합니다.

모든 API는 문서화되어야 합니다.

문서는 API 유용성의 핵심 부분입니다. API 노출 영역의 문법은 명확해 보일 수 있지만 새 클라이언트는 API의 시맨틱스, 동작 또는 컨텍스트를 이해하지 못합니다.

생성된 모든 API는 가이드라인을 준수해야 합니다.

도구로 생성된 API는 손으로 작성한 코드와 동일한 API 가이드라인을 따라야 합니다.

API 생성에 권장되지 않는 도구:

  • AutoValue: AutoValue 작동 방식으로는 최종 값 클래스나 최종 빌더를 구현할 방법이 없는 등 다양한 방식으로 가이드라인을 위반합니다.

코드 스타일

이 카테고리는 개발자가 특히 공개 API를 작성할 때 사용해야 하는 일반적인 코드 스타일과 관련이 있습니다.

별도로 명시된 경우를 제외하고 표준 코딩 규칙을 따릅니다.

Android 코딩 규칙은 외부 기여자를 위해 여기에 문서화되어 있습니다.

https://source.android.com/source/code-style.html

전반적으로 표준 Java 및 Kotlin 코딩 규칙을 따르는 경향이 있습니다.

메서드 이름에서 약어는 대문자로 표시하면 안 됩니다.

예를 들어 메서드 이름은 runCTSTests이 아닌 runCtsTests이어야 합니다.

이름은 Impl로 끝나면 안 됩니다.

이렇게 하면 구현 세부정보가 노출되므로 피하세요.

클래스

이 섹션에서는 클래스, 인터페이스, 상속에 관한 규칙을 설명합니다.

적절한 기본 클래스에서 새 공개 클래스 상속

상속은 적절하지 않을 수 있는 API 요소를 하위 클래스에 노출합니다. 예를 들어 FrameLayout의 새로운 공개 하위 클래스는 FrameLayout에 새로운 동작과 API 요소가 추가된 것과 같습니다. 상속된 API가 사용 사례에 적합하지 않으면 트리에서 더 위쪽에 있는 클래스(예: ViewGroup 또는 View)에서 상속하세요.

기본 클래스의 메서드를 재정의하여 UnsupportedOperationException를 발생시키고 싶다면 사용 중인 기본 클래스를 다시 고려하세요.

기본 컬렉션 클래스 사용

컬렉션을 인수로 사용하든 값으로 반환하든 항상 특정 구현 (예: ArrayList<Foo> 대신 List<Foo> 반환)보다 기본 클래스를 선호하세요.

API에 적절한 제약 조건을 표현하는 기본 클래스를 사용합니다. 예를 들어 컬렉션이 정렬되어야 하는 API에는 List를 사용하고 컬렉션이 고유한 요소로 구성되어야 하는 API에는 Set를 사용합니다.

Kotlin에서는 변경 불가능한 컬렉션을 사용하는 것이 좋습니다. 자세한 내용은 컬렉션 변경 가능성을 참고하세요.

추상 클래스와 인터페이스 비교

Java 8에서는 기본 인터페이스 메서드 지원이 추가되어 API 설계자가 바이너리 호환성을 유지하면서 인터페이스에 메서드를 추가할 수 있습니다. 플랫폼 코드와 모든 Jetpack 라이브러리는 Java 8 이상을 타겟팅해야 합니다.

기본 구현이 상태 비저장인 경우 API 디자이너는 추상 클래스보다 인터페이스를 선호해야 합니다. 즉, 기본 인터페이스 메서드는 다른 인터페이스 메서드에 대한 호출로 구현할 수 있습니다.

기본 구현에 생성자나 내부 상태가 필요한 경우 추상 클래스를 사용해야 합니다.

두 경우 모두 API 디자이너는 람다로 사용을 간소화하기 위해 단일 메서드를 추상으로 남겨둘 수 있습니다.

public interface AnimationEndCallback {
  // Always called, must be implemented.
  public void onFinished(Animation anim);
  // Optional callbacks.
  public default void onStopped(Animation anim) { }
  public default void onCanceled(Animation anim) { }
}

클래스 이름은 확장하는 항목을 반영해야 합니다.

예를 들어 Service를 확장하는 클래스의 이름은 명확성을 위해 FooService로 지정해야 합니다.

public class IntentHelper extends Service {}
public class IntentService extends Service {}

일반 접미사

유틸리티 메서드 모음에 Helper, Util과 같은 일반 클래스 이름 접미사를 사용하지 마세요. 대신 연결된 클래스나 Kotlin 확장 함수에 직접 메서드를 넣으세요.

메서드가 여러 클래스를 연결하는 경우 포함된 클래스에 클래스의 기능을 설명하는 의미 있는 이름을 지정하세요.

매우 제한적인 경우 Helper 접미사를 사용하는 것이 적절할 수 있습니다.

  • 기본 동작의 구성에 사용됩니다.
  • 기존 동작을 새 클래스에 위임하는 것이 포함될 수 있음
  • 지속 상태가 필요할 수 있음
  • 일반적으로 View가 포함됩니다.

예를 들어 백포팅 도움말에 View와 연결된 상태를 유지하고 View에서 여러 메서드를 호출하여 백포트를 설치해야 하는 경우 TooltipHelper는 허용되는 클래스 이름입니다.

IDL 생성 코드를 공개 API로 직접 노출하지 마세요.

IDL 생성 코드를 구현 세부정보로 유지합니다. 여기에는 protobuf, 소켓, FlatBuffers 또는 기타 비Java, 비NDK API 표면이 포함됩니다. 하지만 Android의 IDL은 대부분 AIDL에 있으므로 이 페이지에서는 AIDL에 중점을 둡니다.

생성된 AIDL 클래스는 API 스타일 가이드 요구사항을 충족하지 않으며 (예: 오버로딩을 사용할 수 없음) AIDL 도구는 언어 API 호환성을 유지하도록 명시적으로 설계되지 않았으므로 공개 API에 삽입할 수 없습니다.

대신 초기에는 얕은 래퍼라도 AIDL 인터페이스 위에 공개 API 레이어를 추가하세요.

바인더 인터페이스

Binder 인터페이스가 구현 세부정보인 경우 향후 자유롭게 변경할 수 있으며, 공개 레이어를 통해 필요한 하위 호환성을 유지할 수 있습니다. 예를 들어 내부 호출에 새 인수를 추가하거나 일괄 처리 또는 스트리밍을 사용하고 공유 메모리를 사용하는 등의 방법으로 IPC 트래픽을 최적화해야 할 수 있습니다. AIDL 인터페이스가 공개 API이기도 한 경우에는 이러한 작업을 할 수 없습니다.

예를 들어 FooService를 공개 API로 직접 노출하지 마세요.

// BAD: Public API generated from IFooService.aidl
public class IFooService {
   public void doFoo(String foo);
}

대신 관리자 또는 다른 클래스 내에 Binder 인터페이스를 래핑합니다.

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo);
}

public IFooManager {
   public void doFoo(String foo) {
      mFooService.doFoo(foo);
   }
}

나중에 이 호출에 새 인수가 필요한 경우 내부 인터페이스는 최소화할 수 있으며 편리한 오버로드가 공개 API에 추가될 수 있습니다. 래핑 레이어를 사용하여 구현이 발전함에 따라 다른 이전 버전과의 호환성 문제를 처리할 수 있습니다.

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo, int flags);
}

public IFooManager {
   public void doFoo(String foo) {
      if (mAppTargetSdkLevel < 26) {
         useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
         mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
      } else {
         mFooService.doFoo(foo, 0);
      }
   }

   public void doFoo(String foo, int flags) {
      mFooService.doFoo(foo, flags);
   }
}

Android 플랫폼에 포함되지 않은 Binder 인터페이스 (예: 앱에서 사용할 Google Play 서비스에서 내보낸 서비스 인터페이스)의 경우 안정적이고 게시되고 버전이 지정된 IPC 인터페이스에 대한 요구사항은 인터페이스 자체를 발전시키기가 훨씬 더 어렵다는 것을 의미합니다. 하지만 다른 API 가이드라인과 일치하고 새 버전의 IPC 인터페이스에 동일한 공개 API를 더 쉽게 사용할 수 있도록 래퍼 레이어를 사용하는 것이 좋습니다.

공개 API에서 원시 바인더 객체 사용 금지

Binder 객체는 자체적으로 의미가 없으므로 공개 API에서 사용하면 안 됩니다. 일반적인 사용 사례는 ID 의미 체계가 있으므로 Binder 또는 IBinder를 토큰으로 사용하는 것입니다. 원시 Binder 객체를 사용하는 대신 래퍼 토큰 클래스를 사용하세요.

public final class IdentifiableObject {
  public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
  /**
   * @hide
   */
  public Binder getRawValue() {...}

  /**
   * @hide
   */
  public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}

public final class IdentifiableObject {
  public IdentifiableObjectToken getToken() {...}
}

관리자 클래스는 최종 클래스여야 합니다.

관리자 클래스는 final로 선언해야 합니다. 관리자 클래스는 시스템 서비스와 통신하며 단일 상호작용 지점입니다. 맞춤설정이 필요하지 않으므로 final로 선언합니다.

CompletableFuture 또는 Future를 사용하지 마세요.

java.util.concurrent.CompletableFuture에는 미래 값의 임의 변형을 허용하는 큰 API 노출 영역이 있으며 오류가 발생하기 쉬운 기본값이 있습니다.

반대로 java.util.concurrent.Future에는 비차단 리스닝이 누락되어 비동기 코드와 함께 사용하기 어렵습니다.

플랫폼 코드Kotlin과 Java 모두에서 사용하는 하위 수준 라이브러리 API에서는 완료 콜백, Executor, API가 취소를 지원하는 경우 CancellationSignal의 조합을 선호합니다.

public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
    Executor callbackExecutor,
    android.os.OutcomeReceiver<FooResult, Throwable> callback);

Kotlin을 타겟팅하는 경우 suspend 함수를 사용하는 것이 좋습니다.

suspend fun asyncLoadFoo(): Foo

Java 전용 통합 라이브러리에서는 Guava의 ListenableFuture를 사용할 수 있습니다.

public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();

Optional을 사용하지 않음

Optional는 일부 API 노출 영역에서 장점이 있을 수 있지만 기존 Android API 노출 영역과 일치하지 않습니다. @Nullable@NonNullnull 안전을 위한 도구 지원을 제공하고 Kotlin은 컴파일러 수준에서 null 허용 여부 계약을 적용하므로 Optional가 필요하지 않습니다.

선택적 기본 요소의 경우 페어링된 hasget 메서드를 사용합니다. 값이 설정되지 않은 경우 (hasfalse를 반환함) get 메서드는 IllegalStateException을 발생시켜야 합니다.

public boolean hasAzimuth() { ... }
public int getAzimuth() {
  if (!hasAzimuth()) {
    throw new IllegalStateException("azimuth is not set");
  }
  return azimuth;
}

인스턴스화할 수 없는 클래스에 비공개 생성자 사용

Builder로만 만들 수 있는 클래스, 상수 또는 정적 메서드만 포함하는 클래스 또는 인스턴스화할 수 없는 클래스는 기본 인수가 없는 생성자를 사용한 인스턴스화를 방지하기 위해 비공개 생성자를 하나 이상 포함해야 합니다.

public final class Log {
  // Not instantiable.
  private Log() {}
}

싱글톤

싱글톤은 다음과 같은 테스트 관련 단점이 있으므로 사용하지 않는 것이 좋습니다.

  1. 생성은 클래스에서 관리하므로 가짜 사용이 방지됩니다.
  2. 싱글톤의 정적 특성으로 인해 테스트가 hermetic일 수 없음
  3. 이러한 문제를 해결하려면 개발자가 싱글톤의 내부 세부정보를 알거나 싱글톤 주위에 래퍼를 만들어야 합니다.

이러한 문제를 해결하기 위해 추상 기본 클래스를 사용하는 단일 인스턴스 패턴을 사용하는 것이 좋습니다.

단일 인스턴스

단일 인스턴스 클래스는 private 또는 internal 생성자가 있는 추상 기본 클래스를 사용하고 인스턴스를 가져오는 정적 getInstance() 메서드를 제공합니다. getInstance() 메서드는 후속 호출에서 동일한 객체를 반환해야 합니다.

getInstance()에서 반환된 객체는 추상 기본 클래스의 비공개 구현이어야 합니다.

class Singleton private constructor(...) {
  companion object {
    private val _instance: Singleton by lazy { Singleton(...) }

    fun getInstance(): Singleton {
      return _instance
    }
  }
}
abstract class SingleInstance private constructor(...) {
  companion object {
    private val _instance: SingleInstance by lazy { SingleInstanceImp(...) }
    fun getInstance(): SingleInstance {
      return _instance
    }
  }
}

단일 인스턴스는 개발자가 SingleInstance의 가짜 버전을 만들고 자체 종속 항목 삽입 프레임워크를 사용하여 래퍼를 만들지 않고 구현을 관리할 수 있다는 점에서 싱글톤과 다릅니다. 또는 라이브러리가 -testing 아티팩트에서 자체 가짜를 제공할 수 있습니다.

리소스가 해제되는 클래스는 AutoCloseable을 구현해야 합니다.

close, release, destroy 또는 유사한 메서드를 통해 리소스를 해제하는 클래스는 개발자가 try-with-resources 블록을 사용할 때 이러한 리소스를 자동으로 정리할 수 있도록 java.lang.AutoCloseable를 구현해야 합니다.

android.*에 새 View 하위 클래스 도입 방지

플랫폼 공개 API (즉, android.*)에서 android.view.View로부터 직접 또는 간접적으로 상속받는 새 클래스를 도입하지 마세요.

이제 Android의 UI 툴킷은 Compose 우선입니다. 플랫폼에서 노출하는 새로운 UI 기능은 Jetpack 라이브러리의 개발자가 Jetpack Compose를 구현하고 선택적으로 뷰 기반 UI 구성요소를 구현하는 데 사용할 수 있는 하위 수준 API로 노출해야 합니다. 라이브러리에서 이러한 구성요소를 제공하면 플랫폼 기능을 사용할 수 없는 경우 백포트된 구현을 사용할 수 있습니다.

필드

이러한 규칙은 클래스의 공개 필드에 관한 것입니다.

원시 필드를 노출하지 않음

Java 클래스는 필드를 직접 노출해서는 안 됩니다. 필드는 비공개여야 하며 이러한 필드가 최종인지 여부와 관계없이 공개 getter 및 setter를 사용해서만 액세스할 수 있어야 합니다.

필드를 지정하거나 검색하는 동작을 개선할 필요가 없는 기본 데이터 구조는 드문 예외에 해당합니다. 이러한 경우 필드는 표준 변수 이름 지정 규칙을 사용하여 이름을 지정해야 합니다(예: Point.xPoint.y).

Kotlin 클래스는 속성을 노출할 수 있습니다.

노출된 필드는 final로 표시해야 함

원시 필드는 사용하지 않는 것이 좋습니다 (@see 원시 필드를 노출하지 마세요). 하지만 필드가 공개 필드로 노출되는 드문 경우에는 해당 필드를 final로 표시합니다.

내부 필드는 노출하면 안 됨

공개 API에서 내부 필드 이름을 참조하지 마세요.

public int mFlags;

보호 대신 공개 사용

@see protected 대신 public 사용

상수

공개 상수에 관한 규칙입니다.

플래그 상수는 int 또는 long 값과 겹치면 안 됩니다.

플래그는 일부 결합 값으로 결합할 수 있는 비트를 의미합니다. 그렇지 않은 경우 변수나 상수 flag를 호출하지 마세요.

public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;

공개 플래그 상수를 정의하는 방법에 관한 자세한 내용은 비트 마스크 플래그용 @IntDef를 참고하세요.

static final 상수는 모두 대문자, 밑줄로 구분된 이름 지정 규칙을 사용해야 합니다.

상수의 모든 단어는 대문자여야 하며 여러 단어는 _로 구분해야 합니다. 예를 들면 다음과 같습니다.

public static final int fooThing = 5
public static final int FOO_THING = 5

상수에 표준 접두사 사용

Android에서 사용되는 상수 중 상당수는 플래그, 키, 작업과 같은 표준 항목을 위한 것입니다. 이러한 상수는 이러한 항목으로 더 쉽게 식별할 수 있도록 표준 접두사가 있어야 합니다.

예를 들어 인텐트 추가 정보는 EXTRA_로 시작해야 합니다. 인텐트 작업은 ACTION_로 시작해야 합니다. Context.bindService()와 함께 사용되는 상수는 BIND_로 시작해야 합니다.

키 상수 이름 및 범위

문자열 상수 값은 상수 이름 자체와 일치해야 하며 일반적으로 패키지 또는 도메인으로 범위가 지정되어야 합니다. 예를 들면 다음과 같습니다.

public static final String FOO_THING = "foo"

이름이 일관되게 지정되지도 않고 범위가 적절하지도 않습니다. 대신 다음을 고려하세요.

public static final String FOO_THING = "android.fooservice.FOO_THING"

범위가 지정된 문자열 상수의 android 접두사는 Android 오픈소스 프로젝트용으로 예약되어 있습니다.

인텐트 작업 및 부가 정보와 번들 항목은 정의된 패키지 이름을 사용하여 네임스페이스가 지정되어야 합니다.

package android.foo.bar {
  public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
  public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}

보호 대신 공개 사용

@see protected 대신 public 사용

일관된 접두사 사용

관련 상수는 모두 동일한 접두사로 시작해야 합니다. 예를 들어 플래그 값과 함께 사용할 상수 집합의 경우:

public static final int SOME_VALUE = 0x01;

public static final int SOME_OTHER_VALUE = 0x10;

public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;

public static final int FLAG_SOME_OTHER_VALUE = 0x10;

public static final int FLAG_SOME_THIRD_VALUE = 0x100;

@see 상수에 표준 접두사 사용

일관된 리소스 이름 사용

공개 식별자, 속성, 값은 Java의 공개 필드와 마찬가지로 camelCase 이름 지정 규칙(예: @id/accessibilityActionPageUp 또는 @attr/textAppearance)을 사용하여 이름을 지정해야 합니다.

일부 경우 공개 식별자 또는 속성에는 밑줄로 구분된 공통 접두사가 포함됩니다.

  • config.xml@string/config_recentsComponentName와 같은 플랫폼 구성 값
  • attrs.xml@attr/layout_marginStart와 같은 레이아웃 관련 뷰 속성

공개 테마와 스타일은 계층적 PascalCase 이름 지정 규칙을 따라야 합니다(예: @style/Theme.Material.Light.DarkActionBar 또는 @style/Widget.Material.SearchView.ActionBar). 이는 Java의 중첩 클래스와 유사합니다.

레이아웃 및 드로어블 리소스는 공개 API로 노출해서는 안 됩니다. 하지만 공개해야 하는 경우 공개 레이아웃과 드로어블은 under_score 이름 지정 규칙을 사용하여 이름을 지정해야 합니다(예: layout/simple_list_item_1.xml 또는 drawable/title_bar_tall.xml).

상수가 변경될 수 있는 경우 동적으로 설정

컴파일러가 상수 값을 인라인할 수 있으므로 값을 동일하게 유지하는 것은 API 계약의 일부로 간주됩니다. MIN_FOO 또는 MAX_FOO 상수의 값이 향후 변경될 수 있는 경우 대신 동적 메서드로 만드는 것이 좋습니다.

CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()

콜백의 향후 호환성 고려

이전 API를 타겟팅하는 앱에는 향후 API 버전에서 정의된 상수가 알려지지 않습니다. 따라서 앱에 제공되는 상수는 앱의 타겟 API 버전을 고려해야 하며 최신 상수를 일관된 값에 매핑해야 합니다. 다음 시나리오를 고려해 보세요.

가상의 SDK 소스:

// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;

targetSdkVersion="22"이 있는 가상 앱:

if (result == STATUS_FAILURE) {
  // Oh no!
} else {
  // Success!
}

이 경우 앱은 API 수준 22의 제약 조건 내에서 설계되었으며 가능한 상태가 두 개뿐이라는 (약간) 합리적인 가정을 했습니다. 하지만 앱이 새로 추가된 STATUS_FAILURE_RETRY를 수신하면 이를 성공으로 해석합니다.

상수를 반환하는 메서드는 앱이 타겟팅하는 API 수준과 일치하도록 출력을 제한하여 이와 같은 사례를 안전하게 처리할 수 있습니다.

private int mapResultForTargetSdk(Context context, int result) {
  int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  if (targetSdkVersion < 26) {
    if (result == STATUS_FAILURE_ABORT) {
      return STATUS_FAILURE;
    }
    if (targetSdkVersion < 23) {
      if (result == STATUS_FAILURE_RETRY) {
        return STATUS_FAILURE;
      }
    }
  }
  return result;
}

개발자는 상수 목록이 향후 변경될지 예측할 수 없습니다. 모든 것을 포괄하는 것처럼 보이는 UNKNOWN 또는 UNSPECIFIED 상수로 API를 정의하면 개발자는 앱을 작성할 때 게시된 상수가 완전하다고 가정합니다. 이 기대치를 설정하고 싶지 않다면 API에 catch-all 상수가 적합한지 다시 고려해 보세요.

또한 라이브러리는 앱과 별도로 자체 targetSdkVersion를 지정할 수 없으며 라이브러리 코드에서 targetSdkVersion 동작 변경사항을 처리하는 것은 복잡하고 오류가 발생하기 쉽습니다.

정수 또는 문자열 상수

값의 네임스페이스가 패키지 외부에서 확장할 수 없는 경우 정수 상수와 @IntDef을 사용합니다. 네임스페이스가 공유되거나 패키지 외부의 코드로 확장될 수 있는 경우 문자열 상수를 사용합니다.

data 클래스

데이터 클래스는 변경 불가능한 속성 집합을 나타내며 해당 데이터와 상호작용하기 위한 작고 잘 정의된 유틸리티 함수 집합을 제공합니다.

Kotlin 컴파일러는 생성된 코드의 언어 API 또는 바이너리 호환성을 보장하지 않으므로 공개 Kotlin API에서 data class를 사용하지 마세요. 대신 필요한 함수를 수동으로 구현하세요.

인스턴스화

Java에서 데이터 클래스는 속성이 적은 경우 생성자를 제공하고 속성이 많은 경우 Builder 패턴을 사용해야 합니다.

Kotlin에서 데이터 클래스는 속성 수와 관계없이 기본 인수가 있는 생성자를 제공해야 합니다. Kotlin에서 정의된 데이터 클래스는 Java 클라이언트를 타겟팅할 때 빌더를 제공하는 것이 유용할 수도 있습니다.

수정 및 복사

데이터를 수정해야 하는 경우 복사 생성자 (Java)가 있는 Builder 클래스 또는 새 객체를 반환하는 copy() 멤버 함수 (Kotlin)를 제공합니다.

Kotlin에서 copy() 함수를 제공할 때 인수는 클래스의 생성자와 일치해야 하며 기본값은 객체의 현재 값을 사용하여 채워야 합니다.

class Typography(
  val labelMedium: TextStyle = TypographyTokens.LabelMedium,
  val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
    fun copy(
      labelMedium: TextStyle = this.labelMedium,
      labelSmall: TextStyle = this.labelSmall
    ): Typography = Typography(
      labelMedium = labelMedium,
      labelSmall = labelSmall
    )
}

추가 동작

데이터 클래스는 equals()hashCode()를 모두 구현해야 하며 모든 속성은 이러한 메서드의 구현에서 고려해야 합니다.

데이터 클래스는 Kotlin의 데이터 클래스 구현(예: User(var1=Alex, var2=42))과 일치하는 권장 형식으로 toString()을 구현할 수 있습니다.

메서드

이는 매개변수, 메서드 이름, 반환 유형, 액세스 지정자와 관련된 메서드의 다양한 세부사항에 관한 규칙입니다.

시간

이러한 규칙은 날짜 및 기간과 같은 시간 개념이 API에서 표현되는 방식을 다룹니다.

가능하면 java.time.* 유형을 사용하세요.

java.time.Duration, java.time.Instant 및 기타 여러 java.time.* 유형은 desugaring을 통해 모든 플랫폼 버전에서 사용할 수 있으며 API 매개변수 또는 반환 값으로 시간을 표현할 때 사용하는 것이 좋습니다.

java.time.Duration 또는 java.time.Instant를 허용하거나 반환하는 API 변형만 노출하고 의도된 사용 패턴에서 객체 할당이 금지된 성능 영향을 미치는 API 도메인이 아닌 한 동일한 사용 사례가 있는 기본 변형은 생략하는 것이 좋습니다.

기간을 표현하는 메서드의 이름은 duration이어야 합니다.

시간 값이 관련 시간의 기간을 나타내는 경우 매개변수 이름을 'time'이 아닌 'duration'으로 지정합니다.

ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);

예외:

기간이 제한 시간 값에만 적용되는 경우 'timeout'이 적합합니다.

java.time.Instant 유형의 'time'은 기간이 아닌 특정 시점을 참조할 때 적합합니다.

기간이나 시간을 원시로 표현하는 메서드는 시간 단위로 이름을 지정하고 long을 사용해야 합니다.

기본 유형으로 기간을 허용하거나 반환하는 메서드는 java.time.Duration와 함께 사용할 장식되지 않은 이름을 예약하기 위해 메서드 이름에 연결된 시간 단위 (예: Millis, Nanos, Seconds)를 접미사로 붙여야 합니다. 시간을 참고하세요.

또한 메서드에는 단위와 시간 기준이 적절하게 주석으로 지정되어야 합니다.

  • @CurrentTimeMillisLong: 값은 1970-01-01T00:00:00Z 이후의 밀리초 수로 측정된 음수가 아닌 타임스탬프입니다.
  • @CurrentTimeSecondsLong: 값은 1970-01-01T00:00:00Z 이후의 초 수로 측정된 음수가 아닌 타임스탬프입니다.
  • @DurationMillisLong: 값은 밀리초 단위의 음수가 아닌 기간입니다.
  • @ElapsedRealtimeLong: 값은 SystemClock.elapsedRealtime() 시간 기준의 음수가 아닌 타임스탬프입니다.
  • @UptimeMillisLong: 값은 SystemClock.uptimeMillis() 시간 기준의 음수가 아닌 타임스탬프입니다.

기본 시간 매개변수 또는 반환 값은 int이 아닌 long를 사용해야 합니다.

ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);

시간 단위를 표현하는 메서드는 단위 이름의 약어가 아닌 표기를 선호해야 합니다.

public void setIntervalNs(long intervalNs);

public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);

public void setTimeoutMicros(long timeoutMicros);

긴 시간 인수에 주석 추가

이 플랫폼에는 long 유형 시간 단위에 더 강력한 타이핑을 제공하는 여러 주석이 포함되어 있습니다.

  • @CurrentTimeMillisLong: 값은 1970-01-01T00:00:00Z 이후의 밀리초 수로 측정된 음수가 아닌 타임스탬프이므로 System.currentTimeMillis() 시간 기준입니다.
  • @CurrentTimeSecondsLong: 값은 1970-01-01T00:00:00Z 이후의 시간(초)으로 측정된 음수가 아닌 타임스탬프입니다.
  • @DurationMillisLong: 값은 밀리초 단위의 음수가 아닌 기간입니다.
  • @ElapsedRealtimeLong: 값은 SystemClock#elapsedRealtime() 시간 기준의 음수가 아닌 타임스탬프입니다.
  • @UptimeMillisLong: 값은 SystemClock#uptimeMillis() 시간 기준의 음수가 아닌 타임스탬프입니다.

측정 단위

시간 의 측정 단위를 표현하는 모든 메서드의 경우 CamelCase SI 단위 접두사를 사용하는 것이 좋습니다.

public  long[] getFrequenciesKhz();

public  float getStreamVolumeDb();

선택적 매개변수를 오버로드 끝에 배치

선택적 매개변수가 있는 메서드의 오버로드가 있는 경우 이러한 매개변수를 끝에 두고 다른 매개변수와 일관된 순서를 유지하세요.

public int doFoo(boolean flag);

public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);

public int doFoo(boolean flag, int id);

선택적 인수의 오버로드를 추가할 때 더 간단한 메서드의 동작은 기본 인수가 더 정교한 메서드에 제공된 것과 정확히 동일하게 동작해야 합니다.

결론: 메서드가 다형성인 경우 선택적 인수를 추가하거나 다양한 유형의 인수를 허용하는 경우를 제외하고 메서드를 오버로드하지 마세요. 오버로드된 메서드가 근본적으로 다른 작업을 하는 경우 새 이름을 지정합니다.

기본 매개변수가 있는 메서드는 @JvmOverloads로 주석 처리해야 합니다 (Kotlin만 해당).

바이너리 호환성을 유지하려면 기본 매개변수가 있는 메서드와 생성자에 @JvmOverloads 주석을 달아야 합니다.

자세한 내용은 공식 Kotlin-Java 상호 운용성 가이드의 기본값의 함수 오버로드를 참고하세요.

class Greeting @JvmOverloads constructor(
  loudness: Int = 5
) {
  @JvmOverloads
  fun sayHello(prefix: String = "Dr.", name: String) = // ...
}

기본 매개변수 값을 삭제하지 않음 (Kotlin만 해당)

기본값이 있는 매개변수와 함께 메서드가 제공된 경우 기본값을 삭제하면 소스 호환성이 깨지는 변경사항입니다.

가장 고유하고 식별 가능한 메서드 매개변수가 먼저 와야 합니다.

매개변수가 여러 개인 메서드가 있는 경우 관련성이 가장 높은 매개변수를 먼저 배치하세요. 플래그와 기타 옵션을 지정하는 매개변수는 작업이 수행되는 객체를 설명하는 매개변수보다 중요도가 낮습니다. 완료 콜백이 있는 경우 마지막에 배치합니다.

public void openFile(int flags, String name);

public void openFileAsync(OnFileOpenedListener listener, String name, int flags);

public void setFlags(int mask, int flags);
public void openFile(String name, int flags);

public void openFileAsync(String name, int flags, OnFileOpenedListener listener);

public void setFlags(int flags, int mask);

오버로드에서 선택적 매개변수를 끝에 배치도 참고하세요.

빌더

빌더 패턴은 복잡한 Java 객체를 만드는 데 권장되며 다음 사례에서 Android에 일반적으로 사용됩니다.

  • 결과 객체의 속성은 변경할 수 없어야 합니다.
  • 필수 속성이 많습니다(예: 많은 생성자 인수).
  • 생성 시 속성 간에 복잡한 관계가 있습니다(예: 확인 단계가 필요함). 이러한 복잡성은 API의 유용성에 문제가 있음을 나타내는 경우가 많습니다.

빌더가 필요한지 고려합니다. 빌더는 다음 용도로 사용되는 경우 API 노출 영역에서 유용합니다.

  • 선택적 생성 매개변수의 잠재적으로 큰 집합 중 일부만 구성
  • 호출 사이트가 읽기 혼란스럽거나 쓰기 오류가 발생하기 쉬운 경우 유사하거나 일치하는 유형의 다양한 선택적 또는 필수 생성 매개변수를 구성합니다.
  • 여러 다른 구성 코드 각각이 구현 세부정보로 빌더를 호출할 수 있는 객체의 생성 증분식으로 구성
  • 향후 API 버전에 선택적 생성 매개변수를 추가하여 유형이 확장되도록 허용

필수 매개변수가 3개 이하이고 선택적 매개변수가 없는 유형이 있는 경우 거의 항상 빌더를 건너뛰고 일반 생성자를 사용할 수 있습니다.

Kotlin 소스 클래스는 빌더보다 기본 인수가 있는 @JvmOverloads 주석이 달린 생성자를 선호해야 하지만, 앞에서 설명한 경우에도 빌더를 제공하여 Java 클라이언트의 유용성을 개선할 수 있습니다.

class Tone @JvmOverloads constructor(
  val duration: Long = 1000,
  val frequency: Int = 2600,
  val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
  class Builder {
    // ...
  }
}

빌더 클래스는 빌더를 반환해야 합니다.

빌더 클래스는 build()을 제외한 모든 메서드에서 빌더 객체(예: this)를 반환하여 메서드 체인을 사용 설정해야 합니다. 추가 빌드된 객체는 인수로 전달해야 합니다. 다른 객체의 빌더를 반환하지 마세요. 예를 들면 다음과 같습니다.

public static class Builder {
  public void setDuration(long);
  public void setFrequency(int);
  public DtmfConfigBuilder addDtmfConfig();
  public Tone build();
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}

기본 빌더 클래스가 확장 프로그램을 지원해야 하는 드문 경우 범용 반환 유형을 사용하세요.

public abstract class Builder<T extends Builder<T>> {
  abstract T setValue(int);
}

public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> {
  T setValue(int);
  T setTypeSpecificValue(long);
}

빌더 클래스는 생성자를 통해 생성해야 합니다.

Android API 노출 영역을 통해 일관된 빌더 생성을 유지하려면 모든 빌더는 정적 생성자 메서드가 아닌 생성자를 통해 생성해야 합니다. Kotlin 기반 API의 경우 Kotlin 사용자가 팩토리 메서드/DSL 스타일 생성 메커니즘을 통해 빌더를 암시적으로 사용하도록 예상되는 경우에도 Builder는 공개되어야 합니다. 라이브러리는 @PublishedApi internal를 사용하여 Kotlin 클라이언트에서 Builder 클래스 생성자를 선택적으로 숨겨서는 안 됩니다.

public class Tone {
  public static Builder builder();
  public static class Builder {
  }
}
public class Tone {
  public static class Builder {
    public Builder();
  }
}

빌더 생성자의 모든 인수는 필수여야 합니다 (예: @NonNull).

선택적 인수(예: @Nullable)는 setter 메서드로 이동해야 합니다. 필수 인수가 지정되지 않은 경우 빌더 생성자는 NullPointerException를 발생시켜야 합니다 (Objects.requireNonNull 사용 고려).

빌더 클래스는 빌드된 유형의 최종 정적 내부 클래스여야 합니다.

패키지 내의 논리적 구성을 위해 빌더 클래스는 일반적으로 빌드된 유형의 최종 내부 클래스로 노출되어야 합니다(예: ToneBuilder이 아닌 Tone.Builder).

빌더는 기존 인스턴스에서 새 인스턴스를 만드는 생성자를 포함할 수 있습니다.

빌더는 기존 빌더나 빌드된 객체에서 새 빌더 인스턴스를 만들기 위해 복사 생성자를 포함할 수 있습니다. 기존 빌더나 빌드 객체에서 빌더 인스턴스를 만드는 대체 방법을 제공해서는 안 됩니다.

public class Tone {
  public static class Builder {
    public Builder clone();
  }

  public Builder toBuilder();
}
public class Tone {
  public static class Builder {
    public Builder(Builder original);
    public Builder(Tone original);
  }
}
빌더에 복사 생성자가 있는 경우 빌더 setter는 @Nullable 인수를 사용해야 함

빌더의 새 인스턴스를 기존 인스턴스에서 만들 수 있는 경우 재설정이 필수입니다. 복사 생성자를 사용할 수 없는 경우 빌더에는 @Nullable 또는 @NonNullable 인수가 있을 수 있습니다.

public static class Builder {
  public Builder(Builder original);
  public Builder setObjectValue(@Nullable Object value);
}
빌더 setter는 선택적 속성에 @Nullable 인수를 사용할 수 있음

특히 빌더와 오버로드 대신 기본 인수를 사용하는 Kotlin에서는 2차 입력에 null 허용 값을 사용하는 것이 더 간단한 경우가 많습니다.

또한 @Nullable 설정자는 게터와 일치하며, 게터는 선택적 속성의 경우 @Nullable여야 합니다.

Value createValue(@Nullable OptionalValue optionalValue) {
  Value.Builder builder = new Value.Builder();
  if (optionalValue != null) {
    builder.setOptionalValue(optionalValue);
  }
  return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
  return new Value.Builder()
    .setOptionalValue(optionalValue);
    .build();
}

// Or in other cases:

Value createValue() {
  return new Value.Builder()
    .setOptionalValue(condition ? new OptionalValue() : null);
    .build();
}

Kotlin의 일반적인 사용법:

fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .apply { optionalValue?.let { setOptionalValue(it) } }
    .build()
fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .setOptionalValue(optionalValue)
    .build()

설정자가 호출되지 않은 경우의 기본값과 null의 의미는 설정자와 게터 모두에 적절하게 문서화되어야 합니다.

/**
 * ...
 *
 * <p>Defaults to {@code null}, which means the optional value won't be used.
 */

빌더 setter는 빌드된 클래스에서 setter를 사용할 수 있는 변경 가능한 속성에 제공할 수 있습니다.

클래스에 변경 가능한 속성이 있고 Builder 클래스가 필요한 경우 먼저 클래스에 변경 가능한 속성이 실제로 있어야 하는지 자문해 보세요.

다음으로, 변경 가능한 속성이 필요하다고 확신하는 경우 예상되는 사용 사례에 다음 시나리오 중 어떤 것이 더 적합한지 결정합니다.

  1. 빌드된 객체는 즉시 사용할 수 있어야 하므로 변경 가능한 속성이든 변경 불가능한 속성이든 모든 관련 속성에 대해 설정자를 제공해야 합니다.

    map.put(key, new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .setUsefulMutableProperty(usefulValue)
        .build());
    
  2. 빌드된 객체가 유용하려면 몇 가지 추가 호출이 필요할 수 있으므로 변경 가능한 속성에 대해 설정자를 제공해서는 안 됩니다.

    Value v = new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .build();
    v.setUsefulMutableProperty(usefulValue)
    Result r = v.performSomeAction();
    Key k = callSomeMethod(r);
    map.put(k, v);
    

두 시나리오를 혼합하지 마세요.

Value v = new Value.Builder(requiredValue)
    .setImmutableProperty(immutableValue)
    .setUsefulMutableProperty(usefulValue)
    .build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);

빌더에는 게터가 없어야 함

getter는 빌더가 아닌 빌드된 객체에 있어야 합니다.

빌더 setter에는 빌드된 클래스에 해당하는 getter가 있어야 합니다.

public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }

  public long getDuration();
  public int getFrequency();
  public @NonNull List<DtmfConfig> getDtmfConfigs();
}

빌더 메서드 이름 지정

빌더 메서드 이름은 setFoo(), addFoo() 또는 clearFoo() 스타일을 사용해야 합니다.

빌더 클래스는 build() 메서드를 선언해야 합니다.

빌더 클래스는 생성된 객체의 인스턴스를 반환하는 build() 메서드를 선언해야 합니다.

빌더 build() 메서드는 @NonNull 객체를 반환해야 합니다.

빌더의 build() 메서드는 생성된 객체의 null이 아닌 인스턴스를 반환해야 합니다. 잘못된 매개변수로 인해 객체를 만들 수 없는 경우 검증을 빌드 메서드로 연기하고 IllegalStateException를 발생시켜야 합니다.

내부 잠금 노출 안 함

공개 API의 메서드는 synchronized 키워드를 사용해서는 안 됩니다. 이 키워드를 사용하면 객체나 클래스가 잠금으로 사용되며 다른 사용자에게 노출되므로 클래스 외부의 다른 코드에서 잠금 목적으로 사용하기 시작하면 예기치 않은 부작용이 발생할 수 있습니다.

대신 내부 비공개 객체에 대해 필요한 잠금을 실행하세요.

public synchronized void doThing() { ... }
private final Object mThingLock = new Object();

public void doThing() {
  synchronized (mThingLock) {
    ...
  }
}

접근자 스타일 메서드는 Kotlin 속성 가이드라인을 따라야 함

Kotlin 소스에서 볼 때 접근자 스타일 메서드(get, set 또는 is 접두사를 사용하는 메서드)도 Kotlin 속성으로 사용할 수 있습니다. 예를 들어 Java에서 정의된 int getField()는 Kotlin에서 val field: Int 속성으로 사용할 수 있습니다.

이러한 이유로, 그리고 일반적으로 접근자 메서드 동작에 관한 개발자 기대치를 충족하기 위해 접근자 메서드 접두사를 사용하는 메서드는 Java 필드와 유사하게 동작해야 합니다. 다음과 같은 경우에는 접근자 스타일 접두사를 사용하지 마세요.

  • 메서드에 부작용이 있습니다. 더 설명적인 메서드 이름을 사용하는 것이 좋습니다.
  • 이 메서드는 컴퓨팅 비용이 많이 드는 작업을 포함합니다. compute를 사용하는 것이 좋습니다.
  • 이 메서드는 IPC나 기타 I/O와 같은 값을 반환하기 위해 차단하거나 장기 실행 작업을 포함합니다. fetch를 사용하는 것이 좋습니다.
  • 이 메서드는 값을 반환할 수 있을 때까지 스레드를 차단합니다. await를 사용하는 것이 좋습니다.
  • 이 메서드는 호출될 때마다 새 객체 인스턴스를 반환합니다. create를 사용하는 것이 좋습니다.
  • 메서드가 값을 성공적으로 반환하지 않을 수 있습니다. request을 사용하는 것이 좋습니다.

계산 비용이 많이 드는 작업을 한 번 실행하고 후속 호출을 위해 값을 캐싱하는 것도 계산 비용이 많이 드는 작업을 실행하는 것으로 간주됩니다. 버벅거림은 프레임 간에 상각되지 않습니다.

불리언 접근자 메서드에 is 접두사 사용

이는 Java의 불리언 메서드와 필드에 대한 표준 이름 지정 규칙입니다. 일반적으로 불리언 메서드와 변수 이름은 반환 값으로 답변되는 질문으로 작성해야 합니다.

Java 불리언 액세서 메서드는 set/is 이름 지정 체계를 따라야 하고 필드는 다음 예와 같이 is을 선호해야 합니다.

// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();

// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();

final boolean isAvailable;

Java 접근자 메서드에 set/is를 사용하거나 Java 필드에 is를 사용하면 Kotlin에서 속성으로 사용할 수 있습니다.

obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return

속성과 접근자 메서드는 일반적으로 Disabled이 아닌 Enabled와 같은 긍정적인 이름을 사용해야 합니다. 부정적인 용어를 사용하면 truefalse의 의미가 반전되어 동작을 추론하기가 더 어려워집니다.

// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);

불리언이 속성의 포함 또는 소유권을 설명하는 경우 is 대신 has를 사용할 수 있습니다. 하지만 Kotlin 속성 구문에서는 작동하지 않습니다.

// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();

더 적합할 수 있는 대체 접두사로는 canshould가 있습니다.

// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();

// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();

동작이나 기능을 전환하는 메서드는 is 접두사와 Enabled 접미사를 사용할 수 있습니다.

// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()

마찬가지로 다른 동작이나 기능에 대한 종속성을 나타내는 메서드는 is 접두사와 Supported 또는 Required 접미사를 사용할 수 있습니다.

// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()

일반적으로 메서드 이름은 반환 값으로 답변되는 질문으로 작성해야 합니다.

Kotlin 속성 메서드

클래스 속성 var foo: Foo의 경우 Kotlin은 일관된 규칙을 사용하여 get/set 메서드를 생성합니다. getter의 경우 get을 앞에 추가하고 첫 글자를 대문자로 변환하고 setter의 경우 set을 앞에 추가하고 첫 글자를 대문자로 변환합니다. 속성 선언은 각각 public Foo getFoo()public void setFoo(Foo foo)이라는 메서드를 생성합니다.

속성이 Boolean 유형인 경우 이름 생성에 추가 규칙이 적용됩니다. 속성 이름이 is로 시작하면 getter 메서드 이름에 get가 추가되지 않고 속성 이름 자체가 getter로 사용됩니다. 따라서 이름 지정 가이드라인을 따르려면 is 접두사를 사용하여 Boolean 속성의 이름을 지정하는 것이 좋습니다.

var isVisible: Boolean

속성이 앞서 언급한 예외 중 하나이고 적절한 접두사로 시작하는 경우 속성에 @get:JvmName 주석을 사용하여 적절한 이름을 수동으로 지정합니다.

@get:JvmName("hasTransientState")
var hasTransientState: Boolean

@get:JvmName("canRecord")
var canRecord: Boolean

@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean

비트 마스크 접근자

비트마스크 플래그 정의에 관한 API 가이드라인은 비트마스크 플래그에 @IntDef 사용을 참고하세요.

세터

두 개의 setter 메서드가 제공되어야 합니다. 하나는 전체 비트 문자열을 가져와 기존 플래그를 모두 덮어쓰고 다른 하나는 맞춤 비트 마스크를 가져와 더 많은 유연성을 허용합니다.

/**
 * Sets the state of all scroll indicators.
 * <p>
 * See {@link #setScrollIndicators(int, int)} for usage information.
 *
 * @param indicators a bitmask of indicators that should be enabled, or
 *                   {@code 0} to disable all indicators
 * @see #setScrollIndicators(int, int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators);

/**
 * Sets the state of the scroll indicators specified by the mask. To change
 * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
 * <p>
 * When a scroll indicator is enabled, it will be displayed if the view
 * can scroll in the direction of the indicator.
 * <p>
 * Multiple indicator types may be enabled or disabled by passing the
 * logical OR of the specified types. If multiple types are specified, they
 * will all be set to the same enabled state.
 * <p>
 * For example, to enable the top scroll indicator:
 * {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
 * <p>
 * To disable the top scroll indicator:
 * {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
 *
 * @param indicators a bitmask of values to set; may be a single flag,
 *                   the logical OR of multiple flags, or 0 to clear
 * @param mask a bitmask indicating which indicator flags to modify
 * @see #setScrollIndicators(int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);

Getter

전체 비트 마스크를 가져오는 게터 하나를 제공해야 합니다.

/**
 * Returns a bitmask representing the enabled scroll indicators.
 * <p>
 * For example, if the top and left scroll indicators are enabled and all
 * other indicators are disabled, the return value will be
 * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
 * <p>
 * To check whether the bottom scroll indicator is enabled, use the value
 * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
 *
 * @return a bitmask representing the enabled scroll indicators
 */
@ScrollIndicators
public int getScrollIndicators();

보호 대신 공개 사용

공개 API에서는 항상 protected보다 public를 선호합니다. 보호된 액세스는 장기적으로 고통스러워집니다. 구현자가 기본적으로 외부 액세스가 적합한 경우에 공개 액세스 권한을 제공하기 위해 재정의해야 하기 때문입니다.

protected 공개 상태는 개발자가 API를 호출하는 것을 방지하지 않습니다. 약간 더 불쾌하게 만들 뿐입니다.

equals()와 hashCode() 중 하나도 구현하지 않거나 둘 다 구현합니다.

하나를 재정의하는 경우 다른 하나도 재정의해야 합니다.

데이터 클래스에 toString() 구현

데이터 클래스는 개발자가 코드를 디버깅할 수 있도록 toString()를 재정의하는 것이 좋습니다.

출력이 프로그램 동작용인지 디버깅용인지 문서화

프로그램 동작이 구현에 의존할지 여부를 결정합니다. 예를 들어 UUID.toString()File.toString()은 프로그램에서 사용할 특정 형식을 문서화합니다. 인텐트와 같이 디버깅용 정보만 노출하는 경우 슈퍼클래스에서 문서를 상속한다고 가정합니다.

추가 정보를 포함하지 마세요.

toString()에서 제공되는 모든 정보는 객체의 공개 API를 통해서도 제공되어야 합니다. 그렇지 않으면 개발자가 toString() 출력을 파싱하고 이에 의존하도록 유도하게 되며, 이는 향후 변경을 방해합니다. 객체의 공개 API만 사용하여 toString()를 구현하는 것이 좋습니다.

디버그 출력에 의존하지 않도록 권장

개발자가 디버그 출력을 사용하는 것을 방지할 수는 없지만 객체의 System.identityHashCodetoString() 출력에 포함하면 서로 다른 두 객체의 toString() 출력이 동일할 가능성이 매우 낮아집니다.

@Override
public String toString() {
  return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}

이렇게 하면 개발자가 객체에 assertThat(a.toString()).isEqualTo(b.toString())와 같은 테스트 어설션을 작성하지 않도록 효과적으로 방지할 수 있습니다.

새로 생성된 객체를 반환할 때는 createFoo 사용

새 객체를 구성하는 등 반환 값을 생성하는 메서드에는 get 또는 new가 아닌 create 접두사를 사용합니다.

메서드가 반환할 객체를 만드는 경우 메서드 이름에 이를 명확하게 표시합니다.

public FooThing getFooThing() {
  return new FooThing();
}
public FooThing createFooThing() {
  return new FooThing();
}

File 객체를 허용하는 메서드는 스트림도 허용해야 합니다.

Android의 데이터 저장소 위치가 항상 디스크의 파일인 것은 아닙니다. 예를 들어 사용자 경계를 넘어 전달된 콘텐츠는 content:// Uri로 표현됩니다. 다양한 데이터 소스의 처리를 지원하려면 File 객체를 허용하는 API가 InputStream, OutputStream 또는 둘 다 허용해야 합니다.

public void setDataSource(File file)
public void setDataSource(InputStream stream)

박싱된 버전 대신 원시 프리미티브를 가져오고 반환

누락된 값이나 null 값을 전달해야 하는 경우 -1, Integer.MAX_VALUE 또는 Integer.MIN_VALUE를 사용하는 것이 좋습니다.

public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)

원시 유형의 클래스 동등 항목을 피하면 이러한 클래스의 메모리 오버헤드, 값에 대한 메서드 액세스, 원시 유형과 객체 유형 간의 캐스팅에서 발생하는 오토박싱을 피할 수 있습니다. 이러한 동작을 피하면 메모리와 비용이 많이 들고 더 자주 발생하는 가비지 컬렉션으로 이어질 수 있는 임시 할당을 절약할 수 있습니다.

주석을 사용하여 유효한 매개변수 및 반환 값 명확히 하기

다양한 상황에서 허용되는 값을 명확히 하기 위해 개발자 주석이 추가되었습니다. 이렇게 하면 프레임워크에서 특정 상수 값 집합 중 하나를 요구할 때 임의의 int를 전달하는 등 개발자가 잘못된 값을 제공할 때 도구가 개발자를 더 쉽게 지원할 수 있습니다. 적절한 경우 다음 주석을 모두 사용하세요.

null 허용 여부

Java API에는 명시적 null 허용 여부 주석이 필요하지만 null 허용 여부 개념은 Kotlin 언어의 일부이며 null 허용 여부 주석은 Kotlin API에서 사용하면 안 됩니다.

@Nullable: 지정된 반환 값, 매개변수 또는 필드가 null일 수 있음을 나타냅니다.

@Nullable
public String getName()

public void setName(@Nullable String name)

@NonNull: 지정된 반환 값, 매개변수 또는 필드가 null이 될 수 없음을 나타냅니다. @Nullable로 표시하는 것은 Android에서 비교적 새로운 개념이므로 Android API 메서드의 대부분이 일관되게 문서화되어 있지 않습니다. 따라서 '알 수 없음, @Nullable, @NonNull'의 삼중 상태가 있으며, 이 때문에 @NonNull이 API 가이드라인에 포함됩니다.

@NonNull
public String getName()

public void setName(@NonNull String name)

Android 플랫폼 문서의 경우 메서드 매개변수에 주석을 달면 매개변수 문서의 다른 곳에 'null'이 명시적으로 사용되지 않는 한 '이 값은 null일 수 있습니다.' 형식으로 문서가 자동 생성됩니다.

'실제로 null이 아님' 메서드: 선언된 @Nullable 주석이 없는 API의 기존 메서드는 메서드가 명확한 특정 상황 (예: findViewById())에서 null를 반환할 수 있는 경우 @Nullable로 주석 처리될 수 있습니다. null 검사를 원하지 않는 개발자를 위해 IllegalArgumentException를 발생시키는 동반 @NotNull requireFoo() 메서드를 추가해야 합니다.

인터페이스 메서드: 새 API는 Parcelable.writeToParcel()와 같은 인터페이스 메서드를 구현할 때 적절한 주석을 추가해야 합니다 (즉, 구현 클래스의 메서드는 writeToParcel(Parcel, int)이 아닌 writeToParcel(@NonNull Parcel, int)이어야 함). 주석이 없는 기존 API는 '수정'할 필요가 없습니다.

null 허용 여부 시행

Java에서는 메서드가 Objects.requireNonNull()를 사용하여 @NonNull 매개변수의 입력 유효성 검사를 실행하고 매개변수가 null이면 NullPointerException를 발생시키는 것이 권장됩니다. 이 작업은 Kotlin에서 자동으로 실행됩니다.

리소스

리소스 식별자: 특정 리소스의 ID를 나타내는 정수 매개변수에는 적절한 리소스 유형 정의가 주석으로 달려 있어야 합니다. 모든 리소스 유형(예: @StringRes, @ColorRes, @AnimRes)에는 포괄적인 @AnyRes 외에도 주석이 있습니다. 예를 들면 다음과 같습니다.

public void setTitle(@StringRes int resId)

상수 집합의 @IntDef

매직 상수: 공개 상수로 표시된 유한한 가능한 값 집합 중 하나를 수신하기 위한 Stringint 매개변수는 @StringDef 또는 @IntDef로 적절하게 주석 처리해야 합니다. 이러한 주석을 사용하면 허용되는 매개변수의 typedef와 같이 작동하는 새 주석을 만들 수 있습니다. 예를 들면 다음과 같습니다.

/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
  NAVIGATION_MODE_STANDARD,
  NAVIGATION_MODE_LIST,
  NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);

메서드는 주석이 달린 매개변수의 유효성을 확인하고 매개변수가 @IntDef에 포함되지 않은 경우 IllegalArgumentException을 발생시키는 것이 권장됩니다.

비트마스크 플래그용 @IntDef

주석은 상수가 플래그임을 지정할 수도 있으며 & 및 I와 결합할 수 있습니다.

/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
  FLAG_USE_LOGO,
  FLAG_SHOW_HOME,
  FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}

문자열 상수 집합의 @StringDef

@StringDef 주석도 있습니다. 이는 이전 섹션의 @IntDef와 정확히 동일하지만 String 상수용입니다. 모든 값의 문서를 자동으로 내보내는 데 사용되는 'prefix' 값을 여러 개 포함할 수 있습니다.

SDK 상수의 @SdkConstant

@SdkConstant 공개 필드가 ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE과 같은 SdkConstant 값 중 하나인 경우 주석을 답니다.

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";

재정의에 호환되는 null 허용 여부 제공

API 호환성을 위해 재정의의 null 허용 여부는 상위의 현재 null 허용 여부와 호환되어야 합니다. 다음 표는 호환성 기대치를 나타냅니다. 명시적으로 재정의는 재정의하는 요소보다 제한적이거나 더 제한적이어야 합니다.

유형 부모 자녀용
반환 유형 주석이 없음 주석이 없거나 null이 아님
반환 유형 Null 허용됨 Nullable 또는 nonnull
반환 유형 Nonnull Nonnull
재미있는 주장 주석이 없음 주석이 없거나 null 허용
재미있는 주장 Null 허용됨 Null 허용됨
재미있는 주장 Nonnull Nullable 또는 nonnull

가능하면 null이 아닌(@NonNull 등) 인수를 사용하는 것이 좋습니다.

메서드가 오버로드된 경우 모든 인수가 null이 아닌 것이 좋습니다.

public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }

이 규칙은 오버로드된 속성 설정자에도 적용됩니다. 기본 인수는 null이 아니어야 하며 속성 삭제는 별도의 메서드로 구현해야 합니다. 이렇게 하면 개발자가 필요하지 않은 후행 매개변수를 설정해야 하는 '무의미한' 호출이 방지됩니다.

public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)

// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()

컨테이너에는 null이 아닌 반환 유형(@NonNull 등)을 사용하는 것이 좋습니다.

Bundle 또는 Collection과 같은 컨테이너 유형의 경우 빈 컨테이너를 반환합니다(해당하는 경우 변경 불가능). null를 사용하여 컨테이너의 사용 가능 여부를 구분하는 경우 별도의 불리언 메서드를 제공하는 것이 좋습니다.

@NonNull
public Bundle getExtras() { ... }

get 및 set 쌍의 null 허용 여부 주석이 일치해야 함

단일 논리 속성의 get 및 set 메서드 쌍은 항상 null 허용 여부 주석에 동의해야 합니다. 이 가이드라인을 따르지 않으면 Kotlin의 속성 문법이 무효화되므로 기존 속성 메서드에 동의하지 않는 null 허용 여부 주석을 추가하는 것은 Kotlin 사용자에게 소스 호환성이 깨지는 변경사항입니다.

@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }

실패 또는 오류 조건에서 값 반환

모든 API는 앱이 오류에 반응하도록 허용해야 합니다. false, -1, null 또는 '문제가 발생했습니다'와 같은 기타 포괄적 값을 반환하면 개발자가 사용자 기대치를 설정하거나 필드에서 앱의 안정성을 정확하게 추적하는 데 실패한 이유를 충분히 알 수 없습니다. API를 설계할 때는 앱을 빌드한다고 가정해 보세요. 오류가 발생하면 API에서 사용자에게 표시하거나 적절하게 대응할 수 있는 충분한 정보를 제공하나요?

  1. 예외 메시지에 자세한 정보를 포함해도 되지만 (권장됨) 개발자가 오류를 적절하게 처리하기 위해 이를 파싱할 필요는 없습니다. 자세한 오류 코드나 기타 정보는 메서드로 노출해야 합니다.
  2. 선택한 오류 처리 옵션이 향후 새로운 오류 유형을 도입할 수 있는 유연성을 제공하는지 확인합니다. @IntDef의 경우 OTHER 또는 UNKNOWN 값을 포함해야 합니다. 새 코드를 반환할 때 호출자의 targetSdkVersion를 확인하여 앱에서 알지 못하는 오류 코드를 반환하지 않도록 할 수 있습니다. 예외의 경우 예외가 구현하는 공통 슈퍼클래스가 있어 해당 유형을 처리하는 코드가 하위 유형도 포착하고 처리할 수 있습니다.
  3. 개발자가 실수로 오류를 무시하기가 어렵거나 불가능해야 합니다. 값을 반환하여 오류를 전달하는 경우 @CheckResult로 메서드에 주석을 답니다.

개발자가 잘못한 일로 인해 실패 또는 오류 조건에 도달하는 경우(예: 입력 매개변수의 제약 조건을 무시하거나 관찰 가능한 상태를 확인하지 않음) ? extends RuntimeException를 발생시키는 것이 좋습니다.

비동기식으로 업데이트된 상태나 개발자가 제어할 수 없는 조건으로 인해 작업이 실패할 수 있는 경우 설정자 또는 작업 (예: perform) 메서드는 정수 상태 코드를 반환할 수 있습니다.

상태 코드는 포함 클래스에 public static final 필드로 정의되어야 하며, ERROR_ 접두사가 붙고 @hide @IntDef 주석에 열거되어야 합니다.

메서드 이름은 항상 주어가 아닌 동사로 시작해야 합니다.

메서드 이름은 항상 행위의 대상이 되는 객체가 아닌 동사 (예: get, create, reload 등)로 시작해야 합니다.

public void tableReload() {
  mTable.reload();
}
public void reloadTable() {
  mTable.reload();
}

배열보다 컬렉션 유형을 반환 또는 매개변수 유형으로 사용

일반적으로 유형이 지정된 컬렉션 인터페이스는 고유성 및 순서에 관한 강력한 API 계약, 일반 지원, 여러 개발자 친화적인 편의 메서드 등 배열에 비해 여러 이점을 제공합니다.

기본 요소 예외

요소가 기본형인 경우 자동 박싱 비용을 방지하기 위해 배열을 사용하는 것이 좋습니다. 박스 처리된 버전 대신 원시 프리미티브 가져오기 및 반환을 참고하세요.

성능에 민감한 코드의 예외

성능에 민감한 코드 (예: 그래픽 또는 기타 측정/레이아웃/그리기 API)에서 API를 사용하는 특정 시나리오에서는 할당 및 메모리 변동을 줄이기 위해 컬렉션 대신 배열을 사용할 수 있습니다.

Kotlin 예외

Kotlin 배열은 불변이고 Kotlin 언어는 배열에 관한 충분한 유틸리티 API를 제공하므로 Kotlin에서 액세스하도록 설계된 Kotlin API의 경우 배열이 ListCollection와 동등합니다.

@NonNull 컬렉션 선호

컬렉션 객체에는 항상 @NonNull을 사용하는 것이 좋습니다. 빈 컬렉션을 반환할 때는 적절한 Collections.empty 메서드를 사용하여 비용이 저렴하고, 올바르게 입력되고, 변경 불가능한 컬렉션 객체를 반환하세요.

유형 주석이 지원되는 경우 항상 컬렉션 요소에 @NonNull을 사용하세요.

컬렉션 대신 배열을 사용할 때도 @NonNull를 사용하는 것이 좋습니다 (이전 항목 참고). 객체 할당이 우려되는 경우 상수를 만들어 전달하세요. 빈 배열은 변경할 수 없습니다. 예:

private static final int[] EMPTY_USER_IDS = new int[0];

@NonNull
public int[] getUserIds() {
  int [] userIds = mService.getUserIds();
  return userIds != null ? userIds : EMPTY_USER_IDS;
}

컬렉션 변경 가능 여부

Kotlin API는 API 계약에서 변경 가능한 반환 유형을 명시적으로 요구하지 않는 한 기본적으로 컬렉션에 읽기 전용 (Mutable 아님) 반환 유형을 선호해야 합니다.

하지만 Java API는 기본적으로 변경 가능 반환 유형을 선호해야 합니다. Java API의 Android 플랫폼 구현이 아직 변경 불가능한 컬렉션의 편리한 구현을 제공하지 않기 때문입니다. 이 규칙의 예외는 변경할 수 없는 Collections.empty 반환 유형입니다. 클라이언트가 의도적으로 또는 실수로 변경 가능성을 악용하여 API의 의도된 사용 패턴을 깨뜨릴 수 있는 경우 Java API는 컬렉션의 얕은 복사본을 반환하는 것을 적극적으로 고려해야 합니다.

@Nullable
public PermissionInfo[] getGrantedPermissions() {
  return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
  if (mPermissions == null) {
    return Collections.emptySet();
  }
  return new ArraySet<>(mPermissions);
}

명시적으로 변경 가능한 반환 유형

컬렉션을 반환하는 API는 반환 후 반환된 컬렉션 객체를 수정하지 않는 것이 좋습니다. 반환된 컬렉션을 어떤 방식으로든 변경하거나 재사용해야 하는 경우(예: 변경 가능한 데이터 세트의 적응형 뷰) 콘텐츠가 변경될 수 있는 시기의 정확한 동작을 명시적으로 문서화하거나 확립된 API 명명 규칙을 따라야 합니다.

/**
 * Returns a view of this object as a list of [Item]s.
 */
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)

Kotlin .asFoo() 규칙은 아래에 설명되어 있으며, 원래 컬렉션이 변경되면 .asList()에서 반환된 컬렉션이 변경될 수 있습니다.

반환된 데이터 유형 객체의 변경 가능성

컬렉션을 반환하는 API와 마찬가지로 데이터 유형 객체를 반환하는 API는 반환 후 반환된 객체의 속성을 수정하지 않는 것이 좋습니다.

val tempResult = DataContainer()

fun add(other: DataContainer): DataContainer {
  tempResult.innerValue = innerValue + other.innerValue
  return tempResult
}
fun add(other: DataContainer): DataContainer {
  return DataContainer(innerValue + other.innerValue)
}

매우 제한적인 경우 일부 성능에 민감한 코드에서 객체 풀링 또는 재사용의 이점을 누릴 수 있습니다. 객체 풀 데이터 구조를 직접 작성하지 말고 재사용된 객체를 공개 API에 노출하지 마세요. 어떤 경우든 동시 액세스를 관리할 때 매우 주의해야 합니다.

vararg 매개변수 유형 사용

개발자가 동일한 유형의 관련 매개변수를 여러 개 전달하기 위한 목적으로만 호출 사이트에서 배열을 만들 가능성이 있는 경우 Kotlin 및 Java API 모두 vararg를 사용하는 것이 좋습니다.

public void setFeatures(Feature[] features) { ... }

// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }

// Developer code
setFeatures(Features.A, Features.B, Features.C);

방어적 복사

vararg 매개변수의 Java 및 Kotlin 구현은 모두 동일한 배열 지원 바이트 코드로 컴파일되므로 변경 가능한 배열을 사용하여 Java 코드에서 호출할 수 있습니다. 필드나 익명 내부 클래스에 유지되는 경우 API 설계자는 배열 매개변수의 방어적 얕은 복사본을 만들도록 적극 권장됩니다.

public void setValues(SomeObject... values) {
   this.values = Arrays.copyOf(values, values.length);
}

방어적 복사본을 만들어도 초기 메서드 호출과 복사본 생성 간의 동시 수정으로부터 보호되지 않으며 배열에 포함된 객체의 변형으로부터도 보호되지 않습니다.

컬렉션 유형 매개변수 또는 반환된 유형으로 올바른 시맨틱 제공

List<Foo>이 기본 옵션이지만 추가 의미를 제공하기 위해 다른 유형을 고려하세요.

  • API가 요소의 순서에 무관하고 중복을 허용하지 않거나 중복이 의미가 없는 경우 Set<Foo>를 사용합니다.

  • API가 순서에 무관하고 중복을 허용하는 경우 Collection<Foo>,

Kotlin 변환 함수

Kotlin은 .toFoo().asFoo()를 자주 사용하여 Foo이 변환의 반환 유형 이름인 기존 객체에서 다른 유형의 객체를 가져옵니다. 이는 익숙한 JDK Object.toString()와 일치합니다. Kotlin은 25.toFloat()와 같은 기본 변환에 이를 사용하여 한 단계 더 나아갑니다.

.toFoo().asFoo()라는 전환의 차이는 다음과 같습니다.

새 독립 객체를 만들 때는 .toFoo() 사용

.toString()와 마찬가지로 'to' 변환은 독립적인 새 객체를 반환합니다. 나중에 원본 객체가 수정되더라도 새 객체에는 이러한 변경사항이 반영되지 않습니다. 마찬가지로 new 객체가 나중에 수정되더라도 old 객체에는 이러한 변경사항이 반영되지 않습니다.

fun Foo.toBundle(): Bundle = Bundle().apply {
    putInt(FOO_VALUE_KEY, value)
}

종속 래퍼, 데코레이터 객체 또는 캐스트를 만들 때 .asFoo() 사용

Kotlin에서의 캐스팅은 as 키워드를 사용하여 실행됩니다. 인터페이스의 변경사항을 반영하지만 ID의 변경사항은 반영하지 않습니다. 확장 함수에서 접두사로 사용되면 .asFoo()은 수신자를 장식합니다. 원래 수신자 객체의 변경사항은 asFoo()에서 반환된 객체에 반영됩니다. 새 Foo 객체의 변이는 원래 객체에 반영될 수 있습니다.

fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
    collect {
        emit(it)
    }
}

변환 함수는 확장 함수로 작성해야 함

수신자와 결과 클래스 정의 모두 외부에 변환 함수를 작성하면 유형 간 결합이 줄어듭니다. 이상적인 변환에는 원본 객체에 대한 공개 API 액세스만 필요합니다. 이는 개발자가 원하는 유형으로 유사한 변환을 작성할 수 있음을 예시를 통해 보여줍니다.

적절한 특정 예외 발생

메서드는 java.lang.Exception 또는 java.lang.Throwable과 같은 일반 예외를 발생시켜서는 안 됩니다. 대신 개발자가 지나치게 광범위하지 않은 예외를 처리할 수 있도록 java.lang.NullPointerException과 같은 적절한 특정 예외를 사용해야 합니다.

공개적으로 호출된 메서드에 직접 제공된 인수와 관련이 없는 오류는 java.lang.IllegalArgumentException 또는 java.lang.NullPointerException 대신 java.lang.IllegalStateException를 발생시켜야 합니다.

리스너 및 콜백

다음은 리스너 및 콜백 메커니즘에 사용되는 클래스 및 메서드에 관한 규칙입니다.

콜백 클래스 이름은 단수여야 합니다.

MyObjectCallbacks 대신 MyObjectCallback를 사용합니다.

콜백 메서드 이름은 on 형식이어야 합니다.

onFooEventFooEvent가 발생하고 있으며 콜백이 이에 따라 작동해야 함을 나타냅니다.

과거형과 현재형은 타이밍 동작을 설명해야 합니다.

이벤트에 관한 콜백 메서드는 이벤트가 이미 발생했는지 아니면 발생하는 중인지 나타내도록 이름을 지정해야 합니다.

예를 들어 클릭 작업이 실행된 후 메서드가 호출되면 다음과 같습니다.

public void onClicked()

하지만 메서드가 클릭 작업을 실행하는 역할을 하는 경우 다음을 충족해야 합니다.

public boolean onClick()

콜백 등록

리스너나 콜백을 객체에서 추가하거나 삭제할 수 있는 경우 연결된 메서드의 이름은 add 및 remove또는 register 및 unregister여야 합니다. 클래스 또는 동일한 패키지의 다른 클래스에서 사용하는 기존 규칙을 따릅니다. 이러한 선례가 없는 경우 추가 및 삭제를 선호합니다.

콜백을 등록하거나 등록 취소하는 메서드는 콜백 유형의 전체 이름을 지정해야 합니다.

public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);

콜백의 게터 방지

getFooCallback() 메서드를 추가하지 마세요. 이는 개발자가 기존 콜백을 자체 대체와 연결하려는 경우에 유혹적인 비상구이지만 취약하며 구성요소 개발자가 현재 상태를 추론하기 어렵게 만듭니다. 예를 들면 다음과 같습니다.

  • 개발자 A가 setFooCallback(a)에 전화를 겁니다.
  • 개발자 B가 setFooCallback(new B(getFooCallback()))를 호출합니다.
  • 개발자 A는 콜백 a를 삭제하려고 하지만 B의 유형을 알지 못하고 B가 래핑된 콜백의 이러한 수정을 허용하도록 빌드되지 않았기 때문에 삭제할 방법이 없습니다.

콜백 디스패치를 제어하기 위해 실행자 허용

명시적인 스레딩 기대치가 없는 콜백을 등록할 때는 (UI 툴킷 외부의 거의 모든 곳) 개발자가 콜백이 호출될 스레드를 지정할 수 있도록 등록의 일부로 Executor 매개변수를 포함하는 것이 적극 권장됩니다.

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

일반적인 선택적 매개변수 관련 가이드라인과 달리 매개변수 목록의 마지막 인수가 아니더라도 Executor를 생략하는 오버로드를 제공해도 됩니다. Executor가 제공되지 않으면 Looper.getMainLooper()를 사용하여 기본 스레드에서 콜백을 호출해야 하며, 이는 연결된 오버로드된 메서드에 문서화되어야 합니다.

/**
 * ...
 * Note that the callback will be executed on the main thread using
 * {@link Looper.getMainLooper()}. To specify the execution thread, use
 * {@link registerFooCallback(Executor, FooCallback)}.
 * ...
 */
public void registerFooCallback(
    @NonNull FooCallback callback)

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Executor 구현 주의사항: 다음은 유효한 실행기입니다.

public class SynchronousExecutor implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

즉, 이 형식을 취하는 API를 구현할 때 앱 프로세스 측의 수신 바인더 객체 구현은 앱에서 제공한 Executor에서 앱의 콜백을 호출하기 전에 Binder.clearCallingIdentity()를 호출해야 합니다. 이렇게 하면 권한 확인을 위해 바인더 ID (예: Binder.getCallingUid())를 사용하는 앱 코드가 앱에 실행되는 코드를 올바르게 귀속시키고 앱을 호출하는 시스템 프로세스에는 귀속시키지 않습니다. API 사용자가 호출자의 UID 또는 PID 정보를 원하는 경우 이는 제공된 Executor이 실행된 위치를 기반으로 암시적으로 제공되는 것이 아니라 API 노출 영역의 명시적 부분이어야 합니다.

API에서 Executor를 지정하는 것을 지원해야 합니다. 성능이 중요한 경우 앱은 API의 의견과 함께 즉시 또는 동기적으로 코드를 실행해야 할 수 있습니다. Executor를 수락하면 이 작업이 허용됩니다. 추가 HandlerThread를 만들거나 트램펄린과 유사한 것을 방어적으로 만들면 이 바람직한 사용 사례가 무효화됩니다.

앱이 자체 프로세스에서 리소스를 많이 소모하는 코드를 실행하는 경우 허용하세요. 앱 개발자가 사용자의 제한을 극복하기 위해 찾는 해결 방법은 장기적으로 지원하기가 훨씬 어렵습니다.

단일 콜백 예외: 보고되는 이벤트의 특성상 단일 콜백 인스턴스만 지원해야 하는 경우 다음 스타일을 사용하세요.

public void setFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

public void clearFooCallback()

Handler 대신 Executor 사용

이전에는 Android의 Handler가 콜백 실행을 특정 Looper 스레드로 리디렉션하는 표준으로 사용되었습니다. 이 표준은 대부분의 앱 개발자가 자체 스레드 풀을 관리하여 앱에서 사용할 수 있는 유일한 Looper 스레드가 기본 또는 UI 스레드가 되므로 Executor를 선호하도록 변경되었습니다. Executor를 사용하여 개발자가 기존/선호하는 실행 컨텍스트를 재사용하는 데 필요한 제어 권한을 제공하세요.

kotlinx.coroutines 또는 RxJava와 같은 최신 동시성 라이브러리는 필요할 때 자체 디스패치를 실행하는 자체 일정 예약 메커니즘을 제공하므로 이중 스레드 홉으로 인한 지연 시간을 방지하기 위해 직접 실행기 (예: Runnable::run)를 사용할 수 있는 기능을 제공하는 것이 중요합니다. 예를 들어 Handler을 사용하여 Looper 스레드에 게시하는 홉 하나와 앱의 동시 실행 프레임워크에서 발생하는 또 다른 홉이 있습니다.

이 가이드라인의 예외는 드뭅니다. 일반적인 예외 이의신청은 다음과 같습니다.

이벤트에 epoll를 위해 Looper가 필요하므로 Looper를 사용해야 합니다. 이 예외 요청은 이 상황에서 Executor의 이점을 실현할 수 없으므로 승인됩니다.

앱 코드가 이벤트를 게시하는 스레드를 차단하지 않도록 하고 싶습니다. 이 예외 요청은 일반적으로 앱 프로세스에서 실행되는 코드에 대해 허용되지 않습니다. 이 값을 잘못 가져오는 앱은 전체 시스템 상태에 영향을 미치지 않고 자체적으로만 손해를 입습니다. 이를 올바르게 처리하거나 일반적인 동시 실행 프레임워크를 사용하는 앱은 추가 지연 시간 페널티를 받지 않습니다.

Handler은 동일한 클래스의 다른 유사한 API와 로컬로 일치합니다. 이 예외 요청은 상황에 따라 승인됩니다. Handler 구현을 새 Executor 구현을 사용하도록 이전하여 Executor 기반 오버로드를 추가하는 것이 좋습니다. (myHandler::post는 유효한 Executor입니다.) 클래스 크기, 기존 Handler 메서드 수, 개발자가 새 메서드와 함께 기존 Handler 기반 메서드를 사용할 가능성에 따라 새 Handler 기반 메서드를 추가하는 예외가 부여될 수 있습니다.

등록의 대칭

항목을 추가하거나 등록하는 방법이 있다면 항목을 삭제하거나 등록 취소하는 방법도 있어야 합니다. 메서드

registerThing(Thing)

일치하는

unregisterThing(Thing)

요청 식별자 제공

개발자가 콜백을 재사용하는 것이 합리적인 경우 콜백을 요청에 연결하는 식별자 객체를 제공합니다.

class RequestParameters {
  public int getId() { ... }
}

class RequestExecutor {
  public void executeRequest(
    RequestParameters parameters,
    Consumer<RequestParameters> onRequestCompletedListener) { ... }
}

다중 메서드 콜백 객체

다중 메서드 콜백은 interface를 선호해야 하며 이전에 출시된 인터페이스에 추가할 때는 default 메서드를 사용해야 합니다. 이전에는 Java 7에 default 메서드가 없었기 때문에 이 가이드라인에서 abstract class를 권장했습니다.

public interface MostlyOptionalCallback {
  void onImportantAction();
  default void onOptionalInformation() {
    // Empty stub, this method is optional.
  }
}

차단되지 않는 함수 호출을 모델링할 때 android.os.OutcomeReceiver 사용

OutcomeReceiver<R,E>은 성공 시 결과 값 R를 보고하고 그렇지 않은 경우 E : Throwable를 보고합니다. 일반 메서드 호출이 할 수 있는 것과 동일합니다. 결과를 반환하거나 예외를 발생시키는 차단 메서드를 비차단 비동기 메서드로 변환할 때 OutcomeReceiver를 콜백 유형으로 사용합니다.

interface FooType {
  // Before:
  public FooResult requestFoo(FooRequest request);

  // After:
  public void requestFooAsync(FooRequest request, Executor executor,
      OutcomeReceiver<FooResult, Throwable> callback);
}

이 방식으로 변환된 비동기 메서드는 항상 void를 반환합니다. requestFoo가 반환하는 결과는 제공된 executor에서 호출하여 requestFooAsynccallback 매개변수의 OutcomeReceiver.onResult에 대신 보고됩니다. requestFoo에서 발생할 수 있는 예외는 대신 동일한 방식으로 OutcomeReceiver.onError 메서드에 보고됩니다.

비동기 메서드 결과를 보고하는 데 OutcomeReceiver를 사용하면 androidx.core:core-ktxContinuation.asOutcomeReceiver 확장 프로그램을 사용하여 비동기 메서드용 Kotlin suspend fun 래퍼도 제공됩니다.

suspend fun FooType.requestFoo(request: FooRequest): FooResult =
  suspendCancellableCoroutine { continuation ->
    requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
  }

이러한 확장 프로그램을 사용하면 Kotlin 클라이언트가 호출 스레드를 차단하지 않고 일반 함수 호출의 편리함으로 비차단 비동기 메서드를 호출할 수 있습니다. 플랫폼 API용 이러한 1:1 확장 프로그램은 표준 버전 호환성 검사 및 고려사항과 결합될 때 Jetpack의 androidx.core:core-ktx 아티팩트의 일부로 제공될 수 있습니다. 자세한 내용, 취소 고려사항, 샘플은 asOutcomeReceiver 문서를 참고하세요.

작업이 완료될 때 결과를 반환하거나 예외를 발생시키는 메서드의 의미와 일치하지 않는 비동기 메서드는 OutcomeReceiver을 콜백 유형으로 사용해서는 안 됩니다. 대신 다음 섹션에 나열된 다른 옵션 중 하나를 고려해 보세요.

새 단일 추상 메서드 (SAM) 유형을 만드는 것보다 기능적 인터페이스를 선호하세요.

API 수준 24에서는 콜백 람다로 사용하기에 적합한 Consumer<T>와 같은 일반 SAM 인터페이스를 제공하는 java.util.function.*(참조 문서) 유형이 추가되었습니다. 대부분의 경우 새 SAM 인터페이스를 만들면 유형 안전성이나 의도 전달 측면에서 가치가 거의 없으며 불필요하게 Android API 노출 영역이 확장됩니다.

새 인터페이스를 만드는 대신 다음 일반 인터페이스를 사용하는 것이 좋습니다.

SAM 매개변수 배치

메서드가 추가 매개변수로 오버로드되는 경우에도 Kotlin에서 자연스럽게 사용할 수 있도록 SAM 매개변수를 마지막에 배치해야 합니다.

public void schedule(Runnable runnable)

public void schedule(int delay, Runnable runnable)

문서

API의 공개 문서 (Javadoc)에 관한 규칙입니다.

모든 공개 API는 문서화되어야 합니다.

모든 공개 API에는 개발자가 API를 사용하는 방법을 설명하는 충분한 문서가 있어야 합니다. 개발자가 자동 완성 기능을 사용하거나 API 참조 문서를 탐색하는 중에 메서드를 찾았으며 인접한 API 노출 영역 (예: 동일한 클래스)에서 최소한의 컨텍스트를 가지고 있다고 가정합니다.

메서드

메서드 매개변수와 반환 값은 각각 @param@return 문서 주석을 사용하여 문서화해야 합니다. Javadoc 본문을 '이 메서드는...'이 앞에 오는 것처럼 포맷합니다.

메서드가 매개변수를 사용하지 않고 특별한 고려사항이 없으며 메서드 이름이 나타내는 것을 반환하는 경우 @return를 생략하고 다음과 유사한 문서를 작성할 수 있습니다.

/**
 * Returns the priority of the thread.
 */
@IntRange(from = 1, to = 10)
public int getPriority() { ... }

문서는 관련 상수, 메서드, 기타 요소에 대한 다른 문서로 연결되어야 합니다. 일반 텍스트 단어가 아닌 Javadoc 태그 (예: @see{@link foo})를 사용합니다.

다음 소스 예시를 참고하세요.

public static final int FOO = 0;
public static final int BAR = 1;

일반 텍스트 또는 코드 글꼴을 사용하지 마세요.

/**
 * Sets value to one of FOO or <code>BAR</code>.
 *
 * @param value the value being set, one of FOO or BAR
 */
public void setValue(int value) { ... }

대신 다음 링크를 사용하세요.

/**
 * Sets value to one of {@link #FOO} or {@link #BAR}.
 *
 * @param value the value being set
 */
public void setValue(@ValueType int value) { ... }

매개변수에 @ValueType과 같은 IntDef 주석을 사용하면 허용된 유형을 지정하는 문서가 자동으로 생성됩니다. IntDef에 대한 자세한 내용은 주석에 관한 안내를 참고하세요.

Javadoc 추가 시 update-api 또는 docs 타겟 실행

이 규칙은 @link 또는 @see 태그를 추가할 때 특히 중요하며 출력이 예상대로 표시되는지 확인합니다. Javadoc의 오류 출력은 잘못된 링크로 인해 발생하는 경우가 많습니다. update-api 또는 docs Make 타겟이 이 검사를 실행하지만 Javadoc만 변경하고 update-api 타겟을 실행할 필요가 없는 경우 docs 타겟이 더 빠를 수 있습니다.

{@code foo}를 사용하여 Java 값 구분

true, false, null와 같은 Java 값을 {@code...}로 래핑하여 문서 텍스트와 구분합니다.

Kotlin 소스에서 문서를 작성할 때는 마크다운에서와 같이 백틱으로 코드를 래핑할 수 있습니다.

@param 및 @return 요약은 단일 문장 프래그먼트여야 합니다.

매개변수 및 반환 값 요약은 소문자로 시작해야 하며 단일 문장 조각만 포함해야 합니다. 한 문장을 초과하는 추가 정보가 있는 경우 메서드 Javadoc 본문으로 이동하세요.

/**
 * @param e The element to be appended to the list. This must not be
 *       null. If the list contains no entries, this element will
 *       be added at the beginning.
 * @return This method returns true on success.
 */

다음과 같이 변경해야 합니다.

/**
 * @param e element to be appended to this list, must be non-{@code null}
 * @return {@code true} on success, {@code false} otherwise
 */

Docs 주석에 설명이 필요함

주석 @hide@removed이 공개 API에서 숨겨진 이유를 문서화합니다. @deprecated 주석으로 표시된 API 요소를 대체하는 방법을 설명하는 안내를 포함합니다.

@throws를 사용하여 예외 문서화

메서드가 확인된 예외(예: IOException)를 발생시키면 @throws로 예외를 문서화합니다. Java 클라이언트에서 사용하도록 설계된 Kotlin 소스 API의 경우 @Throws로 함수에 주석을 답니다.

메서드에서 예방 가능한 오류를 나타내는 확인되지 않은 예외(예: IllegalArgumentException 또는 IllegalStateException)를 발생시키는 경우 예외가 발생한 이유를 설명하여 예외를 문서화합니다. 발생한 예외는 발생한 이유도 나타내야 합니다.

확인되지 않은 예외의 특정 사례는 암시적인 것으로 간주되어 문서화할 필요가 없습니다. 예를 들어 인수가 API 계약을 메서드 서명에 삽입하는 @IntDef 또는 유사한 주석과 일치하지 않는 NullPointerException 또는 IllegalArgumentException가 있습니다.

/**
 * ...
 * @throws IOException If it cannot find the schema for {@code toVersion}
 * @throws IllegalStateException If the schema validation fails
 */
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
    boolean validateDroppedTables, Migration... migrations) throws IOException {
  // ...
  if (!dbPath.exists()) {
    throw new IllegalStateException("Cannot find the database file for " + name
        + ". Before calling runMigrations, you must first create the database "
        + "using createDatabase.");
  }
  // ...

또는 Kotlin에서 다음과 같이 작성합니다.

/**
 * ...
 * @throws IOException If something goes wrong reading the file, such as a bad
 *                     database header or missing permissions
 */
@Throws(IOException::class)
fun readVersion(databaseFile: File): Int {
  // ...
  val read = input.read(buffer)
    if (read != 4) {
      throw IOException("Bad database header, unable to read 4 bytes at " +
          "offset 60")
    }
  }
  // ...

메서드가 예외를 발생시킬 수 있는 비동기 코드를 호출하는 경우 개발자가 이러한 예외를 파악하고 이에 대응하는 방법을 고려하세요. 일반적으로 여기에는 콜백에 예외를 전달하고 예외를 수신하는 메서드에서 발생한 예외를 문서화하는 작업이 포함됩니다. 비동기 예외는 실제로 주석이 달린 메서드에서 다시 발생하지 않는 한 @throws로 문서화하면 안 됩니다.

문서의 첫 번째 문장을 마침표로 끝냅니다.

Doclava 도구는 문서를 단순하게 파싱하여 클래스 문서 상단의 빠른 설명에 사용되는 첫 번째 문장인 시놉시스 문서를 마침표(.) 뒤에 공백이 표시되는 즉시 종료합니다. 이로 인해 두 가지 문제가 발생합니다.

  • 짧은 문서가 마침표로 끝나지 않고 해당 구성원이 도구에서 선택한 문서를 상속받은 경우 시놉시스도 상속된 문서를 선택합니다. 예를 들어 R.attr 문서actionBarTabStyle를 참고하세요. 여기에는 시놉시스에 추가된 측정기준에 대한 설명이 있습니다.
  • Doclava는 'g.' 뒤에 시놉시스 문서를 종료하므로 같은 이유로 첫 번째 문장에서 '예'를 사용하지 마세요. 예를 들어 View.javaTEXT_ALIGNMENT_CENTER를 참고하세요. Metalava는 마침표 뒤에 공백이 없는 문자를 삽입하여 이 오류를 자동으로 수정합니다. 하지만 처음부터 이 실수를 하지 않는 것이 좋습니다.

HTML로 렌더링되도록 문서 서식 지정

Javadoc은 HTML로 렌더링되므로 다음과 같이 문서를 형식화하세요.

  • 줄바꿈은 명시적 <p> 태그를 사용해야 합니다. 닫는 </p> 태그를 추가하지 마세요.

  • ASCII를 사용하여 목록이나 표를 렌더링하지 마세요.

  • 목록은 정렬되지 않은 목록과 정렬된 목록에 각각 <ul> 또는 <ol>를 사용해야 합니다. 각 항목은 <li> 태그로 시작해야 하지만 닫는 </li> 태그는 필요하지 않습니다. 마지막 항목 뒤에는 닫는 </ul> 또는 </ol> 태그가 필요합니다.

  • 표는 <table>, <tr>(행), <th>(헤더), <td>(셀)을 사용해야 합니다. 모든 표 태그에는 일치하는 종료 태그가 필요합니다. 태그에 class="deprecated"를 사용하여 지원 중단을 나타낼 수 있습니다.

  • 인라인 코드 글꼴을 만들려면 {@code foo}를 사용합니다.

  • 코드 블록을 만들려면 <pre>를 사용합니다.

  • <pre> 블록 내의 모든 텍스트는 브라우저에서 파싱되므로 괄호 <>에 주의하세요. &lt;&gt; HTML 엔티티를 사용하여 이스케이프 처리할 수 있습니다.

  • 또는 문제가 되는 섹션을 {@code foo}로 래핑하는 경우 코드 스니펫에 원시 괄호 <>를 남겨둘 수 있습니다. 예를 들면 다음과 같습니다.

    <pre>{@code <manifest>}</pre>
    

API 참조 스타일 가이드 준수

클래스 요약, 메서드 설명, 매개변수 설명, 기타 항목의 스타일을 일관되게 유지하려면 Javadoc 도구의 문서 주석 작성 방법에 나와 있는 공식 Java 언어 가이드라인의 권장사항을 따르세요.

Android 프레임워크 관련 규칙

이러한 규칙은 API와 Android 프레임워크에 내장된 동작 (예: Bundle 또는 Parcelable)에 특화된 API, 패턴, 데이터 구조에 관한 것입니다.

의도 빌더는 create*Intent() 패턴을 사용해야 합니다.

인텐트 생성자는 createFooIntent()라는 이름의 메서드를 사용해야 합니다.

새 범용 데이터 구조를 만드는 대신 번들 사용

임의의 키를 유형이 지정된 값 매핑으로 나타내는 새로운 범용 데이터 구조를 만들지 마세요. 대신 Bundle를 사용하는 것이 좋습니다.

이는 일반적으로 플랫폼이 채널을 통해 전송된 데이터를 읽지 않고 API 계약이 플랫폼 외부에서 부분적으로 정의될 수 있는 (예: Jetpack 라이브러리) 비플랫폼 앱과 서비스 간의 통신 채널 역할을 하는 플랫폼 API를 작성할 때 발생합니다.

플랫폼에서 데이터를 읽는 경우 Bundle를 사용하지 말고 강타입 데이터 클래스를 사용하는 것이 좋습니다.

Parcelable 구현에는 공개 CREATOR 필드가 있어야 합니다.

Parcelable 인플레이션은 원시 생성자가 아닌 CREATOR를 통해 노출됩니다. 클래스가 Parcelable를 구현하는 경우 CREATOR 필드도 공개 API여야 하며 Parcel 인수를 사용하는 클래스 생성자는 비공개여야 합니다.

UI 문자열에 CharSequence 사용

문자열이 사용자 인터페이스에 표시될 때 CharSequence를 사용하여 Spannable 인스턴스를 허용합니다.

사용자에게 표시되지 않는 키나 다른 라벨 또는 값인 경우 String를 사용하면 됩니다.

열거형 사용 피하기

IntDef는 모든 플랫폼 API에서 열거형보다 우선하여 사용해야 하며, 번들 해제된 라이브러리 API에서 적극적으로 고려해야 합니다. 새 값이 추가되지 않는다고 확신하는 경우에만 열거형을 사용하세요.

IntDef의 이점:

  • 시간이 지남에 따라 값을 추가할 수 있습니다.
    • 플랫폼에 추가된 enum 값으로 인해 더 이상 완전하지 않게 되면 Kotlin when 문이 런타임에 실패할 수 있습니다.
  • 런타임에 사용되는 클래스나 객체가 없으며, 기본 요소만 사용됩니다.
    • R8 또는 축소는 번들 해제된 라이브러리 API의 경우 이 비용을 방지할 수 있지만 이 최적화는 플랫폼 API 클래스에 영향을 줄 수 없습니다.

열거형의 이점

  • Java, Kotlin의 관용적인 언어 기능
  • 포괄적인 스위치, when 문 사용을 사용 설정합니다.
    • 참고 - 값은 시간이 지나도 변경되지 않아야 합니다(이전 목록 참고).
  • 명확한 범위 지정 및 검색 가능한 이름 지정
  • 컴파일 시간 확인을 사용 설정합니다.
    • 예를 들어 값을 반환하는 Kotlin의 when
  • 인터페이스를 구현하고, 정적 도우미가 있고, 멤버 또는 확장 메서드를 노출하고, 필드를 노출할 수 있는 작동 클래스입니다.

Android 패키지 레이어링 계층 구조 따르기

android.* 패키지 계층 구조에는 하위 수준 패키지가 상위 수준 패키지에 종속될 수 없는 암시적 순서가 있습니다.

Google, 다른 회사, 해당 회사의 제품 언급 피하기

Android 플랫폼은 오픈소스 프로젝트이며 공급업체 중립성을 목표로 합니다. API는 일반적이어야 하며 필수 권한이 있는 시스템 통합자나 앱에서 동일하게 사용할 수 있어야 합니다.

Parcelable 구현은 최종이어야 함

플랫폼에서 정의한 Parcelable 클래스는 항상 framework.jar에서 로드되므로 앱이 Parcelable 구현을 재정의하려고 하면 유효하지 않습니다.

전송 앱이 Parcelable을 확장하면 수신 앱에는 압축을 해제할 발신자의 맞춤 구현이 없습니다. 하위 호환성 관련 참고사항: 클래스가 이전에는 final이 아니었지만 공개적으로 사용 가능한 생성자가 없는 경우에도 final로 표시할 수 있습니다.

시스템 프로세스를 호출하는 메서드는 RemoteException을 RuntimeException으로 다시 발생시켜야 함

RemoteException는 일반적으로 내부 AIDL에 의해 발생하며 시스템 프로세스가 종료되었거나 앱이 너무 많은 데이터를 전송하려고 시도하고 있음을 나타냅니다. 두 경우 모두 앱이 보안 또는 정책 결정을 유지하지 못하도록 공개 API가 RuntimeException로 다시 발생시켜야 합니다.

Binder 호출의 다른 쪽이 시스템 프로세스인 경우 이 상용구 코드가 권장사항입니다.

try {
    ...
} catch (RemoteException e) {
    throw e.rethrowFromSystemServer();
}

API 변경사항에 대한 구체적인 예외 발생

공개 API 동작은 API 수준에 따라 변경될 수 있으며 앱 비정상 종료를 일으킬 수 있습니다 (예: 새로운 보안 정책을 적용하기 위해).

API가 이전에 유효했던 요청에 대해 예외를 발생시켜야 하는 경우 일반 예외 대신 새로운 특정 예외를 발생시킵니다. 예를 들어 SecurityException 대신 ExportedFlagRequired를 사용합니다 (ExportedFlagRequiredSecurityException를 확장할 수 있음).

이를 통해 앱 개발자와 도구가 API 동작 변경사항을 감지할 수 있습니다.

클론 대신 복사 생성자 구현

Java clone() 메서드는 Object 클래스에서 제공하는 API 계약이 없고 clone()를 사용하는 클래스를 확장하는 데 어려움이 있으므로 사용하지 않는 것이 좋습니다. 대신 동일한 유형의 객체를 사용하는 복사 생성자를 사용하세요.

/**
 * Constructs a shallow copy of {@code other}.
 */
public Foo(Foo other)

생성을 위해 빌더를 사용하는 클래스는 사본을 수정할 수 있도록 빌더 복사 생성자를 추가하는 것이 좋습니다.

public class Foo {
    public static final class Builder {
        /**
         * Constructs a Foo builder using data from {@code other}.
         */
        public Builder(Foo other)

FileDescriptor 대신 ParcelFileDescriptor 사용

java.io.FileDescriptor 객체의 소유권 정의가 좋지 않아 종료 후 사용 버그가 발생할 수 있습니다. 대신 API는 ParcelFileDescriptor 인스턴스를 반환하거나 허용해야 합니다. 레거시 코드는 필요한 경우 dup() 또는 getFileDescriptor()를 사용하여 PFD와 FD 간에 변환할 수 있습니다.

홀수 크기의 숫자 값 사용하지 않기

short 또는 byte 값을 직접 사용하지 마세요. 향후 API를 발전시킬 수 있는 방법을 제한하는 경우가 많기 때문입니다.

BitSet 사용 피하기

java.util.BitSet는 구현에는 적합하지만 공개 API에는 적합하지 않습니다. 변경 가능하고, 빈번한 메서드 호출을 위한 할당이 필요하며, 각 비트가 나타내는 항목에 관한 의미론적 의미를 제공하지 않습니다.

고성능 시나리오의 경우 @IntDef와 함께 int 또는 long을 사용하세요. 성능이 낮은 시나리오의 경우 Set<EnumType>를 고려하세요. 원시 바이너리 데이터에는 byte[]를 사용합니다.

android.net.Uri 선호

android.net.Uri는 Android API에서 URI에 선호되는 캡슐화입니다.

java.net.URI는 URI 파싱에 지나치게 엄격하므로 피하고 java.net.URL는 동등성 정의가 심각하게 손상되었으므로 사용하지 마세요.

@IntDef, @LongDef 또는 @StringDef로 표시된 주석 숨기기

@IntDef, @LongDef 또는 @StringDef로 표시된 주석은 API에 전달할 수 있는 유효한 상수 집합을 나타냅니다. 하지만 API 자체로 내보내면 컴파일러가 상수를 인라인 처리하고 이제 쓸모없어진 값만 주석의 API 스텁(플랫폼용) 또는 JAR(라이브러리용)에 남습니다.

따라서 이러한 주석의 사용은 플랫폼에서는 @hide 문서 주석으로, 라이브러리에서는 @RestrictTo.Scope.LIBRARY) 코드 주석으로 표시해야 합니다. API 스텁이나 JAR에 표시되지 않도록 두 경우 모두에서 @Retention(RetentionPolicy.SOURCE)로 표시해야 합니다.

@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
  STREAM_TYPE_FULL_IMAGE_DATA,
  STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}

플랫폼 SDK 및 라이브러리 AAR을 빌드할 때 도구는 주석을 추출하고 컴파일된 소스와 별도로 번들로 묶습니다. Android 스튜디오는 번들 형식으로 읽고 유형 정의를 적용합니다.

새 설정 제공업체 키를 추가하지 않음

Settings.Global, Settings.System 또는 Settings.Secure의 새 키를 노출하지 않습니다.

대신 관련 클래스(일반적으로 '관리자' 클래스)에 적절한 getter 및 setter Java API를 추가하세요. 필요에 따라 클라이언트에 변경사항을 알리는 리스너 메커니즘이나 브로드캐스트를 추가합니다.

SettingsProvider 설정에는 getter/setter와 비교하여 다음과 같은 여러 문제가 있습니다.

  • 타입 안전성이 없습니다.
  • 기본값을 제공하는 통합된 방법이 없습니다.
  • 권한을 맞춤설정할 적절한 방법이 없습니다.
    • 예를 들어 맞춤 권한으로 설정을 보호할 수는 없습니다.
  • 맞춤 로직을 올바르게 추가할 방법이 없습니다.
    • 예를 들어 설정 B의 값에 따라 설정 A의 값을 변경할 수는 없습니다.

예를 들어 Settings.Secure.LOCATION_MODE는 오랫동안 존재해 왔지만 위치팀에서는 적절한 Java API LocationManager.isLocationEnabled()MODE_CHANGED_ACTION 브로드캐스트를 위해 이를 지원 중단했습니다. 이로 인해 팀의 유연성이 훨씬 높아졌으며 API의 의미가 훨씬 명확해졌습니다.

Activity 및 AsyncTask를 확장하지 않음

AsyncTask는 구현 세부정보입니다. 대신 리스너 또는 androidx에서는 ListenableFuture API를 노출하세요.

Activity 하위 클래스는 구성할 수 없습니다. 기능의 활동을 확장하면 사용자가 동일한 작업을 해야 하는 다른 기능과 호환되지 않습니다. 대신 LifecycleObserver와 같은 도구를 사용하여 컴포지션을 사용하세요.

컨텍스트의 getUser() 사용

Context에 바인드된 클래스(예: Context.getSystemService()에서 반환된 항목)는 특정 사용자를 타겟팅하는 멤버를 노출하는 대신 Context에 바인드된 사용자를 사용해야 합니다.

class FooManager {
  Context mContext;

  void fooBar() {
    mIFooBar.fooBarForUser(mContext.getUser());
  }
}
class FooManager {
  Context mContext;

  Foobar getFoobar() {
    // Bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBarForUser(Process.myUserHandle());
  }

  Foobar getFoobar() {
    // Also bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBar();
  }

  Foobar getFoobarForUser(UserHandle user) {
    mIFooBar.fooBarForUser(user);
  }
}

예외: UserHandle.ALL와 같이 단일 사용자를 나타내지 않는 값을 허용하는 경우 메서드가 사용자 인수를 허용할 수 있습니다.

일반 정수 대신 UserHandle 사용

UserHandle는 유형 안전성을 제공하고 사용자 ID와 uid의 혼동을 방지하는 데 선호됩니다.

Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);

피할 수 없는 경우 사용자 ID를 나타내는 int@UserIdInt 주석을 달아야 합니다.

Foobar getFoobarForUser(@UserIdInt int user);

브로드캐스트 인텐트보다 리스너 또는 콜백 선호

브로드캐스트 인텐트는 매우 강력하지만 시스템 상태에 부정적인 영향을 미칠 수 있는 긴급한 동작이 발생하므로 신중하게 새 브로드캐스트 인텐트를 추가해야 합니다.

다음은 새로운 브로드캐스트 인텐트 도입을 권장하지 않는 구체적인 우려사항입니다.

  • FLAG_RECEIVER_REGISTERED_ONLY 플래그 없이 브로드캐스트를 전송하면 아직 실행되지 않은 앱이 강제 시작됩니다. 의도한 결과일 수도 있지만, 이로 인해 수십 개의 앱이 한꺼번에 실행되어 시스템 상태에 부정적인 영향을 미칠 수 있습니다. 다양한 사전 조건이 충족되는 시점을 더 잘 조정하려면 JobScheduler과 같은 대체 전략을 사용하는 것이 좋습니다.

  • 브로드캐스트를 전송할 때 앱에 전송되는 콘텐츠를 필터링하거나 조정할 수 있는 기능이 거의 없습니다. 따라서 향후 개인 정보 보호 문제에 대응하거나 수신 앱의 타겟 SDK에 따라 동작을 변경하기가 어렵거나 불가능합니다.

  • 브로드캐스트 대기열은 공유 리소스이므로 과부하가 발생할 수 있으며 이벤트가 적시에 전송되지 않을 수 있습니다. 엔드 투 엔드 지연 시간이 10분 이상인 브로드캐스트 대기열이 여러 개 관찰되었습니다.

이러한 이유로 새로운 기능에서는 브로드캐스트 인텐트 대신 리스너나 콜백, JobScheduler와 같은 다른 기능을 사용하는 것이 좋습니다.

브로드캐스트 인텐트가 여전히 이상적인 설계인 경우 고려해야 할 권장사항은 다음과 같습니다.

  • 가능하면 Intent.FLAG_RECEIVER_REGISTERED_ONLY를 사용하여 이미 실행 중인 앱으로 브로드캐스트를 제한하세요. 예를 들어 ACTION_SCREEN_ON은 이 설계를 사용하여 앱이 절전 모드에서 해제되지 않도록 합니다.
  • 가능한 경우 Intent.setPackage() 또는 Intent.setComponent()를 사용하여 관심 있는 특정 앱에 브로드캐스트를 타겟팅합니다. 예를 들어 ACTION_MEDIA_BUTTON은 이 디자인을 사용하여 재생 컨트롤을 처리하는 현재 앱에 포커스를 맞춥니다.
  • 가능한 경우 악성 앱이 OS를 가장하지 못하도록 브로드캐스트를 <protected-broadcast>로 정의하세요.

시스템 바운드 개발자 서비스의 인텐트

개발자가 확장하고 시스템에서 바인드하도록 설계된 서비스(예: NotificationListenerService와 같은 추상 서비스)는 시스템의 Intent 작업에 응답할 수 있습니다. 이러한 서비스는 다음 기준을 충족해야 합니다.

  1. 서비스의 정규화된 클래스 이름을 포함하는 클래스에 SERVICE_INTERFACE 문자열 상수를 정의합니다. 이 상수는 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)로 주석 처리해야 합니다.
  2. 개발자가 플랫폼에서 인텐트를 수신하기 위해 AndroidManifest.xml<intent-filter>를 추가해야 하는 클래스에 관한 문서입니다.
  3. 악성 앱이 개발자 서비스에 Intent를 전송하지 못하도록 시스템 수준 권한을 추가하는 것이 좋습니다.

Kotlin-Java 상호 운용성

가이드라인의 전체 목록은 공식 Android Kotlin-Java 상호 운용성 가이드를 참고하세요. 발견 가능성을 높이기 위해 일부 가이드라인이 이 가이드에 복사되었습니다.

API 공개 상태

suspend fun와 같은 일부 Kotlin API는 Java 개발자가 사용하도록 설계되지 않았습니다. 하지만 @JvmSynthetic를 사용하여 언어별 공개 상태를 제어하려고 하지 마세요. 디버깅을 더 어렵게 만드는 디버거에 API가 표시되는 방식에 부작용이 있기 때문입니다.

자세한 안내는 Kotlin-Java 상호 운용성 가이드 또는 비동기 가이드를 참고하세요.

컴패니언 객체

Kotlin은 companion object를 사용하여 정적 멤버를 노출합니다. 경우에 따라 이러한 항목은 포함 클래스가 아닌 Companion라는 내부 클래스에서 Java로 표시됩니다. Companion 클래스가 API 텍스트 파일에 빈 클래스로 표시될 수 있습니다. 이는 의도한 대로 작동하는 것입니다.

Java와의 호환성을 극대화하려면 동반 객체의 상수가 아닌 필드@JvmField를, 공개 함수@JvmStatic를 주석으로 달아 포함된 클래스에 직접 노출하세요.

companion object {
  @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
  @JvmStatic fun fromPointF(pointf: PointF) {
    /* ... */
  }
}

Android 플랫폼 API의 발전

이 섹션에서는 기존 Android API에 적용할 수 있는 변경사항의 유형과 기존 앱 및 코드베이스와의 호환성을 극대화하기 위해 이러한 변경사항을 구현하는 방법을 설명합니다.

바이너리 호환성 중단 변경사항

최종 공개 API 노출 영역에서 바이너리 브레이킹 체인지를 피하세요. 이러한 유형의 변경사항은 일반적으로 make update-api를 실행할 때 오류를 발생시키지만 Metalava의 API 검사에서 포착하지 못하는 특이 사례가 있을 수 있습니다. 확실하지 않은 경우 Eclipse Foundation의 Evolving Java-based APIs 가이드에서 Java에서 호환되는 API 변경사항 유형에 관한 자세한 설명을 참고하세요. 숨겨진 API (예: 시스템)의 바이너리 브레이킹 체인지는 지원 중단/바꾸기 주기를 따라야 합니다.

소스 호환성 중단 변경사항

바이너리 브레이킹이 아니더라도 소스 브레이킹 변경은 권장하지 않습니다. 바이너리 호환이지만 소스 호환성이 깨지는 변경사항의 한 예는 기존 클래스에 일반을 추가하는 것입니다. 이는 바이너리 호환이지만 상속 또는 모호한 참조로 인해 컴파일 오류가 발생할 수 있습니다. 소스 호환성이 깨지는 변경사항은 make update-api을 실행할 때 오류를 발생시키지 않으므로 기존 API 서명 변경사항의 영향을 이해해야 합니다.

경우에 따라 개발자 환경이나 코드 정확성을 개선하기 위해 소스 호환성이 깨지는 변경사항이 필요할 수 있습니다. 예를 들어 Java 소스에 null 허용 여부 주석을 추가하면 Kotlin 코드와의 상호 운용성이 개선되고 오류 발생 가능성이 줄어들지만, 소스 코드를 변경해야 하는 경우가 많으며 때로는 상당한 변경이 필요하기도 합니다.

비공개 API 변경사항

@TestApi로 주석이 추가된 API는 언제든지 변경할 수 있습니다.

@SystemApi로 주석이 추가된 API는 3년 동안 보존해야 합니다. 다음 일정에 따라 시스템 API를 삭제하거나 리팩터링해야 합니다.

  • API y - 추가됨
  • API y+1 - 지원 중단
    • @Deprecated로 코드를 표시합니다.
    • 대체 항목을 추가하고 @deprecated 문서 주석을 사용하여 지원 중단된 코드의 Javadoc에서 대체 항목으로 연결합니다.
    • 개발 주기 중에 내부 사용자에게 API가 지원 중단된다고 알리는 버그를 신고합니다. 이렇게 하면 대체 API가 적절한지 확인할 수 있습니다.
  • API y+2 - 소프트 삭제
    • @removed로 코드를 표시합니다.
    • 선택적으로 출시를 위해 현재 SDK 수준을 타겟팅하는 앱의 경우 예외를 발생시키거나 아무 작업도 하지 않습니다.
  • API y+3 - 강제 삭제
    • 소스 트리에서 코드를 완전히 삭제합니다.

디프리케이션

지원 중단은 API 변경사항으로 간주되며 메이저 (예: 문자) 출시에서 발생할 수 있습니다. API를 지원 중단할 때는 @Deprecated 소스 주석과 @deprecated <summary> 문서 주석을 함께 사용하세요. 요약에 이전 전략이 포함되어야 합니다. 이 전략은 대체 API로 연결되거나 API를 사용하면 안 되는 이유를 설명할 수 있습니다.

/**
 * Simple version of ...
 *
 * @deprecated Use the {@link androidx.fragment.app.DialogFragment}
 *             class with {@link androidx.fragment.app.FragmentManager}
 *             instead.
 */
@Deprecated
public final void showDialog(int id)

android.R 클래스에 노출된 속성 및 스타일 지정 가능 속성을 비롯하여 XML에 정의되고 Java에 노출된 API도 지원 중단해야 합니다. 요약은 다음과 같습니다.

<!-- Attribute whether the accessibility service ...
     {@deprecated Not used by the framework}
 -->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />

API를 지원 중단해야 하는 경우

지원 중단은 새 코드에서 API 채택을 방지하는 데 가장 유용합니다.

또한 API가 @removed되기 전에 @deprecated로 표시해야 하지만, 이는 개발자가 이미 사용 중인 API에서 이전하도록 강력한 동기를 부여하지는 않습니다.

API를 지원 중단하기 전에 개발자에게 미치는 영향을 고려하세요. API 지원 중단의 효과는 다음과 같습니다.

  • javac는 컴파일 중에 경고를 내보냅니다.
    • 지원 중단 경고는 전역적으로 억제하거나 기준을 설정할 수 없으므로 -Werror를 사용하는 개발자는 컴파일 SDK 버전을 업데이트하기 전에 지원 중단된 API의 모든 사용을 개별적으로 수정하거나 억제해야 합니다.
    • 지원 중단된 클래스의 가져오기에 관한 지원 중단 경고는 억제할 수 없습니다. 따라서 개발자는 컴파일 SDK 버전을 업데이트하기 전에 지원 중단된 클래스의 모든 사용에 대해 정규화된 클래스 이름을 인라인해야 합니다.
  • d.android.com 관련 문서에 지원 중단 알림이 표시됩니다.
  • Android 스튜디오와 같은 IDE는 API 사용 사이트에 경고를 표시합니다.
  • IDE에서 자동 완성에서 API의 순위를 낮추거나 API를 숨길 수 있습니다.

따라서 API를 지원 중단하면 코드 상태에 가장 관심이 있는 개발자 (-Werror를 사용하는 개발자)가 새 SDK를 채택하지 않을 수 있습니다. 기존 코드의 경고에 신경 쓰지 않는 개발자는 지원 중단을 완전히 무시할 가능성이 높습니다.

지원 중단이 많이 도입된 SDK는 이 두 가지 사례를 모두 악화시킵니다.

따라서 다음과 같은 경우에만 API를 지원 중단하는 것이 좋습니다.

  • 향후 출시에서 API를 @remove할 예정입니다.
  • API 사용으로 인해 호환성을 깨지 않고는 수정할 수 없는 잘못되거나 정의되지 않은 동작이 발생합니다.

API를 지원 중단하고 새 API로 대체하는 경우 이전 기기와 새 기기를 모두 지원하는 작업을 간소화하기 위해 androidx.core와 같은 Jetpack 라이브러리에 상응하는 호환성 API를 추가할 것을 적극 권장합니다.

현재 및 향후 버전에서 의도한 대로 작동하는 API는 지원 중단하지 않는 것이 좋습니다.

/**
 * ...
 * @deprecated Use {@link #doThing(int, Bundle)} instead.
 */
@Deprecated
public void doThing(int action) {
  ...
}

public void doThing(int action, @Nullable Bundle extras) {
  ...
}

API가 더 이상 문서화된 동작을 유지할 수 없는 경우 지원 중단이 적합합니다.

/**
 * ...
 * @deprecated No longer displayed in the status bar as of API 21.
 */
@Deprecated
public RemoteViews tickerView;

지원 중단된 API 변경사항

지원 중단된 API의 동작을 유지해야 합니다. 즉, 테스트 구현은 동일하게 유지되어야 하며 API를 지원 중단한 후에도 테스트가 계속 통과되어야 합니다. API에 테스트가 없는 경우 테스트를 추가해야 합니다.

향후 출시에서 지원 중단된 API 노출 영역을 확장하지 마세요. 기존 지원 중단된 API에 린트 정확성 주석 (예: @Nullable)을 추가할 수 있지만 새 API는 추가하면 안 됩니다.

지원 중단된 것으로 새 API를 추가하지 마세요. 사전 출시 주기 내에 API가 추가된 후 지원 중단된 경우 (따라서 처음에는 지원 중단된 것으로 공개 API 노출 영역에 표시됨) API를 최종 확정하기 전에 이를 삭제해야 합니다.

소프트 삭제

소프트 삭제는 소스 브레이킹 체인지이므로 API 위원회에서 명시적으로 승인하지 않는 한 공개 API에서 피해야 합니다. 시스템 API의 경우 점진적 삭제 전에 주요 출시 기간 동안 API를 지원 중단해야 합니다. API에 대한 모든 문서 참조를 삭제하고 API를 소프트 삭제할 때는 @removed <summary> 문서 주석을 사용합니다. 요약에는 삭제 이유가 포함되어야 합니다. 지원 중단에서 설명한 대로 이전 전략을 포함할 수도 있습니다.

일시적으로 삭제된 API의 동작은 그대로 유지할 수 있지만, 기존 호출자가 API를 호출할 때 비정상 종료되지 않도록 유지해야 합니다. 경우에 따라 동작을 유지해야 할 수도 있습니다.

테스트 범위는 유지해야 하지만 동작 변경사항을 수용하기 위해 테스트 콘텐츠를 변경해야 할 수 있습니다. 테스트에서는 기존 호출자가 런타임에 비정상 종료되지 않는지 계속 검증해야 합니다. 소프트 삭제된 API의 동작을 그대로 유지할 수 있지만, 더 중요한 것은 기존 호출자가 API를 호출할 때 비정상 종료되지 않도록 동작을 유지해야 합니다. 경우에 따라 동작을 유지해야 할 수도 있습니다.

테스트 범위를 유지해야 합니다. 하지만 동작 변경사항을 수용하기 위해 테스트 콘텐츠를 변경해야 할 수도 있습니다. 테스트에서는 기존 호출자가 런타임에 비정상 종료되지 않는지 계속 검증해야 합니다.

기술적 수준에서 @remove Javadoc 주석을 사용하여 SDK 스텁 JAR 및 컴파일 시간 클래스 경로에서 API를 삭제하지만 @hide API와 마찬가지로 런타임 클래스 경로에는 여전히 존재합니다.

/**
 * Ringer volume. This is ...
 *
 * @removed Not functional since API 2.
 */
public static final String VOLUME_RING = ...

앱 개발자 관점에서 API는 더 이상 자동 완성에 표시되지 않으며 API를 참조하는 소스 코드는 compileSdk가 API가 삭제된 SDK와 같거나 그 이후인 경우 컴파일되지 않습니다. 하지만 소스 코드는 이전 SDK에 대해 계속 성공적으로 컴파일되며 API를 참조하는 바이너리는 계속 작동합니다.

특정 카테고리의 API는 소프트 삭제하면 안 됩니다. 특정 API 카테고리는 소프트 삭제하면 안 됩니다.

추상 메서드

개발자가 확장할 수 있는 클래스에서 추상 메서드를 소프트 삭제해서는 안 됩니다. 이렇게 하면 개발자가 모든 SDK 수준에서 클래스를 성공적으로 확장할 수 없습니다.

개발자가 클래스를 확장할 수 없었고 앞으로도 없을 드문 경우에도 추상 메서드를 소프트 삭제할 수 있습니다.

하드 삭제

강제 삭제는 바이너리 호환성이 깨지는 변경사항이며 공개 API에서는 발생해서는 안 됩니다.

권장되지 않는 주석

@Discouraged 주석은 대부분의 경우(95% 이상) API를 권장하지 않음을 나타내는 데 사용됩니다. 권장되지 않는 API는 지원 중단된 API와 달리 지원 중단을 방지하는 좁은 중요 사용 사례가 있다는 점에서 다릅니다. API를 권장하지 않음으로 표시할 때는 설명과 대체 솔루션을 제공해야 합니다.

@Discouraged(message = "Use of this function is discouraged because resource
                        reflection makes it harder to perform build
                        optimizations and compile-time verification of code. It
                        is much more efficient to retrieve resources by
                        identifier (such as `R.foo.bar`) than by name (such as
                        `getIdentifier()`)")
public int getIdentifier(String name, String defType, String defPackage) {
    return mResourcesImpl.getIdentifier(name, defType, defPackage);
}

권장되지 않으므로 새 API를 추가해서는 안 됩니다.

기존 API의 동작 변경사항

기존 API의 구현 동작을 변경해야 하는 경우도 있습니다. 예를 들어 Android 7.0에서는 개발자가 Binder을 통해 전송하기에 너무 큰 이벤트를 게시하려고 할 때 명확하게 알리도록 DropBoxManager를 개선했습니다.

하지만 기존 앱에 문제가 발생하지 않도록 이전 앱의 안전한 동작을 유지할 것을 적극 권장합니다. 이전에는 앱의 ApplicationInfo.targetSdkVersion에 따라 이러한 동작 변경사항을 보호했지만 최근 앱 호환성 프레임워크를 사용하도록 마이그레이션했습니다. 다음은 이 새로운 프레임워크를 사용하여 동작 변경을 구현하는 방법의 예입니다.

import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;

public class MyClass {
  @ChangeId
  // This means the change will be enabled for target SDK R and higher.
  @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
  // Use a bug number as the value, provide extra detail in the bug.
  // FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
  static final long FOO_NOW_DOES_X = 123456789L;

  public void doFoo() {
    if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
      // do the new thing
    } else {
      // do the old thing
    }
  }
}

이 앱 호환성 프레임워크 설계를 사용하면 개발자가 앱을 디버그하는 과정에서 프리뷰 및 베타 출시 중에 수십 개의 동작 변경사항에 동시에 적응하도록 강제하는 대신 특정 동작 변경사항을 일시적으로 사용 중지할 수 있습니다.

향후 호환성

향후 호환성은 시스템이 이후 버전용으로 설계된 입력을 허용하는 설계 특성입니다. API 설계의 경우 개발자는 코드를 한 번 작성하고 한 번 테스트하면 문제없이 어디서나 실행될 것으로 기대하므로 초기 설계와 향후 변경사항에 특히 주의해야 합니다.

다음은 Android에서 가장 일반적인 하위 호환성 문제를 일으킵니다.

  • 이전에는 완전하다고 가정된 세트 (예: switch에 예외를 발생시키는 default가 있는 경우)에 새 상수를 추가합니다 (예: @IntDef 또는 enum).
  • API 노출 영역에 직접 포착되지 않는 기능 지원 추가(예: 이전에는 <color> 리소스만 지원되었던 XML에서 ColorStateList 유형 리소스 할당 지원).
  • 런타임 검사에 대한 제한사항을 완화합니다(예: 하위 버전에 있던 requireNotNull() 검사 삭제).

이러한 모든 경우에 개발자는 런타임에만 문제가 있음을 알게 됩니다. 더 심각한 문제는 현장의 이전 기기에서 비정상 종료 보고서가 전송되어 이 문제를 알게 될 수 있다는 것입니다.

또한 이러한 사례는 모두 기술적으로 유효한 API 변경사항입니다. 바이너리 또는 소스 호환성을 깨지 않으며 API 린트에서 이러한 문제를 포착하지 않습니다.

따라서 API 설계자는 기존 클래스를 수정할 때 주의를 기울여야 합니다. '이 변경사항으로 인해 최신 버전의 플랫폼에 대해서만 작성되고 테스트된 코드가 하위 버전에서 실패하게 되나요?'라는 질문을 합니다.

XML 스키마

XML 스키마가 구성요소 간 안정적인 인터페이스 역할을 하는 경우 해당 스키마는 명시적으로 지정되어야 하며 다른 Android API와 마찬가지로 이전 버전과의 호환이 가능한 방식으로 진화해야 합니다. 예를 들어 XML 요소와 속성의 구조는 다른 Android API 노출 영역에서 메서드와 변수가 유지되는 방식과 유사하게 유지되어야 합니다.

XML 지원 중단

XML 요소나 속성을 지원 중단하려면 xs:annotation 마커를 추가하면 되지만 일반적인 @SystemApi 진화 수명 주기를 따라 기존 XML 파일을 계속 지원해야 합니다.

<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

요소 유형은 유지되어야 합니다.

스키마는 complexType 요소의 하위 요소로 sequence 요소, choice 요소, all 요소를 지원합니다. 하지만 이러한 하위 요소는 하위 요소의 수와 순서가 다르므로 기존 유형을 수정하면 호환되지 않는 변경사항이 됩니다.

기존 유형을 수정하려면 기존 유형을 지원 중단하고 이를 대체할 새 유형을 도입하는 것이 좋습니다.

<!-- Original "sequence" value -->
<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<!-- New "choice" value -->
<xs:element name="fooChoice">
    <xs:complexType>
        <xs:choice>
            <xs:element name="name" type="xs:string"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

Mainline 관련 패턴

Mainline은 전체 시스템 이미지를 업데이트하는 대신 Android OS의 하위 시스템 ('mainline 모듈')을 개별적으로 업데이트할 수 있는 프로젝트입니다.

Mainline 모듈은 핵심 플랫폼에서 '번들 해제'되어야 합니다. 즉, 각 모듈과 나머지 세계 간의 모든 상호작용은 공식 (공개 또는 시스템) API를 사용하여 실행해야 합니다.

메인라인 모듈이 따라야 하는 특정 설계 패턴이 있습니다. 이 섹션에서는 이러한 기능을 설명합니다.

<Module>FrameworkInitializer 패턴

mainline 모듈이 @SystemService 클래스 (예: JobScheduler)를 노출해야 하는 경우 다음 패턴을 사용하세요.

  • 모듈에서 <YourModule>FrameworkInitializer 클래스를 노출합니다. 이 클래스는 $BOOTCLASSPATH에 있어야 합니다. 예: StatsFrameworkInitializer

  • @SystemApi(client = MODULE_LIBRARIES)로 표시합니다.

  • 여기에 public static void registerServiceWrappers() 메서드를 추가합니다.

  • Context에 대한 참조가 필요한 경우 SystemServiceRegistry.registerContextAwareService()를 사용하여 서비스 관리자 클래스를 등록합니다.

  • Context 참조가 필요하지 않은 경우 SystemServiceRegistry.registerStaticService()를 사용하여 서비스 관리자 클래스를 등록합니다.

  • SystemServiceRegistry의 정적 초기화 프로그램에서 registerServiceWrappers() 메서드를 호출합니다.

<Module>ServiceManager 패턴

일반적으로 시스템 서비스 바인더 객체를 등록하거나 참조를 가져오려면 ServiceManager를 사용하지만 메인라인 모듈은 숨겨져 있으므로 이를 사용할 수 없습니다. 이 클래스는 메인라인 모듈이 정적 플랫폼이나 다른 모듈에서 노출하는 시스템 서비스 바인더 객체를 등록하거나 참조해서는 안 되므로 숨겨져 있습니다.

메인라인 모듈은 대신 다음 패턴을 사용하여 모듈 내부에 구현된 바인더 서비스를 등록하고 참조를 가져올 수 있습니다.

  • TelephonyServiceManager 설계를 따라 <YourModule>ServiceManager 클래스 만들기

  • 클래스를 @SystemApi로 노출합니다. $BOOTCLASSPATH 클래스 또는 시스템 서버 클래스에서만 액세스해야 하는 경우 @SystemApi(client = MODULE_LIBRARIES)를 사용할 수 있습니다. 그렇지 않으면 @SystemApi(client = PRIVILEGED_APPS)가 작동합니다.

  • 이 클래스는 다음으로 구성됩니다.

    • 숨겨진 생성자이므로 정적 플랫폼 코드만 인스턴스화할 수 있습니다.
    • 특정 이름의 ServiceRegisterer 인스턴스를 반환하는 공개 getter 메서드 바인더 객체가 하나 있으면 getter 메서드가 하나 필요합니다. 두 개가 있다면 게터가 두 개 필요합니다.
    • ActivityThread.initializeMainlineModules()에서 이 클래스를 인스턴스화하고 모듈에서 노출된 정적 메서드에 전달합니다. 일반적으로 @SystemApi(client = MODULE_LIBRARIES) API를 사용하는 FrameworkInitializer 클래스에 정적 @SystemApi(client = MODULE_LIBRARIES) API를 추가합니다.

이 패턴은 다른 메인라인 모듈이 이러한 API에 액세스하지 못하도록 합니다. get()register() API가 다른 모듈에 표시되더라도 다른 모듈이 <YourModule>ServiceManager 인스턴스를 가져올 방법이 없기 때문입니다.

전화 통신이 전화 통신 서비스에 대한 참조를 가져오는 방법은 다음과 같습니다(코드 검색 링크).

네이티브 코드에서 서비스 바인더 객체를 구현하는 경우 AServiceManager 네이티브 API를 사용합니다. 이러한 API는 ServiceManager Java API에 상응하지만 네이티브 API는 메인라인 모듈에 직접 노출됩니다. 모듈에서 소유하지 않은 바인더 객체를 등록하거나 참조하는 데 사용하면 안 됩니다. 네이티브에서 바인더 객체를 노출하는 경우 <YourModule>ServiceManager.ServiceRegistererregister() 메서드가 필요하지 않습니다.

Mainline 모듈의 권한 정의

APK를 포함하는 Mainline 모듈은 일반 APK와 동일한 방식으로 APK AndroidManifest.xml에 (맞춤) 권한을 정의할 수 있습니다.

정의된 권한이 모듈 내에서만 내부적으로 사용되는 경우 권한 이름에 APK 패키지 이름을 접두사로 지정해야 합니다(예:

<permission
    android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
    android:protectionLevel="signature" />

정의된 권한이 업데이트 가능한 플랫폼 API의 일부로 다른 앱에 제공되는 경우 권한 이름에 'android.permission.'이 접두사로 붙어야 합니다. (정적 플랫폼 권한과 같이) 모듈 패키지 이름을 더하여 이름 충돌을 방지하면서 모듈의 플랫폼 API임을 나타냅니다. 예를 들면 다음과 같습니다.

<permission
    android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"
    android:label="@string/active_calories_burned_read_content_description"
    android:protectionLevel="dangerous"
    android:permissionGroup="android.permission-group.HEALTH" />

그러면 모듈은 이 권한 이름을 API 노출 영역에서 API 상수(예: HealthPermissions.READ_ACTIVE_CALORIES_BURNED)로 노출할 수 있습니다.