Androidアプリがいきなりビルド出来なくなった時


何も変えていないのにビルド出来ない…

本記事では、僕がAndroidアプリのコードを何も変えていないのに突如ビルド出来なくなった時の話をします。
これはAndroidのパッケージマネージング事情によるところが多いっぽいので、それを主に解説します。
最初からビルドできてない場合は対象外です。

依存関係を疑う

最初に疑うべきは、ライブラリのバージョン指定。
以下のような記述をbuild.gradle内に見付けたら、真っ先に疑う。

android/build.gradle
    compile 'com.google.android.gms:play-services-gcm:+'

まずいのは末尾の:+の部分。+は「その中で一番新しい」を表し、つまり:+は「最新版」という意味。
ライブラリのインストールはアプリをビルドするたびに行われるので、コードを変えていなくてもインストールされるライブラリはバージョンアップに従って変わっていく。

「何も変えてないのにビルドできないよ〜」はまずこれが原因。(経験数2)

Androidサポート系ライブラリのバージョンを一斉指定する

以下のように書くと、com.android.support系ライブラリのバージョンを指定できる。
com.android.support系ライブラリは、buildToolsVersionに対応したバージョン付けが揃って行われているので、このような書き方ですべて合わせられる。
multidexをexcludeしているのは、これだけバージョン付けルールが別だから。

android/build.gradle
subprojects {
   project.configurations.all {
       resolutionStrategy.eachDependency { details ->
           if (details.requested.group == 'com.android.support'
             && !details.requested.name.contains('multidex') ) {
               details.useVersion "26.0.2"
           }
       }
   }
    afterEvaluate {
        project -> if (project.hasProperty("android")) {
            android {
                compileSdkVersion 26
                buildToolsVersion '26.0.2'
            }
        }
    }
}

ライブラリ内も疑う

Androidでは、ライブラリの依存関係の管理が甘いため(実装がではなく人に対して)、
ライブラリ中の依存ライブラリのバージョン指定はけっこう適当に書いてもよく、このように:+と書いてあるものも多い。

node_modules/react-native-admob
dependencies {
    compile 'com.facebook.react:react-native:+'
    compile 'com.google.android.gms:play-services-ads:+'
}

なぜかと言うと、多分だが、重要ライブラリのバージョンは最新に保つ方がセキュリティ好ましいことに加え、Androidのこうした依存ライブラリのバージョン指定は開発者がbuild.gradle側で上書き対処できるからだと思われる。
Rubyでのbundlerやnodeでのyarnとはまた違った文化というか…
そのへんを理解せず他のパケマネ感覚で扱っていると、なかなかハマりそう。ハマった。

ライブラリ内の依存ライブラリバージョン指定を上書きする

ライブラリの依存ライブラリのバージョン指定は、このような書き方で上書きできる。(ややこしい)
まず、build.gradle内でcompile(implementation)しつつ、そのバージョン指定の優先度を上げる書き方。

android/app/build.gradle
    compile 'com.google.android.gms:play-services-ads:16.0.0', {
        force = true;
    }

もしくは、個別にexcludeもできる。

android/app/build.gradle
    compile project(':react-native-admob'), {
        exclude group: "com.google.android.gms"
    }
    compile 'com.google.android.gms:play-services-ads:16.0.0'

以下の記述も見ておいたほうが良いらしいが、
多分こういう設定があるのはgooglePlayServicesみたいな一部の特別なライブラリだけかな…

android/build.gradle

buildscript {
    ...
    ext {
        googlePlayServicesVersion = "16.0.0"
    }
    ...
}

エラー別解説

AAPT: No resource identifier found for attribute 'appComponentFactory' in package 'android'

想定外に新しいライブラリが入ると、アプリやそのAPIレベルに存在しないサービスを求めてくる場合がある。
その結果これが出た場合、ここだけの対症療法は可能だが、どうせ他の部分でエラーが出るので大本の原因を考えること。
原因ライブラリのバージョンを下げるか、APIレベルを引き上げるか、依存ライブラリのバージョンを上げるといった対策が考えられる。

Unable to merge dex

このエラーは、一度xmlに依存関係などをまとめて記載する時に、矛盾があって生じる場合が多い。
その矛盾にも色々あるが、よく起きるのが「ライブラリのバージョン指定が複数あり、まとめられない」という場合。
gradleのバージョンによっては何がコンフリクトしているか表示してくれるようだ。

AndroidManifest.xmlって何(今更)

Androidアプリの重要情報はAndroidManifest.xmlにほぼ全て書き込まれる。
ビルドエラーの発生源もだいたいコイツ。
情報量が膨大なので、大半の情報はGradleで機械的に追記される。その設定ファイルがbuild.gradle

ひな形はandroid/app/src/main/AndroidManifest.xml等の場所にあって、これにbuild.gradle設定による追記が行われて完成する。要するにAndroidManifest.xmlの最終型を調整する方法は「ひな形の編集」と「build.gradleの編集」の2つある。

そしてbuild.gradleは、複数のファイルとプロセスに分かれている。ちょっと混乱するよね…

ビルドエラーが発生した時、ひな形の調整で対応できる事もあるが、基本的にはbuild.gradleの見直しをおすすめする。