안드로이드 ENUM과 Typedef 어노테이션(Annotation)

이 글은 ENUM 사용 시 주의할 점과 어노테이션을 통해 이를 보완할 수 있는 방법에 대해 정리한 글입니다.

주로 참고한 자료는 유튜브 perfmatter 시리즈 중 The price of ENUMs (100 Days of Google Dev) 영상과 Improve Code Inspection with Annotations 문서입니다.

안드로이드 앱 실행 시 메모리 할당

ENUM 사용 시 주의점을 알기 전에 먼저 안드로이드 앱 실행 시 메모리가 어떻게 할당되는지 간단히 짚고 넘어가겠습니다.

앱을 실행시키면 안드로이드는 시스템 메모리 곁에 앱을 위한 메모리를 할당합니다. 이렇게 할당된 메모리는 앱의 코드와 실행중에 동적으로 할당하는 메모리를 위해 사용됩니다. 여기서 앱의 코드는 안드로이드 앱을 빌드할 때 생성되는 DEX 파일입니다.

아래는 The price of ENUMs 영상에서 캡쳐한 이미지입니다.

Android Memory Allocation

ENUM 사용 시 주의점

ENUM은 앱의 코드(DEX) 크기와 런타임 메모리 사용량을 증가시킵니다.

앱의 DEX 크기가 증가된다는건 APK 파일도 커지고 앱이 실행됐을 때 사용하는 메모리의 양도 그만큼 늘어난다는걸 의미합니다. 또한 ENUM은 Integer나 String에 비해 더 많은 메모리를 런타임에 사용합니다.

ENUM은 얼마나 DEX 크기를 더 증가 시킬까?

The price of ENUMs (100 Days of Google Dev) 영상을 보면 그 차이를 분명히 알 수 있습니다.

먼저 상수를 아래와 같이 Integer로 선언한 경우, DEX의 크기는 124 bytes가 늘어납니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static final int VALUE1 = 1;
public static final int VALUE2 = 2;
public static final int VALUE3 = 3;

int func(int value) {
switch(value) {
case VALUE1:
return -1;
case VALUE2:
return -2;
case VALUE3:
return -3;
}
return 0;
}

반면에 이를 ENUM으로 선언하면 DEX의 크기는 1,632 bytes가 증가합니다. 무려 13배나 차이가 납니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static enum Value {
VALUE1,
VALUE2,
VALUE3
}

int func(Value value) {
switch(value) {
case VALUE1:
return -1;
case VALUE2:
return -2;
case VALUE3:
return -3;
}
return 0;
}

여기서 바로 ENUM을 사용할 때 왜 주의해야 하는지를 알 수 있습니다. ENUM 사용으로 인한 DEX의 크기가 커지면 앱과 시스템이 사용하는 메모리는 줄어들게 됩니다.

ENUM을 사용함으로써 증가하는 메모리의 양은 어찌보면 그리 크지 않다 여길 수 있습니다. 하지만 ENUM을 무심코 사용하다보면 그 크기는 결코 무시할 수 없게 됩니다.

해결방법 첫 번째, Proguard 사용

프로가드를 사용하면 빌드 시 enum을 Integer로 바꾸는 최적화를 수행해 줍니다.

해결방법 두 번째, Typedef 어노테이션 사용

위의 예제처럼 ENUM의 대안으로 간단히 Integer를 사용함으로써 위의 이슈를 피해갈 수 있습니다. 하지만 ENUM이 괜히 있는게 아닙니다. ENUM을 사용하면 컴파일 및 런타임에 타입 및 값을 체크할 수 있습니다.

API의 인자나 반환 값에 제약을 줌으로써 API가 오동작을 일으킬 여지를 최대한 줄이는 것은 좋은 코드 작성하는 원칙중에 하나입니다. 하지만 단순히 Integer로 상수를 만들어 사용하는 것 만으로는 이런 제약을 줄 수 없습니다.

하지만 Typedef 어노테이션을 사용하면 컴파일 시에 warning을 통해 이를 미리 확인할 수 있게됩니다.

Typedef 어노테이션이란?

Typedef 어노테이션은 어노테이션을 통해 특정 값의 유효성을 컴파일 시 확인할 수 있는 기능입니다. 자세한 사용법은 Improve Code Inspection with Annotations 이 곳에서 확인 할 수 있습니다.

여기서는 간단한 예제를 가지고 사용법을 확인해보도록 하겠습니다.

