빌드 속도 최적화

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

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

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

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

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

빌드 구성 최적화

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

도구를 최신 상태로 유지

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

개발 시 빌드 변형 생성

앱 릴리스를 준비할 때 필요한 상당수 구성은 앱 개발 중에는 필요 없습니다. 불필요한 빌드 프로세스를 사용 설정하면 증분 및 클린 빌드의 속도가 느려지므로, 앱 개발 중에 필요한 빌드 구성만을 유지하도록 빌드 변형을 구성하세요. 다음 샘플은 릴리스 버전 구성에 관해 'dev' 버전 및 'prod' 버전을 만듭니다.

    android {
      ...
      defaultConfig {...}
      buildTypes {...}
      productFlavors {
        // When building a variant that uses this flavor, the following configurations
        // override those in the defaultConfig block.
        dev {
          // To avoid using legacy multidex when building from the command line,
          // set minSdkVersion to 21 or higher. When using Android Studio 2.3 or higher,
          // the build automatically avoids legacy multidex when deploying to a device running
          // API level 21 or higher—regardless of what you set as your minSdkVersion.
          minSdkVersion 21
          versionNameSuffix "-dev"
          applicationIdSuffix '.dev'
        }

        prod {
          // If you've configured the defaultConfig block for the release version of
          // your app, you can leave this block empty and Gradle uses configurations in
          // the defaultConfig block instead. You still need to create this flavor.
          // Otherwise, all variants use the "dev" flavor configurations.
        }
      }
    }
    

빌드 구성이 이미 제품 버전을 사용하여 서로 다른 버전의 앱을 만드는 경우 버전 차원을 사용하여 'dev' 및 'prod' 구성을 이 제품 버전과 조합할 수 있습니다. 예를 들어 'demo' 및 'full' 버전을 이미 구성한 경우 다음 샘플 구성을 사용하여 'devDemo' 및 'prodFull'과 같은 결합된 버전을 만들 수 있습니다.

    android {
      ...
      defaultConfig {...}
      buildTypes {...}

      // Specifies the flavor dimensions you want to use. The order in which you
      // list each dimension determines its priority, from highest to lowest,
      // when Gradle merges variant sources and configurations. You must assign
      // each product flavor you configure to one of the flavor dimensions.

      flavorDimensions "stage", "mode"

      productFlavors {
        dev {
          dimension "stage"
          minSdkVersion 21
          versionNameSuffix "-dev"
          applicationIdSuffix '.dev'
          ...
        }

        prod {
          dimension "stage"
          ...
        }

        demo {
          dimension "mode"
          ...
        }

        full {
          dimension "mode"
          ...
        }
      }
    }
    

단일 변형 프로젝트 동기화 사용 설정

빌드 구성과 프로젝트를 동기화하는 단계는 Android 스튜디오가 프로젝트 구성 방식을 이해하는 데 중요합니다. 하지만 프로젝트 규모가 큰 경우 이 과정에 시간이 오래 걸릴 수 있습니다. 프로젝트에서 빌드 변형을 여러 개 사용하는 경우 이제 프로젝트 동기화를 현재 선택한 변형으로만 제한하여 동기화를 최적화할 수 있습니다.

이 최적화를 사용 설정하려면 Android 스튜디오 3.3 이상과 Android Gradle Plugin 3.3.0 이상을 사용해야 합니다. 최적화는 모든 프로젝트에서 기본적으로 사용 설정됩니다.

이 최적화를 수동으로 사용 설정하려면 File > Settings > Experimental > Gradle(Mac은 Android 스튜디오 > Preferences > Experimental > Gradle)을 클릭하고 Only sync the active variant 체크박스를 선택합니다.

참고: 이 최적화는 자바 및 C++ 언어를 포함하는 프로젝트를 완전히 지원하며 Kotlin을 일부 지원합니다. Kotlin 콘텐츠가 포함된 프로젝트의 최적화를 사용 설정하면 Gradle 동기화는 전체 변형을 내부적으로 사용하는 방식으로 되돌아갑니다.

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

