Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

ビルド速度の最適化

ビルドに時間がかかると開発プロセスも滞ります。このページでは、ビルドを遅延させるボトルネックを解消するための方法について説明します。

ビルド速度を改善するための一般的なプロセスは次のとおりです。

  1. ビルド設定を最適化する - 数ステップだけで、大半の Android Studio プロジェクトですぐに効果が現れます。
  2. ビルドのプロファイリングを行う - プロジェクトやワークステーションに固有の問題である可能性がある複雑なボトルネックを特定し、診断します。

アプリを開発する際は、可能な限り Android 7.0(API レベル 24)以降を搭載しているデバイスにデプロイしてください。新しいバージョンの Android プラットフォームには、Android ランタイム(ART)マルチ DEX ファイルのネイティブ サポートなど、更新内容をアプリにプッシュするための便利な仕組みが実装されています。

注: このページに記載されている最適化を一切行わなくても、初回のクリーンビルドに続いてクリーンビルドまたは増分ビルドを行う際は、ビルド速度が大幅に向上する場合があります。これは、他の JVM プロセスと同様、パフォーマンスを改善するための「ウォーミング アップ」タイムが Gradle デーモンに設けられているためです。

ビルド設定を最適化する

以下のおすすめの手法を採用することで、Android Studio プロジェクトのビルド速度を向上させることができます。

常に最新のツールを使用する

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 Studio に認識させるうえで重要なステップとなります。ただし、大規模なプロジェクトでは、この処理に時間がかかることがあります。プロジェクト内で複数のビルド バリアントを使用している場合は、現在選択しているバリアントだけをプロジェクトと同期することで、この処理を最適化できます。

この最適化を有効にするには、Android Studio 3.3 以降と、Android Gradle Plugin 3.3.0 以降を使用する必要があります。この最適化機能は、すべてのプロジェクトでデフォルトで有効になっています。

手動でこの最適化を有効にするには、[File] > [Settings] > [Experimental] > [Gradle](Mac の場合は [Android Studio] > [Preferences] > [Experimental] > [Gradle])をクリックして、[Only sync the active variant] チェックボックスをオンにします。

注: この最適化機能は、Java 言語や C++ 言語を使用したプロジェクトを完全にサポートしており、Kotlin にも一部対応しています。Kotlin コンテンツを含むプロジェクトでこの最適化を有効にすると、内部で完全なバリアントを使用するように、Gradle 同期がフォールバックします。

不要なリソースのコンパイルを回避する

テスト対象外のリソース(追加の言語ローカライズ リソースや画面密度リソースなど)のコンパイルやパッケージ化は避けてください。そのためには、次の例のように、「dev」フレーバーに対して、1 つの言語リソースと 1 つの画面密度だけを指定します。

    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 Kit を無効にする必要があります。

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)
            }
    

Java

    // 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 を使用しつつ、増分ビルドの速度を上げるには、ビルドのたびに変わる固有のビルド ID を Crashlytics が参照してアプリリソースを更新するのを防ぐ必要があります。このビルド ID はマニフェストによって参照されるリソース ファイルに格納されるため、ビルド ID の自動生成を無効にした場合、デバッグビルドで Crashlytics とともに Apply Changes を使用することもできます。

Crashlytics がビルド ID を自動的に更新するのを防ぐには、以下の内容を build.gradle ファイルに追加します。

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

Crashlytics を使用した場合のビルド最適化方法については、公式ドキュメントをご覧ください。

デバッグビルドに静的ビルド設定値を使用する

デバッグビルド タイプのマニフェスト ファイルやリソース ファイルに含まれるプロパティには、必ず静的な値またはハードコーディングした値を使用してください。

