어노테이션(Annotation)
자바, 코틀린에서 @을 이용하여 코드에 부가적인 정보를 부여하는 방법으로 컴파일러, 런타임 시스템등이 이를 사용할 수 있게 하는 정형화된 방법
보통 어노테이션과 주석을 비교를 많이 하는데, 주석은 어노테이션처럼 코드에 대한 정보를 나타내나 실제 프로그램에 영향이 없는 반면 어노테이션은 영향을 주는 특징이 있다.
이 글에선 Androidx 에서 지원하는 Annotation API가 기본적으로 제공하는 어노테이션에 대해 알아본다.
그러면 어노테이션을 사용해서 무엇을 얻고자 하는 걸까?
코드에 부가적인 정보를 부여하여 코드를 문서화시키고 컴파일러나 런타임에서 개발자가 원하는 추가적인 처리를 하기 위함이다.
그러면 이 추가적인 처리가 뭐가 있을까?
일반적으론 코드 검사를 하는데 많이 사용한다.
RoomDB에선 어노테이션을 사용해 데이터베이스의 복잡한 작업들을 어노테이션으로 코드 간소화시키기도 한다.
흔히 볼 수 있는 @Override, @Deprecated, @SuppressWarnings 이런 것들이 대표적인 JDK 기본 어노테이션이다.
이제 사용 예시를 알아보자.
사용 예시 1 - 메소드 파라미터로 리소스 사용 시 검사
기본적으로 안드로이드에선 Drawable, AttrRes, ColorRes와 같은 리소스 참조는 정수로 전달되기 때문에 리소스 유형의 유효성을 검사하기 위해서 어노테이션을 사용할 수 있다.
fun changeButtonColor(@ColorRes colorRes: Int){
binding.button.setBackgroundColor(resources.getColor(colorRes))
}
changeButtonColor(R.color.black)
버튼의 배경 색깔을 바꾸지만 기존 res/values/color.xml에 선언해놓은 색깔만 사용해서 색깔을 지정하고 싶을 때 위와 같이 작성할 수 있다.
fun changeButtonColor(@ColorInt colorInt: Int){
binding.button.setBackgroundColor(colorInt)
}
만약 #FF03DAC5 와 같이 ARGB 컬러 정수로 값을 바꾸고 싶다면 @ColorInt를 사용하면 된다.
abstract fun setTitle(@StringRes resId: Int)
res/values/strings.xml에 선언해놓은 String 값만 넣으라고 지정해준 것이다.
사용 예시 2 - 메소드 파라미터 값 제약 조건 걸기
전달된 메소드 파라미터의 값에 대한 제약 조건도 만들 수 있습니다.
일반적으로 유용한 것은 @IntRange, @FloatRange, @Size 어노테이션으로 아래는 예시이다.
@IntRange
Int 값에 제약 조건을 걸기
private fun setUserLevel(@IntRange (from = 0, to = 255) level : Int){
binding.userLevel.text = level.toString()
}
@FloatRange
Float, Double 값에 제약 조건 걸기
private fun setUserExp(@FloatRange(from = 0.0, to =100.0) exp : Double){
binding.userLevel.text = exp.toString()
}
@Size
배열, 리스트와 같은 컬렉션 크기에 제약 조건 걸기
최소 크기를 구할 땐 @Size(min=1)
최대 크기를 구할 땐 @Size(max=2)
정확한 크기는 @Size(3)
크기가 배수여야 하면 @Size(multiple=3) 를 사용할 수 있다.
private fun getCurrentPlayers(@Size(min=1) players: HashMap<Player, Int>){
...
}
위 코드는 플레이어가 아예 없는 경우를 미리 차단하고자 HashMap의 크기를 최소한 1로 만들어야함을
@Size 어노테이션을 사용하여 제약 조건을 건 모습이다.
사용 예시 3 - 메소드 권한 조건을 검사하기
@RequiresPermission
특정 클래스, 메소드를 설계할 때 카메라, 기기 내 저장 장치 등에 접근해야하는 경우가 있다.
이런 경우 권한이 필요함을 미리 명세함으로써 쉽게 디버깅할 수 있게 해준다.
여기서 권한은 AndroidManifext.xml에 설정이 되지 않았다면 오류가 발생하고 anyOf 로 여러 권한 중 한 가지 이상 권한이 필요함을 나타낼 수 있거나, allOf 로 모든 권한이 필요함을 나타낼 수 있다.
@RequiresPermission(Manifest.permission.CAMERA)
@Throws(IOException::class)
fun openCamera(){
...
}
CAMERA의 권한이 있는지 파악하고 없으면 IOException을 Throw 하는 메소드이다.
@RequiresPermission(allOf = [
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_MEDIA_LOCATION
])
@Throws(IOException::class)
fun copyImageFile(dest: String, source: String) {
...
}
READ_EXTERNAL_STORGAE, ACCESS_MEDIA_LOCATION 권한 모두 존재하는 지 파악하고
만약 없다면 IOException Throw하는 메소드를 나타낸 것이다.
all of는 여러 권한을 검사할 때 사용하면 좋은 키워드이다.
사용 예시 3 - 메소드 API 레벨 제약 조건 부여
@RequiresApi
이 어노테이션이 붙었다면 사용할 때 필요한 최소 API 레벨을 나타낸다.
에플리케이션의 minSdkVersion 값이 @RequiresApi 명시된 레벨보다 낮으면 오류가 발생한다.
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
private fun openMainPage(){
...
}
사용 예시 4 - 메소드 사용 범위 제약
@RestrictTo(범위)
범위엔 여러 가지 있다. 목적에 맞게 사용하면 된다.
RestrictTo.Scope.LIBRARY : 코드 있는 곳과 같은 gradle group ID 내에서만 사용
RestrictTo.Scope.SUBCLASSES : 서브 클래스 내에서만 사용
RestrictTo.Scope.TESTS : 테스트 코드에서만 사용
@RestrictTo(RestrictTo.Scope.TESTS)
fun logic() = "hi"
사용 예시 5 - Thread 관련 사용 제약
@AnyThread
모든 스레드에서 호출 가능
@BinderThread
바인더 스레드에서만 호출 가능
@MainThread(@UiThread)
메인 스레드 또는 UiThread에서만 호출 가능
@WorkerThread
Worker 스레드(백그라운드 스레드)에서만 호출 가능
사용 예시 6 - 결과값 사용을 확인하기
@CheckResult
반환값을 개발자가 따로 사용하지 않고 무시했을 때 경고를 준다.
@CheckResult
fun calculateExp(exp : Int):Int{
...
return exp
}
사용 예시 7 - 부모 클래스 메소드 강제로 실행하기
@CallSuper
반드시 super 메소드를 호출해야하게 만드는 어노테이션으로 액티비티나 프래그먼트 생명 주기 메소드에 내부적으로 구현되어 있다.
import androidx.annotation.CallSuper
open class BaseClass {
open fun doSomething() {
// 기본 구현
}
}
class DerivedClass : BaseClass() {
@CallSuper
override fun doSomething() {
// 파생 클래스에서의 추가 작업
super.doSomething() // @CallSuper 어노테이션으로 인해 부모 클래스의 메서드 호출이 필요함
}
}
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 여기서 추가적인 초기화 작업 수행
}
@CallSuper
override fun onStart() {
// 여기서 파생 클래스에서의 추가 작업 수행
super.onStart() // @CallSuper 어노테이션으로 인해 부모 클래스의 메서드 호출이 필요함
}
}
super.onCreate(savedInstanceState)를 사용해야 하는 이유가 바로 AppCompatActivity 내부에 @CallSuper가 되어 있기 때문이다.
참고 문헌
어노테이션(Annotation) (tistory.com)
Android Annotation 정리. 안드로이드 사용되는 어노테이션은 무엇이 있는지 알아봅니다. | by hongbeom | hongbeomi dev | Medium
주석으로 코드 검사 개선 | Android 스튜디오 | Android Developers
Android Support Annotations 라이브러리를 활용한 결함 탐지 (naver.com)
Annotation | Android 개발자 | Android Developers
'Android' 카테고리의 다른 글
[안드로이드]Retrofit(okhttp)의 WebSocket에 대하여 (1) | 2024.01.07 |
---|---|
[안드로이드]RoomDB와 SQLite 비교 (0) | 2023.11.12 |
[안드로이드] 단위 테스트를 도와주는 JUnit 5 (2) | 2023.10.29 |
[안드로이드]Glide에 대하여 (0) | 2023.10.08 |
[안드로이드]Retrofit2 에 관하여 (0) | 2023.09.17 |