빌드 속도 최적화

빌드 시간이 오래 걸리면 개발 프로세스가 느려집니다. 이 페이지에서는 빌드 속도 병목 현상을 해결하는 데 도움이 되는 몇 가지 기법을 제공합니다.

앱의 빌드 속도를 개선하는 일반적인 방법은 다음과 같습니다.

  1. 대부분의 Android 스튜디오 프로젝트에서 즉시 활용할 수 있는 몇 단계를 통해 빌드 구성을 최적화합니다.
  2. 개발자의 프로젝트나 워크스테이션에만 있을 수 있는 까다로운 병목 현상을 식별하고 진단하기 위해 빌드를 프로파일링합니다.

앱 개발 시 가능하면 Android 7.0(API 수준 24) 이상이 실행되는 기기에 앱을 배포합니다. 더 새로운 버전의 Android 플랫폼은 업데이트를 앱으로 푸시하기 위한 개선된 메커니즘을 구현합니다. 이러한 메커니즘에는 Android 런타임(ART)다중 DEX 파일 기본 지원이 있습니다.

참고: 이 페이지에 설명된 최적화를 전혀 사용하지 않더라도, 최초의 클린 빌드 후에는 이후의 빌드(클린 및 증분 빌드)가 더 빠르게 실행된다는 사실을 느끼실 수 있습니다. 그 이유는 다른 JVM 프로세스와 마찬가지로 성능 향상을 위한 '준비' 기간이 Gradle 데몬에 있기 때문입니다.

빌드 구성 최적화

다음 팁에 따라 Android 스튜디오 프로젝트의 빌드 속도를 개선해 보세요.

도구를 최신 상태로 유지

Android 도구에서는 거의 모든 업데이트마다 빌드 최적화와 새로운 기능이 구현됩니다. 이 페이지의 일부 도움말에서는 개발자가 최신 버전을 사용 중이라고 가정합니다. 최신 최적화 기능을 활용하려면 다음을 최신 상태로 유지하세요.

kapt 대신 KSP 사용

Kotlin 주석 처리 도구 (kapt)는 Kotlin 심볼 프로세서 (KSP)보다 상당히 느립니다. 주석이 달린 Kotlin 소스를 작성하고 KSP를 지원하는 주석 (예: Room)을 처리하는 도구를 사용하는 경우 KSP로 이전하는 것이 좋습니다.

불필요한 리소스의 컴파일 피하기

테스트 중이 아닌 리소스(예: 추가적인 언어 현지화 및 화면 밀도 리소스)는 컴파일과 패키징을 피하세요. 대신 다음 샘플과 같이 'dev' 버전에 언어 리소스 하나와 화면 밀도만 지정합니다.

Groovy

android {
    ...
    productFlavors {
        dev {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations "en", "xxhdpi"
        }
        ...
    }
}

Kotlin

android {
    ...
    productFlavors {
        create("dev") {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations("en", "xxhdpi")
        }
        ...
    }
}

Gradle 플러그인 포털을 마지막에 배치하여 실험

Android에서 모든 플러그인은 google()mavenCentral() 저장소에서 찾을 수 있습니다. 그러나 빌드에는 gradlePluginPortal() 서비스를 사용하여 결정되는 서드 파티 플러그인이 필요할 수 있습니다.

Gradle은 선언된 순서대로 저장소를 검색하므로 먼저 나열된 저장소에 대부분의 플러그인이 포함된 경우 빌드 성능이 개선됩니다. 따라서 settings.gradle 파일의 저장소 블록에 gradlePluginPortal() 항목을 마지막으로 배치하여 실험합니다. 대부분의 경우 이 방법은 중복 플러그인 검색을 최소화하고 빌드 속도를 개선합니다.

Gradle이 여러 저장소를 탐색하는 방법에 관한 자세한 내용은 Gradle 문서의 여러 저장소 선언을 참고하세요.

디버그 빌드에서 정적 빌드 구성 값 사용

디버그 빌드 유형의 경우 매니페스트 파일이나 리소스 파일에 들어가는 속성에는 항상 정적 값을 사용해야 합니다.