たとえば、動的なバージョン コードや、バージョン名、リソース、その他マニフェスト ファイルを変更するビルドロジックを使用していると、実際にはホットスワップで十分な変更の場合でも、変更を実行するたびに 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 Studio を使用してビルドする際に Gradle をオフラインで使用するには:

  1. [File] > [Settings](Mac の場合は [Android Studio] > [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 Studio を使用すると、簡単に画像を WebP に変換できます。

PNG 自動最適化を無効にする

PNG 画像を WebP に変換できない場合(あるいは、変換したくない場合)、アプリをビルドするたびに自動で画像圧縮を行わないようにすれば、ビルドの速度を上げることができます。Android プラグイン 3.0.0 以降を使用している場合、「debug」ビルドタイプに対してのみ、PNG 自動最適化はデフォルトで無効になっています。他のビルドタイプに対しても 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 に設定する必要があります。

ビルド キャッシュを有効にする

ビルド キャッシュとは、Android Plugin for Gradle がプロジェクトをビルドするときに生成する特定の出力(非パッケージ化 AAR、事前 DEX 変換済みのリモート依存関係など)を保存する機能です。キャッシュされたファイルは、ビルドシステムが以降のビルドを行う際に再利用できるため、再生成の手間が省け、クリーンビルドの時間が大幅に短縮されます。

Android プラグイン 2.3.0 以降を使用している新しいプロジェクトの場合、明示的にビルド キャッシュを無効にしていない限り、デフォルトでビルド キャッシュが有効になっています。詳細については、ビルド キャッシュを使用してクリーンビルドを高速化するをご覧ください。

増分アノテーション プロセッサを使用する

Android Gradle プラグイン 3.3.0 以降では、増分アノテーション プロセッサのサポートが改善されています。そのため、増分ビルド速度を向上させるには、Android Gradle プラグインをアップデートして、可能な限り増分アノテーション プロセッサだけを使用するように設定してください。

注: この機能は、Gradle 5.1 を除く Gradle バージョン 4.10.1 以降で利用できます(Gradle の問題 #8194 を参照)。

まず、増分アノテーション プロセッサをサポートする一般的なアノテーション プロセッサを示した下記のリストをご覧ください。より詳細なリストについては、一般的なアノテーション プロセッサのサポートの状態をご覧ください。アノテーション プロセッサによっては、最適化を有効にするために追加手順が必要となる場合があります。各アノテーション プロセッサのドキュメントをご確認ください。

また、アプリで Kotlin を使用する場合、kapt 1.3.30 以降を使用して、Kotlin コードで増分アノテーション プロセッサをサポートする必要があります。この動作を手動で有効にする必要があるかどうかについては、公式ドキュメントをご覧ください。

増分ビルドをサポートしていないアノテーション プロセッサを使用している場合、アノテーション処理は増分になりません。ただし、プロジェクトで kapt を使用している場合、Java コンパイルは引き続き増分になります。kapt を使用せず、Java コンパイルを増分にしたい場合は、gradle.properties ファイルに次のフラグを含めることを検討してください。この場合、Android Gradle プラグインは、すべてのアノテーション プロセッサを別のタスク内で実行し、Java コンパイル タスクを段階的に実行できるようにします。

    android.enableSeparateAnnotationProcessing = true
    

ビルドのプロファイリングを行う

大規模なプロジェクトや、カスタム ビルドロジックを多数実装したプロジェクトの場合、ビルドプロセスを詳細に調査して、ボトルネックを特定する必要があります。そのためには、ビルド ライフサイクルの各フェーズや各ビルドタスクを Gradle が実行するのに要する時間をプロファイリングします。ビルド プロファイルによって、たとえば、Gradle がプロジェクトを設定する際に長い時間を要していると判明した場合は、カスタム ビルドロジックを設定フェーズから移行するなどの対処が必要になります。また、ビルド時間の大半を mergeDevDebugResources タスクが占めている場合は、画像を WebP に変換するか、PNG 自動最適化を無効にする必要があります。

一般的に、プロファイリングによってビルドを高速化するには、プロファイリングを有効にしてビルドを実行し、ビルド設定を微調整して、何度かプロファイリングを行い、変更による効果を検証します。

ビルド プロファイルを生成して表示するには:

  1. Android Studio 内で該当のプロジェクトを開いて、[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 が依存関係をダウンロードしてキャッシュした状態にするには、プロジェクトを 1 回ビルドしておきます。
    • --rerun-tasks: Gradle がすべてのタスクを再実行するように強制し、タスクの最適化は行いません。
  4. 図 1: プロファイル レポートの場所を示す [Project] ビュー

    ビルドが完了したら、[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 のように、レポートの [Task Execution] タブに UP-TO-DATE と表示されているタスク)の実行を省いてビルド時間の短縮を図るため、不必要に実行されているタスクがないか確認できます。たとえば、:app:processDevUniversalDebugManifestUP-TO-DATE のマークがない場合、現在のビルド設定では、このマニフェスト ファイルはビルドのたびに動的に更新されます。ただし、:app:checkDevDebugManifest などの一部のタスクは、ビルドのたびに実行する必要があります。

    図 3: タスクの実行結果を表示する

ビルド プロファイル レポートが手に入ったため、レポートの各タブの情報を参考にして、最適化できる部分がないか探すことができます。ビルド設定によっては、プロジェクトやワークステーションによって効果が異なるため、さまざまな設定を試す必要があります。たとえば、大規模なコードベースを含むプロジェクトの場合、ProGuard を使用して、不要なコードを除去し、APK サイズを縮小すると効果が得られます。他方、小規模なプロジェクトの場合は、ProGuard を無効にしたほうが効果が高いこともあります。また、メモリの少ないマシンでは、org.gradle.jvmargs を使用して Gradle のヒープサイズを増やすと逆効果になることがあります。

ビルド設定に変更を加えたら、上記の手順を繰り返して、新しいビルド プロファイルを生成し、変更の効果を確認します。このページで説明した基本的な最適化手法を、同じサンプルアプリに一部適用したレポートの例を図 4 に示します。

図 4: ビルド速度を最適化した後の新しいレポートを表示する

ヒント: 強力なプロファイリング ツールが必要な場合は、Gradle のオープンソース プロファイラを使用することをおすすめします。