ビルド速度を最適化する

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

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

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

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

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

ビルド構成を最適化する

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

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

Android ツールには、アップデートによりほぼ毎回、ビルドの最適化や新しい機能が追加されます。このページに記載のさまざまな手法は、最新版を使用していることを前提としています。最新の最適化機能を活用するには、以下のツールの最新版を使用してください。

kapt の代わりに KSP を使用する

Kotlin Annotation Processing Tool(kapt)は、Kotlin Symbol Processor(KSP)よりも大幅に低速です。アノテーション付きの Kotlin ソースを作成し、KSP をサポートするアノテーションを処理するツール(Room など)を使用している場合は、KSP に移行することをおすすめします。

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

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

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 は宣言された順序でリポジトリを検索するため、最初にリストされたリポジトリにほとんどのプラグインが含まれている場合は、ビルドのパフォーマンスが向上します。そのため、gradlePluginPortal() エントリをテストする場合は、そのエントリを settings.gradle ファイルのリポジトリ ブロックの最後に置いてみてください。ほとんどの場合、これによって冗長なプラグイン検索の回数が最小限に抑えられ、ビルド速度が向上します。

Gradle で複数のリポジトリを操作する方法について詳しくは、Gradle ドキュメントの Declaring multiple repositories をご覧ください。

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

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

動的なバージョン コード、バージョン名、リソースや、マニフェスト ファイルを変更するその他のビルドロジックを使用している場合、実際にはホットスワップで十分な変更であっても、変更するたびにアプリのフルビルドが必要になります。このような動的プロパティがビルド構成に必要な場合は、それらのプロパティをリリース用のビルド バリアントに分離して、デバッグビルドでは値を静的に保ちます。次に例を示します。

  ...
  // 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 Studio を使用すると、簡単に画像を WebP に変換できます。

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

PNG 画像を WebP に変換しない場合でも、アプリをビルドするたびに自動で画像圧縮を行わないようにすれば、ビルド時間を短縮できます。

Android Gradle プラグイン 3.0.0 以降を使用している場合、「debug」ビルドタイプに対して、PNG 自動最適化はデフォルトで無効になっています。他のビルドタイプに対して 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 のヒープサイズを増やす

ビルドが遅く、特に、Build Analyzer の結果でガベージ コレクションがビルド時間の 15% 以上を占める場合は、Java 仮想マシン(JVM)のヒープサイズを増やす必要があります。gradle.properties ファイルで、次の例に示すように上限を 4、6、または 8 GB に設定します。

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 Studio Bumblebee 以降、非推移的な R クラスは、新しいプロジェクトについてはデフォルトで有効になっています。以前のバージョンの Android Studio で作成されたプロジェクトの場合は、[Refactor] > [Migrate to Non-Transitive R Classes] に移動して、非推移的な R クラスが使用されるようにプロジェクトを更新します。

アプリのリソースや R クラスについて詳しくは、アプリリソースの概要をご覧ください。

非定数の R クラスを使用する

Java コンパイルのインクリメンタリティを改善し、より正確なリソース圧縮を可能にするには、アプリとテストで非定数の R クラス フィールドを使用します。R クラス フィールドは、ライブラリに対して常に一定ではありません。これは、APK のパッケージ化の際に、ライブラリに依存するアプリまたはテストを対象として、リソースに番号が付けられるためです。これは、Android Gradle プラグイン 8.0.0 以降ではデフォルトの動作です。

Jetifier フラグを無効にする

ほとんどのプロジェクトで AndroidX ライブラリは直接使用されるため、Jetifier フラグを削除してビルドのパフォーマンスを改善できます。Jetifier フラグを削除するには、gradle.properties ファイルで android.enableJetifier=false を設定します。

Build Analyzer を使用して、このフラグを安全に削除できるかどうかをチェックできます。また、このフラグを削除することで、プロジェクトのビルド パフォーマンスが向上するかどうかや、保守されていない Android サポート ライブラリから移行できるかどうかも確認できます。Build Analyzer について詳しくは、ビルドのパフォーマンスのトラブルシューティングをご覧ください。

構成キャッシュを使用する

構成キャッシュにより、Gradle はビルドタスクグラフに関する情報を記録して、後続のビルドで再利用できます。そのため、Gradle はビルド全体を再構成する必要がなくなります。

構成キャッシュを有効にする手順は次のとおりです。

  1. すべてのプロジェクト プラグインに互換性があることを確認します。

    Build Analyzer を使用して、プロジェクトと構成キャッシュに互換性があるかどうかを確認してください。Build Analyzer では一連のテストビルドが実行され、プロジェクトでこの機能を有効にできるかどうかが判断されます。サポートされているプラグインのリストについては、問題 #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 トラッキングが導入されました。File.exists()File.isDirectory()File.list() などの呼び出しが Gradle によって記録され、構成入力ファイルがトラッキングされます。

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 トラッキングを一時的に無効にする方法については、 ビルド構成の入力トラッキングの改善をご覧ください。