동적인 버전 코드, 버전 이름, 리소스 또는 매니페스트 파일을 변경하는 기타 빌드 로직을 사용하는 경우에는 변경할 때마다 전체 앱 빌드가 필요합니다. 단, 실제 변경에는 핫 스왑만 필요할 수도 있습니다. 이러한 동적 속성이 빌드 구성에 필요한 경우 이들 속성을 출시 빌드 변형으로 격리하고 디버그 빌드에서 값을 정적으로 유지합니다(다음 샘플 참고).

  ...
  // Use a filter to apply onVariants() to a subset of the variants.
  onVariants(selector().withBuildType("release")) { variant ->
      // Because an app module can have multiple outputs when using multi-APK, versionCode
      // is only available on the variant output.
      // Gather the output when we are in single mode and there is no multi-APK.
      val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

      // Create the version code generating task.
      val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
          it.outputFile.set(project.layout.buildDirectory.file("versionCode${variant.name}.txt"))
      }

      // Wire the version code from the task output.
      // map will create a lazy Provider that:
      // 1. Runs just before the consumer(s), ensuring that the producer (VersionCodeTask) has run
      //    and therefore the file is created.
      // 2. Contains task dependency information so that the consumer(s) run after the producer.
      mainOutput.versionCode.set(versionCodeTask.flatMap { it.outputFile.map { it.asFile.readText().toInt() } })
  }
  ...

  abstract class VersionCodeTask : DefaultTask() {

    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun action() {
        outputFile.get().asFile.writeText("1.1.1")
    }
  }

프로젝트에서 동적 버전 코드를 설정하는 방법을 알아보려면 GitHub에서 setVersionsFromTask 레시피를 참고하세요.

정적 종속 항목 버전 사용

build.gradle 파일에서 종속 항목을 선언할 때 동적 버전 번호(예: 'com.android.tools.build:gradle:2.+'와 같이 끝에 더하기 기호가 있는 버전)를 사용하지 마세요. 동적 버전 번호를 사용하면 예기치 않은 버전 업데이트가 발생할 수 있고 버전 차이를 해결하는 것이 어려워질 수 있으며 Gradle의 업데이트 확인으로 인해 빌드 속도가 느려질 수 있습니다. 대신 정적 버전 번호를 사용하세요.

라이브러리 모듈 생성

Android 라이브러리 모듈로 변환할 수 있는 코드를 앱에서 찾습니다. 이런 방식으로 코드를 모듈화할 경우, 빌드 시스템은 수정되는 모듈만을 컴파일할 수 있으며 향후 빌드를 위해 이러한 출력을 캐시할 수 있습니다. 또한 최적화를 사용 설정하는 경우 병렬 프로젝트 실행의 효과도 향상됩니다.

사용자설정 빌드 로직을 위한 작업 생성

빌드 프로필을 생성한 후에 빌드 시간의 상당 부분이 **프로젝트 구성** 단계에 사용된 것으로 빌드 프로필에 표시되면 build.gradle 스크립트를 검토하여 맞춤 Gradle 작업에 포함할 코드를 찾습니다. 일부 빌드 로직을 작업으로 이동하면 필요한 경우에만 작업이 실행되고 후속 빌드를 위해 결과를 캐시할 수 있으며 병렬 프로젝트 실행을 사용 설정한 경우 해당 빌드 로직이 병렬로 실행되도록 할 수 있습니다. 맞춤 빌드 로직 작업에 관한 자세한 내용은 공식 Gradle 문서를 참고하세요.

도움말: 빌드에 맞춤 작업이 다수 포함된 경우 맞춤 작업 클래스를 생성하여 build.gradle 파일을 단순하게 만들 수 있습니다. 클래스를 project-root/buildSrc/src/main/groovy/ 디렉터리에 추가하세요. Gradle은 프로젝트의 모든 build.gradle 파일에 관한 클래스 경로에 이러한 클래스를 자동으로 포함합니다.

WebP로 이미지 변환

WebP는 손실이 있는 압축 (예: JPEG)과 투명도(예: PNG)를 제공하는 이미지 파일 형식입니다. WebP는 JPEG 또는 PNG보다 더 나은 압축을 제공할 수 있습니다.

