ビルド速度を最適化する

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

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

  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 の自動生成を無効にした場合、デバッグビルド用に Apply Changes を Crashlytics とともに使用することもできるようになります。

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 が更新をチェックするようになるため、ビルド時間が長くなります。代わりに、静的な値またはハードコーディングした値を使用してください。

ライブラリ モジュールを作成する

アプリ内で、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 Gradle プラグイン 3.3.0 以上で、増分アノテーション処理のサポートが改善されています。そのため、増分ビルド速度を向上させるには、Android Gradle プラグインを更新して、可能な限り増分アノテーション プロセッサだけを使用してください。

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

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

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

増分ビルドをサポートしていないアノテーション プロセッサを使用している場合、アノテーション処理は増分になりません。ただし、プロジェクトで kapt を使用している場合、Java コンパイルは引き続き増分になります。

増分アノテーション プロセッサのサポート

プロジェクト名アノテーション プロセッサのクラス名サポート対象
DataBindingandroid.databinding.annotationprocessor.ProcessDataBindingAGP 3.5
Roomandroidx.room.RoomProcessor2.3.0-alpha02

2.20: room.incremental オプションを使用します
ButterKnifebutterknife.compiler.ButterKnifeProcessor10.2.0
Glidecom.bumptech.glide.annotation.compiler.GlideAnnotationProcessor4.9.0
Daggerdagger.internal.codegen.ComponentProcessor2.18
Lifecycleandroidx.lifecycle.LifecycleProcessor2.2.0-alpha02
AutoServicecom.google.auto.service.processor.AutoServiceProcessor1.0-rc7
Daggerdagger.android.processor.AndroidProcessor2.18
Realmio.realm.processor.RealmProcessor5.11.0
Lomboklombok.launch.AnnotationProcessorHider$AnnotationProcessor1.16.22
Lomboklombok.launch.AnnotationProcessorHider$ClaimingProcessor1.16.22

JVM ガベージ コレクタを構成する

ビルドのパフォーマンスを改善するには、Gradle で使用される最適な JVM ガベージ コレクタを構成します。JDK 8 ではデフォルトで並列ガベージ コレクタを使用するように構成されていますが、JDK 9 以降では G1 ガベージ コレクタを使用するように構成されています。

ビルドのパフォーマンスを改善するには、並列ガベージ コレクタを使用して Gradle ビルドをテストすることをおすすめします。gradle.properties で次のように設定します。

org.gradle.jvmargs=-XX:+UseParallelGC

このフィールドにすでに他のオプションが設定されている場合は、新しいオプションを追加します。

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

さまざまな構成でビルド速度を測定するには、ビルドのプロファイリングを行うをご覧ください。