티스토리 뷰

Android의 애너테이션 사용법

(Android Support Annotations 라이브러리를 활용한 결함 탐지 에서 요약한 글입니다. 상세한 설명은 링크에서 확인하실 수 있습니다.)

Java에서는 API의 의도를 애너테이션으로 명시해 결함 탐지에 활용하는 기법이 발달했다고 한다.

애너테이션은 Java표준 정의는 아니여서 FingBugs, Checker Framework 같은 도구에서 제공하는 애너테이션을 사용하거나 직접 선언해야 한다.

하지만 안드로이드 개발 환경인 Intellij IDEA는 별도로 애너테이션 패키지 라이브러리를 IDE에 제공한다.

Android 환경은 일반적인 Java 환경보다 잠재적 문제의 소지가 많은데 그 원인중 하나는 성능상 이점을 위해 Enum, EnumSet보다 프리미티브 타입을 사용하는 점이다. (비트 플래그 연산 또는 int 타입 상수 사용; px, dp, color 등의 단위에 별도 타입을 부여하지 않고 사용; R 클래스가 각종 리소스를 int타입 아이디로 관리)

또 다른 원인은 Java 시맨틱으로 검증할 수 없는 제약들로 네트워크 작업이 메인 스레드에서 일어나지 않는지, 상위 클래스 메서드를 반드시 호출해야 하는 일부 클래스, 64K 제한에 따른 앱 전체 메서드 개수 65,536개 확인 등이다.


Android 에서 제공하는 Support Annotations


@NonNull, @Nullable

null 일 수 없고, null 일 수 있다는 애너테이션. 따라서 @Nullable은 무조건 null인지 확인을 해줘야 한다.


@StringRes, @DrawableRes, @ColorRes

리소스가 모두 int 타입으로 관리되기 때문에 넣어야할 리소스 종류를 명시하면 실수를 방지할 수 있다.


@ColorInt, @ColorRes

값을 ARGB 정수로 넣을지 리소스 아이디로 넣을지 선택할 수 있다.


@Dimension, @Px

dp, sp인지 px인지 명시한다. @Dimension(unit = Dimension.SP) 와같은 방식으로 사용한다.


@IntRange, @FloatRange

숫자형 값의 범위를 한정할 수 있다. 예를 들면 투명도에서 0~1 범위 소수값을 쓰는지 0~255범위 정수값을 쓰는지 헤깔릴 때 쓸 수 있다. @FloatRange(from=0.0, to=1.0)


@Size

배열, 컬렉션 크기 그리고 문자열 길이를 한정한다.

@Size(multiple=n) : n의 배수로 한정

@Size(min=5, max= 12)


@IntDef, @StringDef

int, String으로 이루어진 프리미티브 타입을 Enum처럼 안전하게 쓸 수 있도록 제약한다.

//값 정의
static final int INITAILIZED = 0;
static final int STARTED = 1;
static final int ENDED = 2;
static final int CANCELED = 3;
//값 열거
@IntDef({INITIALIZED, STARTED, ENDED, CANCELED})
@Retention(RetentionPolicy.SOURCE)
@interface SharingState {} //interface 선언

//선언한 인터페이스 붙이기
@SharingState
int getState() {...}
void setState(@SharingState int state) {...}

비트 플래그를 사용하도록 flag를 선언할 수 있다.

//값 정의
static final int NONE = 0;
static final int BOLD = 1;
static final int ITALIC = 1 << 1;
static final int UNDELINE = 1 << 2;
static final int STRIKETHROUGH = 8; // 1 << 3
//값 열거
@IntDef({NONE, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH}, flag = true)
@Retention(RetentionPolicy.SOURCE)
@interface TextDecoration {} //interface 선언

//선언한 인터페이스 붙이기
void setTextDecoration(@TextDecoration int deco) {...}

setTextDecoration(BOLD | UNDERLINE);
setTextDecoration(0); // flag=true시 0도 가능


@UiThread, @MainThread, @WorkerThread, @BindThread

명시한 스레드에서만 메서드를 호출할 수 있도록 함. Ui와 Main스레드는 실질적으로 동일하다.


@CallSuper

하위 클래스에서 오버라이드할 때 반드시 상위 클래스의 메서드를 호출하도록 강제


@CheckResult

반드시 메서드 반환값을 사용하도록 강제 (조건 만족 여부 를 boolean 타입으로 반환할 때)


@RequiresPermission

이 애너테이션이 붙은 메서드나 인텐트 액션, 콘텐츠 프로바이더를 사용할 때 필요한 권한을 나타낸다. 이 권한이 AndroidManifest.xml에 설정되어 있지 않으면 오류가 발생

@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})

Location getLocation() {...}

@RequiresPermission(allOf = {READ_SMS, WRITE_SMS})

void deleteAllSpams() {...}


@RequiresApi

Support Annotation 라이브러리 24.0.0에 추가되었고 필요한 최소 API 레벨을 나타낸다. @TargetApi와 유사하지만 더 명확히 표현하는 것이 목적이다. minSdkVersion 값이 명시된 API 레벨보다 낮으면 오류가 발생한다.


@Keep

소스 코드 최적화시 제거 또는 이름 변경이 안 됨을 나타낸다. ProGuard로 소스 코드 최적화시 리플렉션으로 접근해야 하는 요소는 -keep 규칙을 지정해야 한다. @Keep 애너테이션으로 최적화하지 않게 지정할 수 있는데 자동으로 최적화 대상에서 제외되지는 않아서 ProGuard 설정을 추가해야 한다.

-keep @android.support.annotation.Keep class *

-keep @android.support.annotation.Keep class * {
    *;
}

-keep class * {
    @android.support.annotation.Keep *;
}


@VisibleForTesting

테스트 코드는 대상 클래스의 private 메서드에 접근할 수 없는데 1. 테스트 클래스에서 접근할 수 있게 가시성을 완화하거나 2. 리플렉션을 이용하거나 3. 테스트하지 않는다.

1번은 캡슐화 원칙을 위반한다. @VisibleForTesting은 1번을 대체하는 애너테이션이다.

Android Support Annotations 라이브러리를 활용한 결함 탐지

class SomeService {   public int process(int param) {       int intimidate = prosessStep1(param);       return processStep2(intimidate);   }   // 원래는 private이어야 하지만 테스트를 위해 package-private으로 가시성을 완화한다   @VisibleForTesting   /* private */ int processStep1(int param) {   } } class SomeServiceTest {   @Test   void testStep1() {       SomeService service = new SomeService();       // processStep1() 메서드의 가시성이 완화돼 테스트 클래스에서 접근할 수 있다.       assertEquals(0, service.processStep1(1));   } }

Reference

http://d2.naver.com/helloworld/8725603


댓글