[안드로이드] 개발에 도움을 주는 어노테이션

hee09·2022년 3월 13일
0
post-thumbnail

안드로이드 어노테이션

안드로이드에서 코드를 작성할 때, 코드의 무결성에 도움을 주는 Annotation이 있습니다.
Annotation을 적용하면 Annotation의 타입에 따라서 어떤 것은 에러를, 어떤 것은 경고를 발생시킵니다. 개발자가 코드를 사용하는데 실수를 줄이기 위해 사용한다고 생각하면 될 것 같습니다.

Annotation은 변수, 파라미터, 리턴 값과 같은 곳에 사용할 수 있고, null pointer exception과 resource type 충돌과 같은 문제를 찾는데 도움을 줍니다. 아래에서 여러 종류의 annotation을 확인해보겠습니다.


Resource annotations

안드로이드의 경우 resource의 아이디는 int로 정의되어 있습니다. 따라서 매개변수의 인자나 반환값으로 넘기는 경우 int로 받기에 일반적으로 사용하는 int형 변수와 혼동해 실수를 유발할 수도 있습니다. 이러한 에제로는 Color, Layout, String에서 사용하는 id들이 될 수 있습니다. 이때 Annotation을 활용한다면 정해진 Annotaion Resource Type에 맞지 않는 Resource Type이 들어올 경우 경고를 통해 알려주기에 개발자가 실수를 줄일 수 있습니다.

  • @AnyRes: R 타입의 리소스(모든 타입의 리소스)
  • @AnimationRes : animator 리소스 참조
  • @AnimRes : anim 리소스 참조
  • @ArrayRes : 배열 타입의 리소스 참조
  • @AttrRes : attribute 리소스 참조
  • @BoolRes : boolean 리소스 참조
  • @ColorRes : color 리소스 참조
  • @DimenRes : dimen 리소스 참조
  • @DrawableRes : drawable 리소스 참조
  • @FontRes : font 리소스 참조
  • @IdRes : id 리소스 참조
  • @IntegerRes : integer 리소스 참조
  • @IntegerpolatorRes : interpolator 리소스 참조
  • @LayoutRes : layout 리소스 참조
  • @MenuRes : menu 리소스 참조
  • @NavigationRes : navigation 리소스 참조
  • @PluralRes : plurals(개수에 따른 문자열 표현) 리소스 참조
  • @RawRes : raw(원본 리소스 : txt, 음악 파일 등) 리소스 참조
  • @StringRes : string 리소스 참조
  • @StyleRes : style 리소스 참조
  • @TransitionRes : transition(전환 애니메이션 효과) 리소스 참조
  • @XmlRes : XML 리소스 참조

Thread annotations

메소드에 해당 어노테이션이 붙은 경우 해당 스레드에서 호출될 수 있음을 나타냅니다. 만약, annotation이 클래스에 붙는다면 해당 클래스의 모든 메소드들은 해당 스레드에서만 호출할 수 있습니다.

  • @MainThread : 메소드는 main thread 에서만 호출이 가능합니다.
  • @UiThread : 메소드나 생성자는 ui thread 에서만 호출이 가능합니다.
  • @WorkerThread : 메소드는 worker thread 에서만 호출이 가능합니다.
  • @BinderThread : 메소드는 binder thread 에서만 호출이 가능합니다.
  • @AnyTherad : 메소드는 모든 thread 에서 호출이 가능합니다.

@MainThread 와 @UiThread 어노테이션은 서로 교환이 가능해서 @MainThread 어노테이션이 붙은 메소드에서 @UiThread 메소드를 호출할 수 있고 그 반대의 경우도 마찬가지입니다. 다만 여러 스레드에 대한 다중 뷰를 가진 앱에서는 @UiThread 와 @MainThread 가 다를 수 있습니다. 따라서 @UiThread는 View 계층과 연결된 메소드에 달고, @MainThread는 앱의 수명주기와 관련된 메소드에 달아야 합니다.


Value constraint annotations

해당 annotation은 매개변수로 전달되는 값을 검증해주는 역할을 수행합니다. 종류는 다음과 같고 @IntRange 와 @FloatRange 같은 경우는 사용자가 범위를 잘못 알 수 있는 파라미터에 적용할 때 유용합니다.

  • @IntRange : integer, long 타입의 변수의 값을 특정한 범위로 제한합니다.
  • @FloatRange : float, double 타입의 변수의 값을 특정한 범위로 제한합니다.
// alpha integer 타입 변수 0 ~ 255로 제한
fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) {
	...
}

// alpha float 타입 변수 0.0 ~ 1.0
fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {
	...
}
  • @Size : 문자열의 길이 또는 collection, array의 크기를 해당 어노테이션에 지정된 값으로 제한합니다. 즉, 연결된요소가 주어진 크기 또는 길이를 가져야 함을 나타냅니다.
fun showList(@Size(max=2) nameList: List<String>) {
	nameList.forEach { println("name : $it") }
}

// value -> 정확한 크기 지정
// min -> 최소 크기
// max -> 최대 크기
// multiple -> 해당 인자의 배수 크기

