Android

[안드로이드] 어노테이션(Annotation) 개념과 예시

나맘임 2023. 11. 19. 19:11

어노테이션(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()
}

그림 1. @IntRange 사용 시 범위에 벗어난 값 입력 시 오류를 알려주는 모습

@FloatRange

Float, Double 값에 제약 조건 걸기 

private fun setUserExp(@FloatRange(from = 0.0, to =100.0) exp : Double){
    binding.userLevel.text = exp.toString()
}

그림 2. @FloatRange 사용 시 범위에 벗어난 값 입력

@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"
그림 3. Test 코드를 메인 액티비티에서 사용할 시 모습

사용 예시 5 - Thread 관련 사용 제약

@AnyThread

모든 스레드에서 호출 가능

@BinderThread

바인더 스레드에서만 호출 가능

@MainThread(@UiThread)

메인 스레드 또는 UiThread에서만 호출 가능

@WorkerThread

Worker 스레드(백그라운드 스레드)에서만 호출 가능

 

 

사용 예시 6 - 결과값 사용을 확인하기

@CheckResult

반환값을 개발자가 따로 사용하지 않고 무시했을 때 경고를 준다.

    @CheckResult
    fun calculateExp(exp : Int):Int{
    	...
        return exp
    }

그림 4. @CheckResult 사용 모습

 

 

사용 예시 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 어노테이션으로 인해 부모 클래스의 메서드 호출이 필요함
    }
}

그림 5. 내부적으로 CallSuper가 선언되어 있는 모습

super.onCreate(savedInstanceState)를 사용해야 하는 이유가 바로 AppCompatActivity 내부에 @CallSuper가 되어 있기 때문이다.

 

 

 

 

참고 문헌

어노테이션(Annotation) (tistory.com)

 

어노테이션(Annotation)

어노테이션(Annotation)에 대해 알아보자 나는 공부할 때 사진이 많은 것은 ppt로 정리하여 두고, 텍스트만있다면 txt에 적어두는데, 블로그에 올리기 위하여 ppt자료를 풀어서 써본다(피피티에 더 자

sjh836.tistory.com

Android Annotation 정리. 안드로이드 사용되는 어노테이션은 무엇이 있는지 알아봅니다. | by hongbeom | hongbeomi dev | Medium

 

Android Annotation 정리

안드로이드 사용되는 어노테이션은 무엇이 있는지 알아봅니다.

medium.com

주석으로 코드 검사 개선  |  Android 스튜디오  |  Android Developers

 

주석으로 코드 검사 개선  |  Android 스튜디오  |  Android Developers

주석을 사용하여 린트와 같은 코드 검사 도구에 힌트를 줌으로써 보다 미묘한 코드 문제를 더 잘 탐지하는 방법을 알아보세요.

developer.android.com

Android Support Annotations 라이브러리를 활용한 결함 탐지 (naver.com)

Annotation  |  Android 개발자  |  Android Developers

 

Annotation  |  Android 개발자  |  Android Developers

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Annotation 도구 및 다른 개발자가 앱의 코드를 이해하는 데 도움이 되는 메타데이터를 노출합니다. 이 표에는 a

developer.android.com