この記事は
私がAndroid Dagashiから一つだけ記事をピックアップして読み、理解し、そして私のような初心者のAndroiderもしくはモバイルアプリ開発者が読むと仮定して、「誰でも理解できること」を目的として内容をまとめます!
今回は第二回です!
よろしくお願いします!!
今週のピックアップ記事
今週は、2022-03-13の記事の中からピックアップして読むことにします。
そして今回のピックアップ記事は。。。
10 ideas to improve your Gradle build times [Part III]
です!
10 ideas to improve your Gradle build times [Part III]
和訳:Gradleのビルド時間を改善する10個のアイデア
Gradleとは
Androidを始めたばかりの人にとってはまだ聴き馴染みのない言葉かもしれませんので、簡単に説明させて頂きます。
読み方: グレイドル
Javaのビルドシステムのことです。
こちら側でJavaのコンパイルとかをしなくても勝手によしなにやってくれてjarファイルを作ってくれます。
jarファイルは複数のクラスファイルが統合してアプリケーションとなったものです。
Androidの場合だとjarファイルの代わりにapkというパッケージが作成されます。
1. 開発ツールのアップグレード
開発ツールを常に最新に保つことで、最新の性能向上が得られます。
次のようなさまざまな開発ツールの最新バージョンを使用していることを確認してください。
- Android Studio
- Android StudioのKotlinプラグイン
- Android SDK ツール
- Gradle
- JVM
2. api()とimplementation()の比較
例えば、gradleプロジェクトにA, B, Cの3つのモジュールがあったとします。
そしてそれぞれのbuild.gradleにimplementation()
を使って以下のような設定をしたとします。
Aのbuild.gradle
dependencies {
implementation(":B:")
}
Bのbuild.gradle
dependencies {
implementation(":C")
}
この場合、B→Cの依存はある(BからCのコードは見える)が、A→Cの依存はしてない(AからCのコードは見えない)。
この時Cを変更した場合、BはCに依存しているため再コンパイルが必要となります。
一方、A→Cの依存はないため、Aの再コンパイルはされません。
今度はgradleファイルをapi()
を使って以下のように変更してみます。
Aのbuild.gradle
dependencies {
implementation(":B")
}
Bのbuild.gradle
dependencies {
api(":C")
}
こうなった場合、Cへの依存は上位モジュールに対しても伝播します。
すなわち、AはCに依存してしまうことになります。
ということは、Cの変更を行うとAの再コンパイルも発生するということになります。
この事実を踏まえて、AがCを必要とするならapi()
を使って、AがCを必要としないならimplementation()
を使うと良いと言えます。
これによってAがCに依存しないようにすることができるので、Cが変更された時にAが再コンパイルされません。
これがビルド時間の短縮につながります。
ちなみに、api()
とかimplementation()
の対象はこの例のようなマルチモジュールにおけるモジュールでも良いし、普通のライブラリでも同様です。
3. 不要なリソースのコンパイルを避ける [Androidのみ]
まず前提知識としてAndroidの開発には「画面密度」や「言語リソース」という概念が存在します。
画面密度について
Androidはさまざまなメーカーによって端末が作られているため、どの端末でも一定の表示をできるようにするため画面密度という概念が存在します。
例) dpi
dpiという書き方がよくされるが dots per inch
で、1インチのうちにいくつドットが入るかという単位のこと。
Androidではmdpi, hdpi, xhdpi, xxhdpi, xxxhdpiが存在し、drawableフォルダでよく見かけると思います。
言語リソースについて
Androidにおいて端末の設定言語によって表示を変えるための概念。
普段string resource
をおいておく場所は res/value/strings.xml
だと思うが、ローカライゼーション(多言語対応)をする場合は例えば英語の場合はres/values-en/strings.xml
や 日本語の場合はres/values-jp/strings.xml
のようにvalues
の後ろに -国コード
としておくと、端末がいい感じに設定言語に合わせたstring resource
を使ってくれます。
これを前提として、「不要なリソースのコンパイルを避ける」とは、
パッケージング = apkファイルを作る過程で複数のclassファイルを統合しますが、その過程の中に不要なリソースがあるとソイツをコンパイルする時間はどがとても無駄で、ビルド時間に影響を及ぼします。なので、開発環境では最小限のリソースで抑えて、多様なリソースでのテスト・ビルドはCIに持たせるのが良いです。
例えば、実際のアプリの場合だと日本語以外にも英語にも対応した方がいいでしょう。
しかし、ローカルでのビルドの場合、毎回日本語と英語の両方のstring resource
をコンパイルする必要はあるでしょうか。そういう時は以下のような設定をgradleに加えます。
// build.gradleファイル
android {
defaultConfig {
if (System.getenv("CI") !="true") {
resConfigs = project.property("resConfigsOnLocalEnv")
}
}
}
// CI環境のgradle.propatiesファイル
resConfigsOnLocalEnv = "en", "jp"
// ローカルのgradle.propatiesファイル
resConfigsOnLocalEnv = "jp"
このように設定して実行すると、res/values-jp/strings.xml
の日本語のresファイルだけ残ってvalues-enやvalues-en
などはビルド時に削除される。これによって、不要なリソースはコンパイルされずにビルド時間の短縮につながる。
4. 非遷移型Rクラス【Androidのみ】
これはマルチモジュールのお話です。
Rクラスとは、resource
をあらわすクラス。
名前空間は例えばclassファイルの上にあるパッケージのやつで、大抵の場合以下のような見た目をしている。
com.username.androiduitraining.view.fragment.home.HogeFragment(クラス名)
gradle.propaties
ファイルにおいて以下の設定を行うと各モジュールにおけるRクラスの名前空間を有効にできます。
// 各モジュールにRクラスを作るという指定
android.nonTransitiveRClass=true
名前空間を有効にしない場合、全てのモジュールにあるresource
ファイルが一つのRクラスになります。
つまり全てのモジュールのRクラスが全部合体しちゃいます。
こうなると、全然関係ないモジュールが変更された時もRクラスを作り替えないと行けなったり、異なるモジュールで同じ名前のリソースファイルがあった時にどっちも参照できてしまったりします。
gradle.propaties
に上記の設定を行うだけで、各モジュールにRクラスがそれぞれ設定され、Rクラスに固有の名前空間をつけるところまでやってくれるので、それぞれのモジュールでぞれぞれのRクラスを使えるようになるのです。
5. 未使用の依存関係を削除する
build.gradle
上で未使用の依存関係が宣言されていると、ビルド時間が長くなります。
それらの未使用の依存関係を検出するために、gradle-lint-plugin
を使用することができます。
ルートのbuild.gradleファイルに以下の設定を追加するだけです。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.netflix.nebula:gradle-lint-plugin:{VERSION}'
}
}
allprojects {
apply plugin: 'nebula.lint'
gradleLint.rules = ['all-dependency']
}
ここでいう依存関係とは、api()
とかimplementation()
とかで指定されてるライブラリのこと。
gradle-lint-plugin
で使用されていないライブラリがあった場合は検知することができる。
重複している場合も出してくれるっぽい。
6. 並列実行
gradle.properties
に以下の行を追加することで、タスクが異なるプロジェクトにある限り、Gradleがタスクを並行して実行するように強制することができます。
org.gradle.parallel=true
「タスクを並行して実行する」というのがビルド時間短縮の肝です。
この文章の「タスクが異なる」とは「お互いが独立しあっているタスク」という意味です。
例えば、モジュールA, B, Cがあったとします。
そしてこの3つのモジュールの依存関係が A→C←B となっているとします。
この場合、AとBは独立していると言えます。
このような場合、Cを先にビルドして、その次にA、Aが終わったらB...というふうに直列にビルドすると時間がかかってしまいます。
そこで、gradle.propaties
にorg.gradle.parallel=true
の設定をしておくと、Cが一番最初にビルドされ、その後にAとBが同時並行にビルドされます。
これによってビルド時間が短縮される。
7. デバッグビルドで静的なビルド設定値を使用する【Androidのみ】
デバッグビルドタイプのマニフェストファイルまたはリソースファイルに入るプロパティには、常に静的/ハードコードされた値を使用してください。
例えばこんな感じです。
defaultConfig {
applicationId = "com.nemo.androiduitraining"
minSdk = 26
targetSdk = 30
versionCode = 1
versionName = "1.0"
}
動的に宣言しちゃうとインスタントランができないんだと思います。ちなみに、インスタンスランとはアプリを閉じなくてもactivityを再スタートできるやーつです。デバッグビルドの場合に上記のように静的に宣言していれば、インスタントランがで可能となり、ビルド時間の短縮につながるというわけです。
8. Gradleコンフィギュレーションキャッシュを有効にする
Gradleのビルドには三段階あります。
- イニシャライゼーション(Gradleのdeamonを起動したりとか?)
- コンフィギュレーション(なんかやってくれる。何かは...分からない..。)
- エグゼキューション(Gradleコマンドを叩いて実行)
コンフィギュレーションキャッシュを有効にしておくと、build gradle
をいじってない場合、キャッシュによってコンフィギュレーションの部分がスキップされる。それによってビルド時間が短縮されるということです。
9. 未使用のバリアントを無効にする【Androidのみ】
build variant
とはビルド可能な様々なアプリのバージョンを表したものです。
実はこのbuild variant
というのは二つの要素の組み合わせなんです。
build variant
= build type
* product falvor
になっています。つまり、 build type
とproduct falvor
の組み合わせというわけです。
具体的にはこのようなコードでそれぞれを規定することができます。
buildTypes {
getByName("release") {
}
getByName("debug") {
}
}
productFlavors {
register("development") {
}
register("qa") {
}
register("production") {
}
}
これによってdevelopment release
, qa release
, production release
, development debug
,...組み合わせによって様々なbuild variant
を設定できるのです。
このうちproduction debug
, qa debug
を使わないとチームで判断したとする。
その時にこのままこれらのbuild variant
が存在し続けると邪魔になりますし。
gradleのタスクとして残るのも邪魔。
このような場合に下記のような設定を加えておくことで不要なbuild variant
の設定を削除することが可能です。
buildTypes {
if (project.property("instrumentationBuildTypeEnabled") == true) {
instrumentation {
...
}
}
}
10. PNGのクランキング(圧縮のこと)を無効にする【Androidのみ】
これはもうカスみたいなアドバイスです。
apkファイルがでかくなったとしても問題がない場合、pngなどの画像ファイルを圧縮しない設定を加えることで画像圧縮を行う時間がなくなるのでビルド時間を短縮できるというものです。
以下の設定をgradleに加えます。
android {
buildTypes {
release {
// Disables PNG crunching for the release build type.
crunchPngs false
}
}
}
ちな、例えばapkファイルがデカくてもOKな場合はデバッグビルドなどの場合です。
リリースビルドの場合だとapkファイルが小さい方がいいです。インストールにかかる時間にも影響しますし、ファイルの大きさに上限があったりするので。
しかし、デバッグビルドの場合、pngの圧縮はデフォルトで無効化されています。
つまり上記の設定をわざわざ加える必要はないのです。
つまりこのアドバイスはカスってことです。()
最後に
Gradleについてこんなに色々調べたり、人に質問したりすることが今までなかったのでめっちゃ勉強になりました。
うおお。。しかし難しかった。
もし間違っている部分やその他アドバイスなどあればコメントお願いします!!
優しく指摘していただけると嬉しいです!!