이미지 파일 크기를 줄이면 빌드 시간 압축을 하지 않고도 빌드 속도를 높일 수 있습니다(특히 많은 이미지 리소스가 앱에 사용되는 경우). 그러나 WebP 이미지의 압축을 해제하는 동안 기기 CPU 사용량이 약간 증가할 수 있습니다. Android 스튜디오를 사용하여 이미지를 WebP로 쉽게 변환하세요.

PNG 크런칭 비활성화

PNG 이미지를 WebP로 변환하지 않아도 앱을 빌드할 때마다 자동 이미지 압축을 사용 중지하여 빌드 속도를 높일 수 있습니다.

Android Gradle 플러그인 3.0.0 이상을 사용하는 경우 '디버그' 빌드 유형에서는 PNG 크런칭이 기본적으로 사용 중지됩니다. 다른 빌드 유형에서도 이 최적화를 사용 중지하려면 build.gradle 파일에 다음을 추가합니다.

Groovy

android {
    buildTypes {
        release {
            // Disables PNG crunching for the "release" build type.
            crunchPngs false
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Disables PNG crunching for the "release" build type.
            isCrunchPngs = false
        }
    }
}

빌드 유형이나 제품 버전은 이 속성을 정의하지 않으므로 출시 버전의 앱을 빌드할 때 이 속성을 true로 수동 설정해야 합니다.

JVM 병렬 가비지 수집기 실험

Gradle에서 사용하는 최적의 JVM 가비지 수집기를 구성하여 빌드 성능을 개선할 수 있습니다. JDK 8은 기본적으로 병렬 가비지 수집기를 사용하도록 구성되어 있지만, JDK 9 이상에서는 G1 가비지 수집기를 사용하도록 구성됩니다.

빌드 성능을 개선하려면 병렬 가비지 수집기로 Gradle 빌드를 테스트하는 것이 좋습니다. gradle.properties에서 다음을 설정합니다.

org.gradle.jvmargs=-XX:+UseParallelGC

다른 옵션이 이미 이 필드에 설정되어 있으면 새 옵션을 추가합니다.

org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC

다양한 구성으로 빌드 속도를 측정하려면 빌드 프로파일링을 참고하세요.

JVM 힙 크기 늘리기

느린 빌드가 관찰되고, 특히 빌드 분석 도구 결과에서 가비지 컬렉션이 빌드 시간의 15% 이상을 차지하는 경우 자바 가상 머신(JVM) 힙 크기를 늘려야 합니다. 다음 예와 같이 gradle.properties 파일에서 제한을 4, 6 또는 8GB로 설정하세요.

org.gradle.jvmargs=-Xmx6g

그런 다음 빌드 속도가 개선되었는지 테스트합니다. 최적의 힙 크기를 판단하는 가장 쉬운 방법은 제한을 약간 늘린 후 빌드 속도가 충분히 개선되었는지 테스트하는 것입니다.

JVM 병렬 가비지 수집기도 사용하는 경우 전체 줄은 다음과 같습니다.

org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g

HeapDumpOnOutOfMemoryError 플래그를 사용 설정하여 JVM 메모리 오류를 분석할 수 있습니다. 이렇게 하면 JVM이 메모리가 부족할 때 힙 덤프를 생성합니다.

비전환 R 클래스 사용

여러 모듈이 있는 앱을 더 빠르게 빌드하도록 하려면 비전환 R 클래스를 사용하세요. 이렇게 하면 종속 항목에서 참조를 가져오지 않고 각 모듈의 R 클래스에 자체 리소스 참조만 포함되도록 하여 리소스 중복을 방지할 수 있습니다. 이렇게 하면 빌드 속도가 빨라지고 그에 따른 컴파일 방지의 이점이 더 향상됩니다. 이는 Android Gradle 플러그인 8.0.0 및 이후 버전의 기본 동작입니다.

Android 스튜디오 Bumblebee부터 비전환 R 클래스가 새 프로젝트에 기본적으로 사용 설정됩니다. 이전 버전의 Android 스튜디오로 만든 프로젝트의 경우 Refactor > Migrate to Non-Transitive R Classes로 이동하여 비전환 R 클래스를 사용하도록 업데이트합니다.

앱 리소스 및 R 클래스에 관한 자세한 내용은 앱 리소스 개요를 참고하세요.

