린트와 같은 코드 검사 도구를 사용하면 문제점을 찾아내고 코드를 개선하는 데는 도움이 될 수 있지만 검사 도구가 유추할 수 있는 범위도 한정되어 있습니다. 예를 들어, Android 리소스 ID는 int를 사용해 문자열, 그래픽, 색상 및 기타 리소스 유형을 식별하므로, 색상을 지정했어야 하는 위치에서 문자열 리소스를 지정했더라도 검사 도구가 이 사실을 알려주지는 못합니다. 즉, 코드 검사 기능을 사용하더라도 앱이 잘못 렌더링되거나 아예 실행조차 되지 않을 수도 있습니다.
주석을 사용하면 린트와 같은 코드 검사 도구에 힌트를 줌으로써 보다 미묘한 코드 문제를 더 잘 탐지하도록 할 수 있습니다. 주석은 변수, 매개변수 및 반환 값에 추가하여 메서드 반환 값, 전달된 매개변수, 로컬 변수 및 필드를 검사하는 메타데이터 태그로 추가됩니다. 주석을 코드 검사 도구와 함께 사용하면 null 포인터 예외 및 리소스 유형 충돌과 같은 문제를 감지하는 데 도움이 될 수 있습니다.
Android는 Jetpack Annotations 라이브러리를 통해 다양한 주석을 지원합니다.
androidx.annotation 패키지를 통해 이 라이브러리에 액세스할 수 있습니다.
참고: 모듈에 주석 프로세서 종속 항목이 있는 경우 해당 종속 항목을 추가하려면 kapt 또는 ksp 종속 항목 구성(Kotlin)이나 annotationProcessor 종속 항목 구성(Java)을 사용해야 합니다.
프로젝트에 주석 추가하기
프로젝트에서 주석을 사용하려면 라이브러리나 앱에 androidx.annotation:annotation 종속 항목을 추가합니다. 그러면 추가하는 주석에 대해 코드 검사 또는 lint 작업을 실행할 때 검사가 이루어집니다.
Jetpack Annotations 라이브러리 종속 항목 추가
Jetpack Annotations 라이브러리는 Google의 Maven 저장소에 게시됩니다.
프로젝트에 Jetpack Annotations 라이브러리를 추가하려면 build.gradle 또는 build.gradle.kts 파일의 dependencies 블록에 다음 줄을 포함합니다.
Kotlin
dependencies { implementation("androidx.annotation:annotation:1.10.0") }
Groovy
dependencies { implementation 'androidx.annotation:annotation:1.10.0' }
자체 라이브러리 모듈에서 주석을 사용하는 경우에는 주석이 XML 형식으로 된 Android 보관 파일(AAR) 아티팩트의 일부로 annotations.zip 파일에 포함됩니다. androidx.annotation 종속 항목을 추가하더라도 라이브러리의 다운스트림 사용자에 대해서는 종속 항목이 적용되지 않습니다.
참고: 다른 Jetpack 라이브러리를 사용하는 경우 androidx.annotation 종속 항목을 추가할 필요가 없을 수도 있습니다. 다른 많은 Jetpack 라이브러리는 Jetpack Annotations 라이브러리에 종속되므로, 이미 주석에 액세스할 수 있습니다.
Jetpack 저장소에 포함된 주석의 전체 목록은 Jetpack Annotations 라이브러리 참조에서 확인하거나 자동 완성 기능을 사용하여 import androidx.annotation. 명령문에 사용 가능한 옵션을 표시하면 됩니다.
코드 검사 실행하기
Android 스튜디오에서 주석 유효성 검사와 자동 린트 검사가 있는 코드 검사를 시작하려면 메뉴에서 Analyze > Inspect Code를 선택합니다. Android 스튜디오에서는 충돌 메시지를 표시하여 개발자의 코드가 주석과 충돌할 가능성이 있는 문제를 신고하고 가능한 해결 방법을 제안합니다.
명령줄을 사용해 lint 작업을 실행함으로써 주석을 강제 적용할 수도 있습니다. 이 방법은 지속적 통합 서버 관련 문제를 신고하는 데 유용할 수도 있지만, lint 작업은 nullness 주석을 강제 적용하지 않습니다(다음 섹션에서 설명). Android 스튜디오만 강제 적용합니다. 린트 검사의 사용 설정과 실행에 관한 자세한 내용은 린트 검사로 코드 개선을 참고하세요.
주석 충돌로 인해 경고가 생성되더라도 앱 컴파일은 가능합니다.
Nullness 주석
Nullness 주석은 Java 코드에서 값이 null일 수 있는지 여부를 강제 적용하는 데 유용할 수 있습니다. Kotlin 코드에서는 비교적 덜 유용합니다. Kotlin이 컴파일 시 적용되는 null 허용 여부 규칙을 빌드했기 때문입니다.주어진 변수, 매개변수 또는 반환 값의 nullness를 확인하려면 @Nullable 및 @NonNull 주석을 추가하세요. @Nullable 주석은 null이 될 수 있는 변수, 매개변수 또는 반환 값을 나타냅니다.
@NonNull은 null이 될 수 없는 변수, 매개변수 또는 반환 값을 나타냅니다.
예를 들어, null 값을 포함하는 로컬 변수가 특정 메서드에 매개변수로서 전달되고 메서드의 @NonNull 주석이 매개변수에 연결되어 있는 경우 코드를 빌드하면 null이 아닌 충돌을 나타내는 경고가 생성됩니다. 또한 결과가 null인지를 먼저 확인하지 않고 @Nullable로 표시된 메서드의 결과를 참조하려고 하면 nullness 경고가 생성됩니다. 메서드를 사용할 때마다 명시적으로 null 여부를 확인해야 할 경우에는 메서드의 반환 값에 @Nullable만 사용합니다.
다음 예에서는 실제 null 허용 여부를 보여줍니다. Kotlin 예시 코드는 null을 허용하지 않는 유형이 지정되면 생성된 바이트 코드에 자동으로 추가되기 때문에 @NonNull 주석을 활용하지 않습니다. Java 예시에서는 context 및 attrs 매개변수의 @NonNull 주석을 활용하여 전달된 매개변수 값이 null이 아님을 확인합니다. 또한 onCreateView() 메서드 자체가 null을 반환하지 않는지 확인합니다.
Kotlin
... /** Annotation not used because of the safe-call operator(?)**/ override fun onCreateView( name: String?, context: Context, attrs: AttributeSet ): View? { ... } ...
Java
import androidx.annotation.NonNull; ... /** Add support for inflating the <fragment> tag. **/ @NonNull @Override public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) { ... } ...
null 허용 여부 분석
Android 스튜디오는 null 허용 여부 분석을 실행하여 코드에서 nullness 주석을 자동으로 유추하고 삽입하는 기능을 지원합니다. null 허용 여부 분석에서는 다음 항목을 찾아내기 위해 코드의 메서드 계층 구조 전반에 걸쳐 계약을 검색합니다.
- null을 반환할 수 있는 호출 메서드
- null을 반환해서는 안 되는 메서드
- null이 될 수 있는 필드, 로컬 변수, 매개변수와 같은 변수
- null 값을 보유할 수 없는 필드, 로컬 변수, 매개변수와 같은 변수
그런 다음, 발견된 위치에 적절한 null 주석을 자동으로 삽입합니다.
Android 스튜디오에서 null 가능 분석을 실행하려면 Analyze > Infer Nullity를 선택합니다. Android 스튜디오가 코드에서 검색된 위치에 Android @Nullable 및 @NonNull 주석을 삽입합니다. null 분석을 실행한 후에는 삽입된 주석을 확인하는 것이 좋습니다.
참고: nullness 주석을 추가할 때 자동 완성 기능이 Android null 주석 대신 IntelliJ @Nullable 및 @NotNull 주석을 제안할 수 있으며 그에 상응하는 라이브러리를 자동으로 가져올 수 있습니다. 하지만 Android 스튜디오 린트 검사기는 Android null 주석만 찾습니다. 주석을 확인할 때는 린트 검사기가 코드 검사 중에 올바로 알려줄 수 있도록 프로젝트에서 Android null 주석을 사용하는지 확인하세요.
리소스 주석
드로어블, 문자열 리소스와 같은 리소스의 Android 참조는 정수로 전달되기 때문에 리소스 유형의 유효성 검사가 유용할 수 있습니다.
매개변수가 특정 유형의 리소스(예: String)를 참조할 것으로 예상되는 코드는 예상되는 참조 유형인 int로 전달될 수 있지만, 실제로는 R.string 리소스와 같은 다른 리소스 유형을 참조할 수 있습니다.
예를 들어, 아래에 나온 것처럼 @StringRes 주석을 추가하면 리소스 매개변수에 R.string 참조가 포함되어 있는지 검사할 수 있습니다.
Kotlin
abstract fun setTitle(@StringRes resId: Int)
Java
public abstract void setTitle(@StringRes int resId)
코드 검사 중 매개변수에 R.string 참조가 전달되지 않으면 주석에서 경고가 생성됩니다.
@DrawableRes, @DimenRes, @ColorRes, @InterpolatorRes 같은 다른 리소스 유형의 주석도 같은 주석 형식을 사용해 추가하고 코드 검사 중에 실행할 수 있습니다.
매개변수가 여러 리소스 유형을 지원할 경우 주어진 매개변수에 리소스 유형 주석을 두 개 이상 배치할 수 있습니다. 주석이 지정된 매개변수가 임의의 R 리소스 유형일 수 있음을 나타내려면 @AnyRes를 사용합니다.
@ColorRes를 사용하면 매개변수가 색상 리소스여야 함을 지정할 수 있습니다. 하지만, 색상 정수(RRGGBB 또는 AARRGGBB 형식)는 색상 리소스로 인식되지 않습니다. 대신, @ColorInt 주석을 사용해야 매개변수가 색상 정수여야 함을 나타낼 수 있습니다. 빌드 도구는 색상 정수가 아니라 android.R.color.black과 같은 색상 리소스 ID를 주석이 지정된 메서드로 전달하는 잘못된 코드를 플래그 지정합니다.
스레드 주석
스레드 주석은 메서드가 특정 유형의 스레드로부터 호출되는지 확인합니다. 다음과 같은 스레드 주석이 지원됩니다.
빌드 도구는 @MainThread 및 @UiThread 주석을 교환 가능한 것으로 취급하므로, @MainThread 메서드에서 @UiThread 메서드를 호출할 수 있고 그 반대 방향으로도 호출할 수 있습니다. 하지만 서로 다른 스레드의 뷰가 여러 개 있는 시스템 앱의 경우 UI 스레드가 주 스레드와 다를 가능성이 있습니다. 따라서 @UiThread가 있는 앱의 뷰 계층 구조와 연결된 메서드에 주석을 달고 @MainThread가 있는 앱의 수명 주기와 연결된 메서드에만 주석을 달아야 합니다.
클래스에 있는 모든 메서드의 스레딩 요구사항이 동일하다면 클래스에 단일 스레드 주석을 추가하여 클래스의 모든 메서드가 같은 유형의 스레드로부터 호출되는지 확인할 수 있습니다.
스레드 주석의 일반적인 용도는 @WorkerThread 주석이 달린 메서드나 클래스가 적절한 백그라운드 스레드에서만 호출되는지 확인하는 것입니다.
값 제약 조건 주석
전달된 매개변수 값의 유효성 검사를 실행하려면 @IntRange, @FloatRange 및 @Size 주석을 사용하세요. @IntRange 및 @FloatRange는 사용자가 잘못된 범위를 지정할 가능성이 있는 매개변수에 적용할 때 가장 유용합니다.
@IntRange 주석은 integer 또는 long 형식의 매개변수 값이 지정 범위 내에 있는지 확인합니다. 다음 예는 alpha 매개변수가 0~255의 정수 값을 포함해야 함을 나타냅니다.
Kotlin
fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) { ... }
Java
public void setAlpha(@IntRange(from=0,to=255) int alpha) { ... }
@FloatRange 주석은 float 또는 double 형식의 매개변수 값이 부동 소수점 값의 지정 범위 내에 있는지 검사합니다. 다음 예는 alpha 매개변수에 0.0~1.0의 부동 소수점 값을 포함해야 함을 나타냅니다.
Kotlin
fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {...}
Java
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}
@Size 주석은 컬렉션 또는 배열의 크기나 문자열의 길이를 검사합니다. @Size 주석을 사용하여 다음과 같은 특성을 확인할 수 있습니다.
- 최소 크기(예:
@Size(min=2)) - 최대 크기(예:
@Size(max=2)) - 정확한 크기(예:
@Size(2)) - 크기가 배수여야 하는 숫자(예:
@Size(multiple=2))
예를 들어, @Size(min=1)은 컬렉션이 비어 있지 않은지 검사하고 @Size(3)은 배열에 정확히 3개의 값이 있는지 확인합니다.
다음 예는 location 배열에 요소를 1개 이상 포함해야 함을 나타냅니다.
Kotlin
fun getLocation(button: View, @Size(min=1) location: IntArray) { button.getLocationOnScreen(location) }
Java
void getLocation(View button, @Size(min=1) int[] location) { button.getLocationOnScreen(location); }
권한 주석
메서드 호출자 권한의 유효성을 검사하려면 @RequiresPermission 주석을 사용합니다. 유효한 권한의 목록에서 단일 권한이 있는지 검사하려면 anyOf 속성을 사용하고 권한 집합이 있는지 검사하려면 allOf 속성을 사용합니다. 다음 예에서는 setWallpaper() 메서드에 주석을 달아 메서드 호출자에게 permission.SET_WALLPAPERS 권한이 있어야 함을 나타냅니다.
Kotlin
@RequiresPermission(Manifest.permission.SET_WALLPAPER) @Throws(IOException::class) abstract fun setWallpaper(bitmap: Bitmap)
Java
@RequiresPermission(Manifest.permission.SET_WALLPAPER) public abstract void setWallpaper(Bitmap bitmap) throws IOException;
다음 예에서는 copyImageFile() 메서드의 호출자에게 외부 저장소의 읽기 권한과 복사된 이미지의 위치 메타데이터의 읽기 권한이 모두 있어야 합니다.
Kotlin
@RequiresPermission(allOf = [ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION ]) fun copyImageFile(dest: String, source: String) { ... }
Java
@RequiresPermission(allOf = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION}) public static final void copyImageFile(String dest, String source) { //... }
인텐트 관련 권한의 경우 인텐트 작업 이름을 정의하는 문자열 필드에 권한 요건을 지정합니다.
Kotlin
@RequiresPermission(android.Manifest.permission.BLUETOOTH) const val ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"
Java
@RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
읽기와 쓰기 액세스에 개별 권한이 필요한 콘텐츠 제공업체 관련 권한의 경우 각 권한 요건을 @RequiresPermission.Read 또는 @RequiresPermission.Write 주석에 래핑하세요.
Kotlin
@RequiresPermission.Read(RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(RequiresPermission(WRITE_HISTORY_BOOKMARKS)) val BOOKMARKS_URI = Uri.parse("content://browser/bookmarks")
Java
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
간접 권한
권한이 메서드의 매개변수에 제공되는 특정 값에 종속될 때는 특정 권한을 나열하지 않고 매개변수 자체에서 @RequiresPermission을 사용합니다.
예를 들어 startActivity(Intent) 메서드는 자체에 전달되는 인텐트에 간접 권한을 사용합니다.
Kotlin
abstract fun startActivity(@RequiresPermission intent: Intent, bundle: Bundle?)
Java
public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle)
간접 권한을 사용할 때는 빌드 도구가 데이터 흐름 분석을 실행하여 메서드로 전달되는 인수에 @RequiresPermission 주석이 있는지 확인합니다. 그런 다음, 매개변수에서 기존 주석을 메서드 자체에 강제 적용합니다. startActivity(Intent) 예에서 적절한 권한이 없는 인텐트가 메서드로 전달되면 Intent 클래스의 주석 때문에 그림 1처럼 startActivity(Intent)의 잘못된 사용을 알리는 경고가 발생합니다.
그림 1. startActivity(Intent) 메서드의 간접 권한 주석에서 생성되는 경고
빌드 도구는 Intent 클래스에 있는 상응하는 인텐트 작업 이름의 주석에서 startActivity(Intent) 관련 경고를 생성합니다.
Kotlin
@RequiresPermission(Manifest.permission.CALL_PHONE) const val ACTION_CALL = "android.intent.action.CALL"
Java
@RequiresPermission(Manifest.permission.CALL_PHONE) public static final String ACTION_CALL = "android.intent.action.CALL";
필요하다면 메서드의 매개변수에 주석을 달 때 @RequiresPermission.Read 또는 @RequiresPermission.Write 대신 @RequiresPermission을 사용할 수 있습니다. 하지만 간접 권한의 경우 읽기 또는 쓰기 권한 주석 중 어느 하나와 함께 @RequiresPermission을 사용해서는 안 됩니다.
반환 값 주석
@CheckResult 주석을 사용하여 메서드의 결과 또는 반환 값이 실제로 사용되는지 확인합니다. 무효가 아닌 모든 메서드에 @CheckResult 주석을 다는 대신에 혼동을 줄 수 있는 메서드의 결과를 분명히 밝히는 주석을 추가합니다.
예를 들어, 초보 Java 개발자는 주로 <String>.trim()이 원래 문자열에서 공백을 제거한다고 착각합니다. 메서드에 @CheckResult 주석을 달면 <String>.trim() 사용 플래그가 지정되며, 이때는 호출자가 메서드의 반환 값으로 아무것도 하지 않습니다.
다음은 메서드의 반환 값이 실제로 참조되는지 확인하기 위해 checkPermissions() 메서드에 주석을 다는 예시입니다. 이 예는 또한 개발자에게 대체 메서드로 제안할 메서드로 enforcePermission() 메서드를 지정합니다.
Kotlin
@CheckResult(suggest = "#enforcePermission(String,int,int,String)") abstract fun checkPermission(permission: String, pid: Int, uid: Int): Int
Java
@CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String permission, int pid, int uid);
CallSuper 주석
재정의하는 메서드가 메서드의 슈퍼 구현을 호출하는지 확인하려면 @CallSuper 주석을 사용합니다.
다음은 재정의하는 모든 메서드 구현이 super.onCreate()를 호출하도록 하기 위해 onCreate() 메서드에 주석을 다는 예시입니다.
Kotlin
@CallSuper override fun onCreate(savedInstanceState: Bundle?) { }
Java
@CallSuper protected void onCreate(Bundle savedInstanceState) { }
Typedef 주석
Typedef 주석은 특정 매개변수, 반환 값, 필드가 특정 상수 집합을 참조하는지 확인합니다. 또한 허용되는 상수를 자동으로 제공하도록 코드 완성을 사용 설정합니다.
@IntDef 및 @StringDef 주석을 사용하면 정수 및 문자열 집합으로 구성된 열거형 주석을 생성하여 다른 유형의 코드 참조에 대한 유효성을 검사할 수 있습니다.
Typedef 주석은 @interface를 사용하여 새로운 열거형 주석 형식을 선언합니다.
@IntDef 및 @StringDef 주석은 @Retention과 함께 새 주석을 생성하는데, 이러한 주석은 열거 형식을 정의하는 데 필요합니다. @Retention(RetentionPolicy.SOURCE) 주석은 컴파일러에 .class 파일에 열거형 주석 데이터를 저장하지 않도록 지시합니다.
다음은 메서드 매개변수로 전달된 값이 정의된 상수 중 하나를 참조하는지 확인하는 주석을 만드는 단계를 보여주는 예시입니다.
Kotlin
import androidx.annotation.IntDef //... // Define the list of accepted constants and declare the NavigationMode annotation. @Retention(AnnotationRetention.SOURCE) @IntDef(NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS) annotation class NavigationMode // Declare the constants. const val NAVIGATION_MODE_STANDARD = 0 const val NAVIGATION_MODE_LIST = 1 const