먼저 어노테이션을 사용하기 위해 build.gradle 파일에 서포트 라이브러리를 추가해줍니다.

1
dependencies { compile 'com.android.support:support-annotations:24.2.0' } 

그리고 아래와 같이 Typedef 어노테이션을 사용합니다. 아래의 예제는 실제 안드로이드 ActionBar 코드 중 일부입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import android.support.annotation.IntDef;
...
public abstract class ActionBar {
...
// Define the list of accepted constants and declare the NavigationMode annotation
@Retention(RetentionPolicy.SOURCE)
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
public @interface NavigationMode {}

// Declare the constants
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

// Decorate the target methods with the annotation
@NavigationMode
public abstract int getNavigationMode();

// Attach the annotation
public abstract void setNavigationMode(@NavigationMode int mode);

NavigationMode라는 Typedef 어노테이션을 생성합니다. NavigationMode에는 @IntDef를 통해 NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS 세 가지 값만 들어올 수 있음을 명시하고 있고, 각 상수는 아래 public static final int로 선언이 되어 있습니다. **@Retention(RetentionPolicy.SOURCE)**는 컴파일러가 .clss에 @NavigationMode의 데이터를 저장하지 않도록 합니다.

이제 NavigationMode는 함수의 반환값이나 인자, 객체의 필드에 사용될 수 있습니다. 코드에서 보면 getNavigationMode() 함수의 반환값과 setNavigationMode() 함수의 인자에 @NavigationMode 어노테이션이 붙어있습니다.

이를 통해 getNavigationMode()의 반환값이나 setNavigationMode()의 mode 인자에 NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS 외의 값이 들어가면 warning이 발생합니다.

이렇게 생성한 Typedef 어노테이션은 안드로이드 스튜디오의 코드 완성에도 적용되어 간편하게 사용할 수 있습니다. @StringDef를 통해 문자열도 동일하게 사용이 가능합니다.

Typedef 어노테이션 사용 시 주의점

Typedef 어노테이션을 사용한다 해도 빌드는 정상적으로 수행됩니다. 따라서 반드시 IDE 자체에서 실시간으로 보여주는 warning을 확인하거나, 안드로이드 스튜디오의 Analyze -> Code Infection을 통해 warning을 확인 하는 것이 중요합니다.

마무리

이 글은 ENUM 사용 시 주의할 점을 정리한 글이지만 ENUM을 사용하지 말라는 글은 절대 아닙니다.

ENUM이 DEX의 크기를 증가시키는 문제는 있지만 ENUM을 사용하면 코드의 가독성과 타입의 값에 대한 유효성(Type safety)이 좀 더 보장됩니다.

이는 굉장한 장점입니다. 따라서 가독성이나 안정성이 중요시 되는 코드에는 ENUM을 사용하는게 맞습니다.

비슷한 예로 C언어의 매크로 함수가 있습니다. 매크로 함수는 함수처럼 사용할 수 있지만 컴파일 시에 직접 코드로 치환되어 실행파일에 삽입됩니다. 따라서 매크로 함수를 사용하면 함수 호출의 부하를 줄일 수 있습니다.

하지만 매크로 함수도 잘 못 사용하면 오히려 성능을 저하시킵니다. 가령 코드의 너무 많은 곳에서 매크로 함수를 호출하게되면 호출 부에 모두 코드로 치환되기 때문에 코드 영역의 크기가 매우 커질 수 있습니다. 즉 경우에 따라 오히려 성능을 떨어뜨릴 수 있습니다.

ENUM과 Typedef 어노테이션 역시 비슷합니다. 각각의 장단점을 보고 필요한 곳에 잘 사용하는 것이 중요합니다. 또한 Proguard를 사용하면 위의 최적화를 대부분 알아서 수행해 줍니다. 어찌보면 굳이 Typedef 어노테이션을 사용할 필요가 없을지도 모릅니다.

이런 관점에서보면 안드로이드 플랫폼 코드에 Typedef 어노테이션을 권장하는 이유를 알 수 있습니다. 안드로이드 플랫폼 코드는 프로가드를 적용하지 않고, 전체 코드의 크기가 굉장히 크기 때문에 ENUM의 사용을 제한하지 않으면 플랫폼 코드가 대책없이 커질 수 있으니 딱 Typedef 어노테이션을 사용하기 좋은 곳이겠죠.

참고 자료

The price of ENUMs (100 Days of Google Dev)

Perfmatter 시리즈 영상 중 ENUM 사용 시 주의점에 대해 설명하는 영상입니다.

Improve Code Inspection with Annotations

구글 개발자 페이지 문서입니다. 어노테이션을 이용해서 빌드 시 코드의 잠재적인 이슈를 미리 확인하는 방법을 다룹니다.

안드로이드 그래들 빌드 속도 올리기

이 글은 2017년 5월 구글I/O의 Speeding Up Your Android Gradle Builds (Google I/O ‘17 세션 중 일부를 정리한 내용입니다.

IMAGE ALT TEXT HERE

발표 영상, 슬라이드는 맨 아래 참고 링크에서 확인 할 수 있습니다. 발표자는 안드로이드 스튜디오 팀의 James Lau 입니다.

영상을 보면, 산타트래커란 샘플 프로젝트에 하나씩 최적화를 적용하면서 어느정도 최적화가 되었는지 보여주는 방식으로 진행됩니다. 빌드속도는 당연히 프로젝트의 구성과 환경에 영향을 받습니다. 발표자가 테스트에 사용한 산타트래커는 아래와 같이 구성되었습니다.

  • 안드로이드 웨어를 포함한 9개의 모듈
  • 500개의 자바 파일
  • 1700개의 xml
  • 3500개의 PNG
  • 멀티덱스
  • 60MB 정도의 apk를 크기
  • 어노테이션프로세서는 사용치 않음

벤치마크는 아래 세 가지 경우로 보여줍니다. incremental build는 구글 개발자 페이지에 증분 빌드로 번역하고 있어서 그대로 사용했습니다. 참고 링크

  • 전체 빌드(Full build)
    • Clean, assemble debug
  • 증분 빌드(Incremental build - Java change)
    • 한 메소드 안에 한줄의 자바코드 수정
  • 증분 빌드(Incremental build - resource changing)
    • 스트링 리소스 한 개를 추가하거나 제거

빠른 빌드를 위한 10가지 팁

세션에서 소개된 팁은 총 10개로 아래 목차와 같습니다.

  1. 최신 안드로이드 그래들 플러그인을 쓸 것
    • Use lastest Android Gradle Plugin
  2. 레거시 멀티덱스를 피할 것
    • Avoid legacy multidex
  3. 멀티 APK를 사용하지 말 것
    • Disable multi-APK
  4. 최소한의 리소스만 포함 시킬 것
    • Include minimal resources
  5. PNG 최적화 설정을 사용하지 말 것
    • Disable PNG crunching
  6. 인스턴트 런을 사용할 것
    • User Instant Run
  7. 의도치 않은 수정은 피할 것
    • Avoid inadvertent changes
  8. 다이나믹 버전은 사용하지 말 것
    • Don’t use dynamic version
  9. 메모리를 확인할 것
    • Watch the memory
  10. 그래들 캐시를 사용할 것
    • Use Gradle Cache

1. 최신 안드로이드 그래들 플러그인을 쓸 것

안드로이드 스튜디오 팀에서는 빌드 속도와 버그수정을 위해 그래들 플러그인을 계속 업데이트 하고 있습니다. 그래들 3.0부터는 구글 메이븐 저장소를 통해 그래들이 배포됩니다. 따라서 메이븐 구글 저장소를 추가해주고, dependency에 gradle 3.0.0을 추가해줍니다. 발표 영상에는 alpha1인데 오늘(2017.09.20일) 확인해본 바로는 beta4까지 나왔네요.

1
2
3
4
5
6
7
8
9
buildscript {
repository {
jcenter()
maven { url 'https://maven.google.com' }
}
dependency {
classpath 'com.android.tools.build:gradle:3.0.0-alpha1'
}
}

결과를 보니 단순히 그래들 버전을 3.x대로 올리는 것만으로 속도가 아래와 같이 엄청나게 향상됩니다. 아래 결과는 발표자 영상에 있는걸 그대로 옮긴거구요, 대강 어느정도 향상되는지 참고 하시면 될 것 같습니다.

빌드 타입 | 속도 차이(초) | 속도 차이(백분율)
— | — |
전체 빌드 | -15s | -25%
증분 빌드(자바) | -10s | -38%
증분 빌드(리소스) | -2.5s | -16%

2. 레거시 멀티덱스를 피할 것

멀티덱스 사용 시 minSdkVersion < 21인 경우 레거시 멀티덱스가 적용됩니다. 그리고 이 레거시 멀티덱스는 빌드 속도를 심각하게 다운 시킵니다. 안드로이드 스튜디오 2.3+에서부터는 런 버튼을 클릭해서 앱을 실행하면, 연결된 디바이스나 에물레이터의 API 버전을 감지해서 가능하면 네이티브 멀티덱스를 적용해준다고 합니다. 참고로 커맨드라인으로 빌드하는 경우엔 이렇게 자동으로 버전을 감지해서 최적화 해주는 기능은 동작하지 않습니다.

만약 프로덕트가 api 레벨 21 미만의 단말도 지원해야 한다면 아래와 같이 빌드 변형 구성을 이용해 개발 용 빌드의 minSdkVersion을 21로 설정할 수 있습니다.

1
2
3
4
5
6
7
productFlavors {
development {
minSdkVersion 21
...
}

}

결과는 아래와 같습니다. 이것도 성능 향상이 꽤 크네요.

빌드 타입 | 속도 차이(초) | 속도 차이(백분율)
— | — |
전체 빌드 | -5.5s | -12%
증분 빌드(자바) | -8s | -53%
증분 빌드(리소스) | same | same

3. 멀티 APK를 사용하지 말 것

멀티 APK는 ABI나 density에 따라 apk를 다르게 빌드하는 기능입니다. 한 프로젝트에 대해 여러 APK가 생성되지만, 각 빌드의 결과물로 나오는 APK 크기가 작아지는 장점이 있습니다. 하지만 개발환경에선 필요없는 기능이죠. 따라서 개발 용 빌드인 경우에는 멀티 APK 기능을 아래와 같이 꺼줍니다.

1
2
3
4
5
6
android {
if (project.hasProperty('devBuild')) {
splits.abi.enable = false
splits.density.enable = false
}
}

위의 그래들 빌드 스크립트를 보면 ‘devBuild’란 속성을 확인하는데 저 속성은 커맨드라인 빌드 시 아래와 같이 넘겨줍니다.

1
./gradlew santa-tracker:assembleDevelopmentDebug -PdevBuild

이걸 안드로이드 스튜디오에 적용한다면 preference -> Build, Execution, Deployment -> Compiler -> Command-line Option 에 -PdevBuild을 적어주면 됩니다.

결과는 아래와 같습니다. 리소스에 대한 증분 빌드 시에 많은 속도 향상이 있네요.

빌드 타입 | 속도 차이(초) | 속도 차이(백분율)
— | — |
전체 빌드 | -4.8s | -12%
증분 빌드(자바) | -0.5s | -6%
증분 빌드(리소스) | -3s | -26%

4. 최소한의 리소스만 포함시킬 것

앱이 다국어를 지원한다면 여러 스트링 리소스를 포함합니다. 다국어를 지원하지 않더라도 대부분의 앱은 하나 이상의 스크린 density를 지원하는 경우가 많습니다. 안드로이드의 빌드 시 기본 설정은 모든 버전의 리소스를 빌드에 포함시키도록 되어있습니다. 하지만 아래와 같이 특정 버전의 리소스들만 포함시키도록 할 수 있습니다. 아래는 개발용 빌드에 특정 리소스만 포함 시키도록 하는 그래들 설정 예시입니다.

1
2
3
4
5
6
7
productFlavor {
developement {
minSdkVersion 21
resConfigs ("en", "xxhdpi")
...
}
}

적용 결과입니다.

빌드 타입 | 속도 차이(초) | 속도 차이(백분율)
— | — |
전체 빌드 | -6s | -17%
증분 빌드(자바) | -1.5s | -24%
증분 빌드(리소스) | -2s | -21%

5. 개발 빌드에는 PNG 최적화를 끄자

안드로이드 빌드 툴은 PNG 크기 최적화를 기본으로 수행합니다. 최적화라면 PNG 파일들을 압축하는 거겠죠. 당연히 PNG 파일이 많으면 빌드 시 많은 시스템 자원을 사용하게 됩니다. 릴리즈 시에는 APK 크기를 줄여주므로 중요한 기능이지만 개발 시에는 별 필요 없는 기능입니다. 역시나 아래와 같이 그래들 빌드 스크립트를 이용해 설정을 살포시 꺼줍니다.

1
2
3
4
5
6
7
8
productFlavor {
developement {
minSdkVersion 21
resConfigs ("en", "xxhdpi")
aaptOptions.cruncherEnabled = false
...
}
}

아니면 아예 webP 포맷을 사용하는 것도 좋은 방법입니다. webP는 PNG 파일보다 최대 25% 작은 크기를 갖는 포맷입니다. 안드로이드 스튜디오 2.3+ 부터는 IDE에서 PNG를 webP로 변환하는 기능을 지원합니다. 불투명 이미지는 안드로이드 API 15+, 투명도를 갖는 이미지는 API 18+부터 지원합니다. 또한 애초에 최적화된 PNG를 사용하면 최적화가 수행되어도 실제 APK의 크기는 별 차이가 없습니다. 그냥 시간만 잡아 먹는 거죠.

아래는 위의 PNG 최적화 기능을 끈 결과입니다.

빌드 타입 | 속도 차이(초) | 속도 차이(백분율)
— | — |
전체 빌드 | -9s | -33%
증분 빌드(자바) | same | same
증분 빌드(리소스) | same | same

6. 인스턴트 런을 사용할 것

인스턴트 런은 안드로이드 스튜디오 2.0 때 런칭된 기능입니다. 발표자 말로는 런칭 이후 인스턴트 런의 신뢰성(reliability)를 위해 많은 노력을 기울였다고 합니다. 바꿔말하면 그동안에 인스턴트 런은 문제가 많았다는 뜻이겠죠. 저 역시도 인스턴트 런을 사용하지 않는데 이유는 수정한 코드가 실제로 빌드에 적용이 되지 않아 삽질을 한 기억이 많기 때문입니다. 수정한 코드가 빌드에 적용이 안된다면 빌드가 아무리 빨리 된들 무슨 소용이 있을까요.

하지만 안드로이드 스튜디오 3.0에 적용된 인스턴트 런은 2.0과 매우 다르다 합니다. 한번 믿어보는것도 좋을것 같습니다. 참고로 플랫폼의 한계에 따라 적용했던 많은 핵들을 신뢰성을 위해 과감히 제거하고 대신 안드로이드 스튜디오 3.0부터의 인스턴트 런은 API 레벨 21 이상에서만 동작한다고 합니다. (minSdk가 21 이상이여야 한다는게 아니라 연결된 단말의 버전이 21 이상이면 된다는 얘기입니다!)

안드로이스 스튜디오 3.0 이상에서는 인스턴트 런과 일반 빌드를 쉽게 구분하기 위해 런 버튼을 Run과 Apply Changes 두 개로 분리 했습니다.

Run 버튼을 클릭하면 콜드 스왑을 시도하고 앱은 재실행 됩니다. 반면 Apply Changes 버튼을 클릭하면 hot or warm swap을 시도합니다. Cold, hot, warm swap에 대한 내용은 이 링크에서 자세히 알 수 있습니다.

인스턴트 런을 실행하면 연결된 디바이스의 API, 스크린 density등을 분석해서 자동으로 필요한 최적화를 수행해 줍니다. 즉 앞서 얘기했던 특정 버전의 리소스만 포함하는 빌드를 생성한다거나 멀티 APK등에 대한 최적화 들을 알아서 해준다는 것 같습니다.

아래는 인스턴트 런을 적용한 결과입니다.

빌드 타입 | 속도 차이(초) | 속도 차이(백분율)
— | — |
전체 빌드 | +7s | +37%
증분 빌드(자바) | -3s | -54%
증분 빌드(리소스) | -3s | -42%

특이한게 인스턴트 런을 적용 했을 때, 풀빌드의 경우 오히려 빌드 시간이 증가했습니다. 이는 인스턴트 런이 sharding을 APK에 적용해야하기 때문이라 합니다. 이 sharding은 swap(cold, hot, warm)을 하기 위해 필요한 미리 수행되어야 하는 작업으로 발표자는 풀빌드에서 시간이 더 걸리지만 이후의 증분 빌드에서 시간을 줄일 수 있다고 이야기합니다. 간단히 위의 결과를 가지고 생각해보면 풀빌드 한번 후 증분 빌드를 2번만 하면 거의 동일하고 2번 이상부터는 매번 3초가량 시간을 벌 수 있겠네요.

7. 의도치 않은 수정은 피할 것

1
2
3
4
5
6
7
8
def buildDateTime = new Date().format('yyMMddHHmm').toInteger()

android {
defaultConfig {
versionCode buildDateTime
...
}
}

위의 코드는 매 빌드가 유일한 버전코드를 갖도록 시간을 가지고 버전 코드를 설정하고 있습니다. 별거 아닌것 같은 코드이지만 이 코드는 매번 빌드를 할때마다 AndroidManifest를 변경합니다. 자세히 얘기하면 versionCode같은 값들은 그래들 빌드 스크립트에 기술되어 있지만 실제 빌드가 되는 과정에서 AndroidManifest.xml에 합쳐(merge)집니다.참고.

위의 코드를 보면 매번 빌드 할때마다 versionCode가 변경되는데 이는 곧 매 빌드 마다 AndroidManifest의 uses-sdk 속성이 변경 되는 것이죠.참고. 아무튼 위의 코드가 적용된 후 결과는 아래와 같습니다.

빌드 타입 | 속도 차이(초) | 속도 차이(백분율)
— | — |
증분 빌드(자바) | +3s | +130%
증분 빌드(리소스) | +3.6s | +90%

이런 문제를 개선하기 위해서는 앞서 개발 빌드 시에 멀티 APK를 적용하지 않았던 방법을 이용하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
def buildDateTime = project.hasProperty('devBuild')? 100: new Date().format('yyMMddHHmm').toInteger()

android {
...
defaultConfig {
...
versionCode buildDateTime
...
}
...
}

8. 다이나믹 버전은 사용하지 말 것

다이나믹 버전은 아래와 같이 그래들에 외부 라이브러리를 임포트 할 때, 뒤에 +를 붙여서 가장 최근 버전의 라이브러리를 가져올 수 있게하는 기능입니다.

발표자의 설명에 의하면 이 기능이 라이브러리의 새 버전을 추가로 확인하게 만들기 때문에 의존성 결정(dependency resolution) 시간을 증가시킨다고 합니다.

1
2
3
4
5
android {
dependencies {
compile 'com.android.support:appcompat-v7:+'
}
}

또 하나의 문제는 이게 앱을 nondeterministic하게 만든다는 겁니다. 코드의 수정이 전혀 없더라도 라이브러리의 새 버전이 나오면 빌드가 달라진다는 겁니다. 이거는 앱의 형상관리랑도 연관이 있는 부분입니다.

대부분의 프로젝트는 git같은 형상관리 툴을 이용해서 관리합니다. 형상관리 툴의 핵심기능이자 키포인트 중에 하나는, 특정 시점의 빌드를 내가 원할때 빌드해 낼 수 있고 이 빌드는 항상 동일해야 한다는 겁니다. 바이너리 수준에서 완전히 동일하진 않을 수도 있겠지만 적어도 라이브러리가 바뀌면 안되겠죠.

오래 전 내보낸 빌드에 핫픽스가 필요해서 코드의 특정 부분만 고쳐 새로 빌드를 했는데 임포트하는 라이브러리중에 업데이트가 된게 있다면? 최악의 경우 업데이트 된 라이브러리가 하위호환성을 지켜주지 않는다면? 꽤 골치아픈 상황이 되겠죠.

9. 메모리를 확인할 것

그래들에 할당하는 메모리

1
2
#gradle.property 파일
org.gradle.jvmargs=-Xmx1536m

덱스 처리 시 사용하는 메모리

1
2
3
4
#build.gradle 파일
dexOptions {
javaMaxHeapSize = "4g"
}

그래들에 할당하는 메모리의 적정량은 프로젝트 마다 다르니 메모리를 잘 조절하면서 최적의 값을 찾으라고 합니다. 한가지 주의할 점은 무작정 메모리를 많이 할당하면 오히려 성능이 떨어질 수도 있습니다. 본인 시스템의 메모리 용량에 따라 잘 맞춰서 설정해야합니다.

두번째는 덱스 프로세스의 자바 힙 크기를 설정하는 방법입니다. 덱스 프로세스가 기존에는 out-process로 동작했으나 안드로이드 스튜디오 2.1 부터 기본값이 in-process로 변경되어 저 설정은 이제 의미가 없다고 합니다. 있으면 그냥 지워버리라네요. 근데 여기서 out-process랑 in-process가 뭔지 조금 애매합니다. 기존에는 별도의 프로세스로 처리하다가 2.1부터는 안드로이드 스튜디오 프로세스 내에서 처리한다는 걸로 이해했는데 맞는지 모르겠네요. 안드로이드 스튜디오에서 빌드 한번 돌리면 램을 2기가 넘게 써대던데 이것 때문일지도 모르겠습니다.

10. 그래들 캐시를 사용할 것

그래들의 새 캐싱 메카니즘 설정이라는데 모든 작업 결과를 캐시할 수 있다고 합니다. 안드로이드 스튜디오 2.3에 소개됐던 빌드 캐시랑 다른 점은 빌드 캐시는 pre-dexed external libararies만 캐시하는데 요건 가장 최근 빌드 뿐만 아니라 이전 빌드에 대한 캐시도 있다고 합니다. 또한 브랜치를 오가며 빌드하는 경우에도 캐시가 되고 캐싱된걸 배포할수도 있다고 하는데… 뭔 말인지는 잘 모르겠는데 어쨌든 안드로이드 스튜디오 3.0에 제대로 들어갈거라 하니 안드로이드 스튜디오 3.0이 정식 릴리즈 되면 그때 지켜봐야겠습니다.

1
2
# Set this in gradle.properties
org.gradle.caching=true

마무리

위의 최적화를 모두 마친 결과가 아래와 같습니다.

  • 전체 빌드 : 59s -> 19s로 3배 빨라짐
  • 증분 빌드(자바) : 24s -> 2s로 12배 빨라짐
  • 증분 빌드(리소스): 15s -> 4.5s로 3배 빨라짐

팁들을 대강 살펴보면 개발 빌드에 필요 없는 항목들은 빌드를 하지 않는데 초점이 맞쳐져 있는걸 알 수 있습니다. 즉 개발 중에는 위의 팁들을 이용해 빌드 속도가 많이 향상될 수 있지만 릴리즈 빌드 시에는 결국 비슷한 속도로 빌드가 진행되게 됩니다.

또한 위의 팁들은 빌드 툴의 영향을 받는 것들이 대부분입니다. 즉 발표자의 발표 시점과 실제 적용 시에는 차이가 있을 수 있습니다. 따라서 위의 결과들은 앞으로 안드로이드 스튜디오나 빌드 툴이 변해가며 계속 변할 수 있는 것들이라는 점을 유념해야 합니다.

어쨋거나 개발 과정에 빌드 속도는 생산성에 큰 영향을 미치는 요소입니다. 빌드 속도가 빠르면 코드 수정 후 결과를 빠르게 확인 할 수 있고 그만큼 집중력을 유지할 수 있겠죠. 개인적으로는 아주 재미있었던 세션이였습니다. 시간이 되시는 분들은 원래 영상을 꼭 직접 보시면 좋을 것 같습니다.

번외(직접 테스트)

참고로 제가 개발하는 회사 프로젝트에 간단히 적용할 수 있는 최적화를 몇개 해보았는데 전체 빌드 기준 1분41초 -> 56초의 개선이 있었습니다.

적용한 최적화 팁

  • 그래들 버전업(2.2.2 -> 3.0.0-beta6)
  • 레거시 멀티 덱스 우회(개발 빌드 시 minSdk 16 -> 19)
  • PNG Crunch 모드 끄기
  • 필요한 리소스만 빌드(xhdpi만 빌드)
최적화 전 최적화 후
101초 56초

참고 링크

Speeding Up Your Android Gradle Builds (Google I/O ‘17

구글 I/O의 ‘Speeding Up Your Android Gradle Builds’ 세션 영상입니다.

Slide for the speech(Spddeing Up Your Android Gradle Build)

발표자의 발표자료입니다.

Optimize Your Build Speed

안드로이드 개발자 사이트의 Optimize Your Build Speed 문서입니다. 발표 영상에 나오는 대부분의 내용들을 볼 수 있습니다.

Build and Run Your App

안드로이드 빌드 및 실행에 대한 전반적인 설명이 있는 페이지입니다.

Cold, hot, warm swap

콜드, 핫, 웜 스왑에 대한 설명입니다.

Version Your App

안드로이드 버전 설정에 대한 내용입니다.

AndroidManifest uses-sdk property

그래들에 명시한 sdk 관련 설정들이 빌드 과정에서 AndroidManifest에 병합되어 처리된다는 걸 알 수 있습니다.

About Android Plugin for Gradle 3.0.0

그래들 3.0.0 버전에 대한 설명 페이지입니다.