일정하지 않은 R 클래스 사용

앱 및 테스트에서 일정하지 않은 R 클래스 필드를 사용하여 자바 컴파일의 성과 증분을 개선하고 더 정확한 리소스 축소를 지원합니다. R 클래스 필드는 라이브러리에 종속되는 앱 또는 테스트의 APK를 패키징할 때 리소스의 번호가 지정되므로 항상 일정하지 않습니다. 이는 Android Gradle 플러그인 8.0.0 및 이후 버전의 기본 동작입니다.

Jetifier 플래그 사용 중지

대부분의 프로젝트는 AndroidX 라이브러리를 직접 사용하므로 Jetifier 플래그를 삭제하여 빌드 성능을 개선할 수 있습니다. Jetifier 플래그를 삭제하려면 gradle.properties 파일에 android.enableJetifier=false를 설정합니다.

빌드 분석 도구는 프로젝트의 빌드 성능을 높이고 관리되지 않은 Android 지원 라이브러리에서 이전하기 위해 플래그를 안전하게 삭제할 수 있는지 확인하는 검사를 실행할 수 있습니다. 빌드 분석 도구에 관한 자세한 내용은 빌드 성능 문제 해결을 참고하세요.

구성 캐시 사용

구성 캐시를 사용하면 Gradle이 빌드 작업 그래프에 관한 정보를 기록하고 이를 후속 빌드에서 재사용할 수 있으므로 Gradle이 전체 빌드를 다시 구성할 필요가 없습니다.

구성 캐시를 사용 설정하려면 다음 단계를 따르세요.

  1. 모든 프로젝트 플러그인이 호환되는지 확인합니다.

    빌드 분석 도구를 사용하여 프로젝트가 구성 캐시와 호환되는지 확인합니다. 빌드 분석 도구는 테스트 빌드 시퀀스를 실행하여 프로젝트에 이 기능을 사용 설정할 수 있는지 확인합니다. 지원되는 플러그인 목록은 문제 #13490을 참고하세요.

  2. 다음 코드를 gradle.properties 파일에 추가합니다.

      org.gradle.configuration-cache=true
      # Use this flag carefully, in case some of the plugins are not fully compatible.
      org.gradle.configuration-cache.problems=warn

구성 캐시가 사용 설정되면 처음 프로젝트를 실행할 때 빌드 출력에 Calculating task graph as no configuration cache is available for tasks가 표시됩니다. 후속 실행 중에는 빌드 출력에 Reusing configuration cache가 표시됩니다.

구성 캐시에 관한 자세한 내용은 구성 캐시 심층 분석 블로그 게시물 및 구성 캐시에 관한 Gradle 문서를 참고하세요.

Gradle 8.1 및 Android Gradle 플러그인 8.1에 발생한 구성 캐시 문제

Gradle 8.1에서 구성 캐시가 안정화되고 파일 API 추적이 도입되었습니다. Gradle은 File.exists(), File.isDirectory(), File.list()와 같은 호출을 기록하여 구성 입력 파일을 추적합니다.

Android Gradle 플러그인 (AGP) 8.1은 Gradle이 캐시 입력으로 간주해서는 안 되는 일부 파일에 이러한 File API를 사용합니다. 이로 인해 Gradle 8.1 이상에서 사용할 때 추가 캐시 무효화가 트리거되어 빌드 성능이 저하됩니다. 다음은 AGP 8.1에서 캐시 입력으로 처리됩니다.

입력 Issue Tracker 고정 위치
$GRADLE_USER_HOME/android/FakeDependency.jar 문제 #289232054 AGP 8.2
cmake 출력 문제 #287676077 AGP 8.2
$GRADLE_USER_HOME/.android/analytics.settings 문제 #278767328 AGP 8.3

이러한 API 또는 이러한 API를 사용하는 플러그인을 사용하면 빌드 시간에 회귀가 발생할 수 있습니다. 이러한 API를 사용하는 일부 빌드 로직이 추가 캐시 무효화를 트리거할 수 있기 때문입니다. 이러한 패턴에 관한 설명과 빌드 로직을 수정하거나 파일 API 추적을 일시적으로 사용 중지하는 방법은 빌드 구성 입력 추적 개선사항을 참고하세요.