테스트 중이 아닌 리소스(예: 추가적인 언어 현지화 및 화면 밀도 리소스)는 컴파일과 패키징을 피하세요. 그렇게 하려면 'dev' 버전에 관해 하나의 언어 리소스와 화면 밀도만을 지정하면 됩니다(다음 샘플 참조).

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

디버그 빌드 시 Crashlytics 사용 중지

Crashlytics 보고서를 실행할 필요가 없는 경우 다음과 같이 플러그인을 사용 중지하여 디버그 빌드의 속도를 높입니다.

    android {
      ...
      buildTypes {
        debug {
          ext.enableCrashlytics = false
        }
    }
    

또한 아래와 같이 앱에서 Fabric 지원을 초기화하는 방식을 변경하여 디버그 빌드 시 런타임에 Crashlytics 키트를 사용 중지해야 합니다.

Kotlin

    // Initializes Fabric for builds that don't use the debug build type.
    Crashlytics.Builder()
            .core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
            .build()
            .also { crashlyticsKit ->
                Fabric.with(this, crashlyticsKit)
            }
    

자바

    // Initializes Fabric for builds that don't use the debug build type.
    Crashlytics crashlyticsKit = new Crashlytics.Builder()
        .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
        .build();

    Fabric.with(this, crashlyticsKit);
    

자동 빌드 ID 생성 사용 중지

Crashlytics를 디버그 빌드와 함께 사용하려는 경우, 모든 빌드 중에 개발자는 Crashlytics가 자체의 고유 빌드 ID로 앱 리소스를 업데이트하는 것을 방지함으로써 증분 빌드의 속도를 높일 수 있습니다. 이 빌드 ID는 manifest에 의해 참조되는 리소스 파일에 저장되므로 자동 빌드 ID 생성을 사용 중지하면 디버그 빌드 시 Crashlytics와 함께 변경사항을 적용하는 기능을 사용 할 수 있습니다.

Crashlytics가 빌드 ID를 자동으로 업데이트하지 못하게 하려면 build.gradle 파일에 다음을 추가합니다.

    android {
      ...
      buildTypes {
        debug {
          ext.alwaysUpdateBuildId = false
        }
    }
    

Crashlytics 사용 중에 빌드를 최적화하는 방법에 관한 자세한 내용은 공식 문서를 참조하세요.

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

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

예를 들어, manifest 파일을 변경하는 동적인 버전 코드, 버전 이름, 리소스 또는 기타 빌드 로직을 사용하는 경우에는 변경할 때마다 전체 APK 빌드가 필요합니다. 단, 실제 변경에는 핫 스왑만 필요할 수도 있습니다. 이러한 동적 속성이 빌드 구성에 필요한 경우 이들 속성을 릴리스 빌드 변형으로 격리시키고 디버그 빌드에서 해당 값을 정적으로 유지합니다(아래 build.gradle 파일 참조).

    int MILLIS_IN_MINUTE = 1000 * 60
    int minutesSinceEpoch = System.currentTimeMillis() / MILLIS_IN_MINUTE

    android {
        ...
        defaultConfig {
            // Making either of these two values dynamic in the defaultConfig will
            // require a full APK build and reinstallation because the AndroidManifest.xml
            // must be updated.
            versionCode 1
            versionName "1.0"
            ...
        }

        // The defaultConfig values above are fixed, so your incremental builds don't
        // need to rebuild the manifest (and therefore the whole APK, slowing build times).
        // But for release builds, it's okay. So the following script iterates through
        // all the known variants, finds those that are "release" build types, and
        // changes those properties to something dynamic.
        applicationVariants.all { variant ->
            if (variant.buildType.name == "release") {
                variant.mergedFlavor.versionCode = minutesSinceEpoch;
                variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName;
            }
        }
    }
    

정적 종속성 버전 사용

build.gradle 파일에서 종속성을 선언하는 경우 끝에 더하기 기호가 있는 버전 번호를 사용해서는 안 됩니다(예: 'com.android.tools.build:gradle:2.+'). 동적 버전 번호를 사용할 경우 예상치 못한 버전 업데이트가 발생할 수 있고 버전 차이를 확인하기가 어려울 수 있으며 Gradle의 업데이트 확인으로 인해 빌드 속도가 느려질 수 있습니다. 대신 정적/하드 코딩 버전 번호를 사용해야 합니다.

오프라인 모드 사용 설정

네트워크 연결이 느린 경우 Gradle이 종속성 해결을 위해 네트워크 리소스를 사용하려고 시도하면 빌드 시간에 영향을 미칠 수도 있습니다. 이 경우 로컬로 캐싱한 아티팩트만을 사용하고 네트워크 리소스는 사용하지 않도록 Gradle에 알릴 수 있습니다.

Android 스튜디오에서 빌드할 때 Gradle을 오프라인으로 사용하려면 다음 단계를 진행하세요.

  1. File > Settings(Mac은 Android 스튜디오 > Preferences)를 클릭하여 Preferences 창을 엽니다.
  2. 왼쪽 창에서 Build, Execution, Deployment > Gradle을 클릭합니다.
  3. Offline work 체크박스를 선택합니다.
  4. Apply 또는 OK를 클릭합니다.

명령줄에서 빌드하는 경우 --offline 옵션을 전달합니다.

라이브러리 모듈 생성

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

맞춤형 빌드 로직을 위한 작업 생성

빌드 프로필을 생성한 후에 빌드 시간의 상당 부분이 '프로젝트 구성' 단계에 사용된 것으로 나타난다면, build.gradle 스크립트를 검토하고 맞춤형 Gradle 작업에 포함할 수 있는 코드를 찾습니다. 일부 빌드 로직을 작업으로 옮겨놓으면 필요할 때만 이 작업이 실행되고, 이후 빌드에 사용하기 위해 결과를 캐싱할 수 있으며, 이 빌드 로직을 병렬로 실행할 수 있게 됩니다(병렬 프로젝트 실행을 사용 설정한 경우). 자세히 알아보려면 공식 Gradle 문서를 참조하세요.

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

WebP로 이미지 변환

WebP는 손실이 있는 압축(예: JPEG) 및 투명도(예: PNG)를 제공하지만 JPEG 또는 PNG보다 더 나은 압축을 제공할 수 있는 이미지 파일 형식입니다. 빌드 시 압축하지 않고 이미지 파일 크기를 줄이면 특히 앱에서 많은 이미지 리소스를 사용하는 경우 빌드 속도를 높일 수 있습니다. 그러나 WebP 이미지의 압축을 해제하는 동안 기기 CPU 사용량이 약간 증가할 수 있습니다. Android 스튜디오를 사용하면 WebP로 이미지를 쉽게 변환할 수 있습니다.

PNG 크런칭 사용 중지

WebP로 PNG 이미지를 변환할 수 없는 경우(또는 변환을 원치 않는 경우) 앱을 빌드할 때마다 자동 이미지 압축을 사용 중지하여 빌드 속도를 높일 수 있습니다. Android 플러그인 3.0.0 이상을 사용하는 경우 '디버그' 빌드 유형에서만 PNG 크런칭이 기본적으로 사용 중지됩니다. 다른 빌드 유형에서도 이 최적화를 사용 중지하려면 build.gradle 파일에 다음을 추가합니다.

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

    // If you're using an older version of the plugin, use the
    // following:
    //  aaptOptions {
    //      cruncherEnabled false
    //  }
    }
    

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

빌드 캐시 사용 설정

빌드 캐시는 프로젝트를 빌드할 때 Gradle용 Android 플러그인이 생성하는 특정 출력을 저장합니다(예: 언패키징된 AAR 및 pre-dexed 원격 종속성). 클린 빌드는 캐시를 사용하는 동안에 훨씬 더 빠릅니다. 그 이유는 빌드 시스템이 후속 빌드 동안 캐시된 파일을 다시 생성하는 것이 아니라 재사용할 수 있기 때문입니다.

Android 플러그인 2.3.0 이상을 사용하는 새 프로젝트에서는 기본적으로 빌드 캐시를 사용 설정합니다(명시적으로 빌드 캐시를 사용 중지하지 않는 한). 자세히 알아보려면 빌드 캐시로 클린 빌드 가속화를 참조하세요.

증분 주석 프로세서 사용

Android Gradle 플러그인 3.3.0 이상에서는 증분 주석 처리의 지원이 향상됩니다. 따라서 증분 빌드 속도를 높이려면 Android Gradle 플러그인을 업데이트하고 가능한 경우 증분 주석 프로세서만 사용해야 합니다.

참고: 이 기능은 Gradle 버전 4.10.1 이상에서만 호환됩니다. 단, Gradle 5.1은 제외됩니다(Gradle 문제 #8194 참조).

시작하려면 증분 주석 처리를 지원하는 다음과 같은 인기 있는 주석 프로세서 목록을 참조하세요. 전체 목록은 인기 주석 프로세서 지원 상태를 참조하세요. 일부 주석 프로세서에는 최적화를 위해 추가 단계가 필요할 수 있으므로 각 주석 프로세서의 설명서를 읽어보세요.

또한 앱에서 Kotlin을 사용하는 경우 kapt 1.3.30 이상을 사용해야 Kotlin 코드에 증분 주석 프로세서를 지원할 수 있습니다. 이 동작을 직접 사용 설정해야 하는지 여부를 알아보려면 공식 문서를 참조하세요.

증분 빌드를 지원하지 않는 주석 프로세서를 하나 이상 사용해야 하는 경우 주석이 증분 처리되지 않습니다. 하지만 프로젝트에서 kapt를 사용하는 경우 자바 컴파일은 계속 증분됩니다. kapt를 사용하지 않지만 자바 컴파일이 증분되기를 원하는 경우 gradle.properties 파일에 다음 플래그를 추가하는 것을 고려해 보세요. 그러면 Android Gradle 플러그인은 별도의 작업에서 모든 주석 프로세서를 실행하고 자바 컴파일 작업의 점진적 실행을 허용합니다.

    android.enableSeparateAnnotationProcessing = true
    

빌드 프로파일링

크기가 큰 프로젝트나 많은 맞춤형 빌드 로직이 구현된 프로젝트에서는 병목 현상을 찾아내기 위해 빌드 프로세스를 더 깊이 들여다봐야 할 수 있습니다. 그렇게 하려면 Gradle이 빌드 수명 주기의 각 단계와 각 빌드 작업을 실행하는 데 얼마나 시간이 걸리는지를 프로파일링하면 됩니다. 예를 들어, Gradle이 프로젝트를 구성하는 데 너무 많은 시간을 소모한다고 빌드 프로필에 나타난다면 맞춤형 빌드 로직을 구성 단계에서 삭제하라고 제안할 수 있습니다. 또한 mergeDevDebugResources 작업이 빌드 시간의 상당 부분을 차지하는 경우 WebP로 이미지를 변환하거나 PNG 크런칭을 사용 중지하라고 표시할 수도 있습니다.

빌드 속도를 개선하기 위해 프로파일링을 사용하는 과정에는 일반적으로 프로파일링을 사용 설정하여 빌드를 실행하는 작업, 빌드 구성을 조정하는 작업, 변경 결과를 관찰하기 위한 프로파일링 작업이 포함됩니다.

빌드 프로필을 생성하고 보려면 다음 단계를 따르세요.

  1. Android 스튜디오에서 프로젝트를 열고 View > Tool Windows > Terminal을 선택하여 프로젝트 루트에서 명령줄을 엽니다.
  2. 다음의 명령을 입력하여 클린 빌드를 시행합니다. 빌드를 프로파일링할 때 Gradle은 작업에 관한 입력(예: 소스 코드)이 변하지 않을 때는 이 작업을 건너뛰므로, 개발자는 프로파일링되는 각 빌드 간에 클린 빌드를 실행해야 합니다. 따라서 입력 변화가 없는 두 번째 빌드는 작업이 재실행되지 않기 때문에 항상 더 빠르게 실행됩니다. 그래서 빌드 간에 clean 작업을 실행하면 전체 빌드 프로세스의 프로파일링이 보장됩니다.
        // On Mac or Linux, run the Gradle wrapper using "./gradlew".
        gradlew clean
        
  3. 제품 버전(예: 'dev' 버전) 중 하나의 디버그 빌드를 다음 플래그와 함께 실행합니다.
        gradlew --profile --offline --rerun-tasks assembleFlavorDebug
        
    • --profile: 프로파일링을 사용 설정합니다.
    • --offline: Gradle이 온라인 종속성을 가져오는 것을 중지합니다. 이렇게 하면 종속성 업데이트를 시도하는 Gradle에 의해 발생하는 모든 지연이 프로파일링 데이터와 충돌하지 않습니다. Gradle이 이미 종속성을 다운로드하고 캐싱했는지 확인하려면 개발자가 한 번이라도 프로젝트를 이미 빌드했어야 합니다.
    • --rerun-tasks: 모든 작업을 재실행하고 모든 작업 최적화를 무시하도록 Gradle을 강제합니다.
  4. 그림 1. 프로필 보고서의 위치를 나타내는 프로젝트 보기

    빌드가 완료되면 Project 창을 사용하여 project-root/build/reports/profile/ 디렉터리로 이동합니다 (그림 1 참조).

  5. profile-timestamp.html 파일을 마우스 오른쪽 버튼으로 클릭하고 Open in Browser > Default를 선택합니다. 보고서의 모양은 그림 2와 비슷합니다. 보고서의 각 탭을 검사하여 빌드를 알아보세요. 예를 들어 Task Execution 탭은 Gradle이 각 빌드 작업을 실행하는 데 걸리는 시간을 나타냅니다.

    그림 2. 브라우저에서 보고서 보기

  6. 선택사항: 프로젝트나 빌드 구성을 변경하기 전에 3단계의 명령을 반복하되 --rerun-tasks 플래그는 생략합니다. 시간 절약을 위해 Gradle은 입력이 변하지 않은 작업을 재실행하지 않으므로 (이러한 작업은 그림 3의 보고서에서 UP-TO-DATE 탭에 UP-TO-DATE로 표시), 실행해서는 안 될 때 어떤 작업이 실행 중인지 확인할 수 있습니다. 예를 들어, :app:processDevUniversalDebugManifestUP-TO-DATE로 표시되지 않은 경우 각 빌드에서 빌드 구성이 manifest를 동적으로 업데이트하도록 제안할 수도 있습니다. 그러나 일부 작업은 각 빌드 중에 실행되어야 합니다(예: :app:checkDevDebugManifest).

    그림 3. 작업 실행 결과 보기

이제 빌드 프로필 보고서가 있으므로, 이 보고서의 각 탭에 있는 정보를 검사하여 최적화를 시작할 수 있습니다. 프로젝트와 워크스테이션 간에 이점 차이가 있을 수 있으므로 일부 빌드 설정에서는 실험이 필요합니다. 예를 들어, 대량의 코드베이스가 있는 프로젝트는 ProGuard를 사용하여 미사용 코드를 삭제하고 APK 크기를 축소하는 이점이 있을 수 있습니다. 그러나 더 작은 프로젝트는 ProGuard를 모두 사용 중지할 때 더 이점이 있을 수 있습니다. 또한 Gradle 힙 크기를 늘리면( org.gradle.jvmargs 사용) 메모리 용량이 낮은 시스템에서 성능에 악영향을 미칠 수도 있습니다.

빌드 구성을 변경한 후 위의 단계를 반복하고 새 빌드 프로필을 생성하여 변경 결과를 관찰합니다. 예를 들어, 그림 4는 이 페이지에 설명된 기본적인 최적화를 동일한 샘플 앱에 적용한 후의 보고서를 보여줍니다.

그림 4. 빌드 속도를 최적화한 후의 새 보고서 보기

팁: 더 강력한 프로파일링 도구가 필요하면 Gradle의 오픈소스 프로파일러를 사용해 보세요.