Permission annotations

  • RequiresPermission : 해당 annotation은 메소드를 호출할 때 하나 이상의 permission을 검증하는데 사용됩니다. 즉, permission을 허용하고 해당 메소드를 사용하고 있는지 확인할 수 있습니다. 만약 허용 코드를 사용하지 않고 메소드를 호출한다면 에러를 통해 알려줍니다. anyof 속성을 사용하여 permission 리스트 중 하나만 만족하도록 검사할 수 있고, allof 속성을 사용하여 permission 리스트 모두를 만족하도록 검사할 수도 있습니다.
// 단일 권한이 필요한 경우
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
fun setWallpaper(bitmap: Bitmap) {

}

// 하나 이상의 권한이 필요한 경우
@RequiresPermission(anyOf = [
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.ACCESS_MEDIA_LOCATION
])
fun copyImageFile(dest: String, source: String) {

}


// 모든 권한이 필요한 경우
@RequiresPermission(allOf = [
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.ACCESS_MEDIA_LOCATION
])
fun copyImageFile(dest: String, source: String) {

}

// Content Provider에 대해 읽기 및 쓰기가 별도의 권한이 필요한 경우
// Read나 Write를 사용해서 나눌 수 있습니다.
@RequiresPermission.Read(RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(RequiresPermission(WRITE_HISTORY_BOOKMARKS))
val BOOKMARKS_URI = Uri.parse("content://browser/bookmarks")

Return value annotations

  • CheckResult : 해당 어노테이션이 붙은 메소드가 리턴한 결과를 개발자가 사용하지 않았으면 경고를 나타냅니다.


CallSuper annotations

  • CallSuper : 이 어노테이션이 붙은 메서드를 하위 클래스에서 오버라이드 할 땐 반드시 상위 클래스의 메서드를 호출하도록 강제함을 나타냅니다. 예를 들면 액티비티의 라이플 사이클 메소드와 같은 것이 있습니다.

이외 여러가지 어노테이션

  • @Keep : 빌드 시 코드가 최적화 될 때 어노테이션이 연결된 요소를 제거하지 않아야 함을 나타냅니다.

  • @RequiresApi ; 해당 어노테이션은 @TargetApi 와 유사하게 지정된 API 레벨 이상에서만 호출되어야 함을 의미하지만 해당 메소드에 대한 호출자의 요구사항임을 더욱 명확하게 나타냅니다.

  • @VisibleForTesting : 선택적으로 가시성을 지정할 수 있습니다. 이 어노테이션이 달린다면 해당 메소드 혹은 필드의 가시성이 테스트를 위해 완화된 것임을 명시적으로 나타냅니다.

  • @ColorInt : AARRGGBB 와 같이 패킹된 color를 나타냅니다.

  • @Dimension : 변수나 필드 또는 메소드의 리턴 값이 dimension임을 나타냅니다. 인수로는 DP, PX, SP를 지정할 수 있습니다.

  • @Px : 변수나 필드 또는 메소드의 리턴 값이 pixel dimension임을 나타냅니다.

  • @GuardedBy : 어노테이션이 달린 메소드 또는 필드에 사용하려면 lock을 보유해야만 접근할 수 있음을 나타냅니다.

  • @ContentView : 생성자에 연결할 수 있는 어노테이션으로 LayoutRes 를 생성자의 인자로 받으면 setContentView를 대신할 수 있습니다.

class MainFragment: Fragment {
  fun MainFragment() {
    // This constructor is annotated with @ContentView
    super(R.layout.main)
  }
}

// Fragment 내부
@ContentView
public Fragment(@LayoutRes int contentLayoutId) {
    this();
    mContentLayoutId = contentLayoutId;
}

  • @ChecksSdkIntAtLeast : 해당 어노테이션이 추가된 메소드가 SDK_INT API 레벨이 최소 주어진 값인지 검사하고 해당 값을 반환하거나 지정된 람다를 실행합니다.
// 어노테이션 인자로 들어갈 수 있는 속성은 아래와 같습니다.
// api : 최소 API 레벨
// codename : String -> 최소 API의 코드명("R")
// lambda : Int -> 최소 API 레벨을 만족할 경우 실행될 람다의 파라미터 번호
// parameter : int -> 최소 API 레벨이 파라미터에 지정되며 첫 번째 파라미터 번호는 0 부터 시작

// 최소 O 버전 이상인지 검사
 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
 public static boolean isAtLeastO() {
     return Build.VERSION.SDK_INT >= 26;
 }

 // 첫 번째 인자로 필요로 하는 API 레벨이 전달되며,
 // SDK_INT가 그 이상으로 높으면 두 번째 매개변수의 함수(람다)가 실행된다
 @ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
 inline fun fromApi(value: Int, action: () -> Unit) {
     if (Build.VERSION.SDK_INT >= value) {
         action()
     }
 }

 // 코틀린 프로퍼티에 사용
 @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.GINGERBREAD)
 val isGingerbread: Boolean
    get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD

참조

profile
되새기기 위해 기록

0개의 댓글