読者です 読者をやめる 読者になる 読者になる

ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Android Studio 0.4.0 への移行

Android アプリ Gradle プラクティス

Android Studio 0.4.0 がリリースされていますね! わー、ぱちぱち。

Android Studio 0.4.0 への移行は、単にバージョンを上げるだけという感じではなかったので、苦労した箇所などを簡単にメモしておきます。

Gradle と Android Gradle プラグインのバージョンアップ

Android 0.4.0 では、Gradle 1.9、Android Gradle プラグイン 0.7 が必要となっています。 それぞれバージョンアップを行いましょう。 Gradle は Gradle ラッパーとしてプロジェクト内に持っていることが多いと思うので、gradle/wrapper/gradle-wrapper.properties を次のように書きかえればいいはずです。 *1

-distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-bin.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip

Android Gradle プラグインのバージョンアップは、build.gradle の記述を次のような感じで変更すればよいですね。 (0.7.1 がリリースされており、後述する packagingOptions の exclude は 0.7.1 から使えるようになったものなので、バージョンは 0.7.1 以降を指定しています。)

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
-        classpath 'com.android.tools.build:gradle:0.6.+'
+        classpath 'com.android.tools.build:gradle:0.7.1+'

build.gradle の記述変更

Android Gradle プラグイン 0.7 では、大きめの変更が入っていますので、そのままビルドしようとしても失敗する可能性があります。 とりあえずビルドしてみて、エラーが出たら必要に応じて変更を加えればよいでしょう。

以下に、私が行った変更を書いておきます。

buildConfig から buildConfigField に変更

Android Gradle プラグイン 0.6.x のときには、BuildConfig クラスのフィールドを定義するために、次のように buildConfig メソッドを使用することができました。

android {
    buildTypes {
        release {
            buildConfig "public final static boolean XXX_TEST_MODE = false;"
        }

0.7.0 以降ではこれは使用できなくなっており、代わりに buildConfigField メソッドを使用します。 buildConfigField は 3 引数をとるメソッドで、前から順にフィールドの型、フィールドの識別子、フィールドの値を設定します。

android {
    buildTypes {
        release {
            buildConfigField "boolean", "XXX_TEST_MODE", "false"
        }
私がはまったところ

私はもともと次のようなビルドスクリプトを書いていました。

android {
    buildTypes {
        debug {
            buildConfig "public final static boolean XXX_TEST_MODE = false;"
        }
        testtarget.initWith(buildTypes.debug)
        testtarget {
            buildConfig "public final static boolean XXX_TEST_MODE = true;"
        }

で、Android Gradle プラグイン 0.6.x の頃までは問題なくビルドできていたのですが、0.7.1 に移行して次のように書きかえるとビルドできなくなりました。

android {
    buildTypes {
        debug {
            buildConfigField "boolean", "XXX_TEST_MODE", "false"
        }
        testtarget.initWith(buildTypes.debug)
        testtarget {
            buildConfigField "boolean", "XXX_TEST_MODE", "true"
        }

ビルドに失敗する際のエラー出力は次のようなもの。

.../BuildConfig.java:14: エラー: 変数 XXX_TEST_MODEはすでにクラス BuildConfigで定義されています
  public static final boolean XXX_TEST_MODE = true;
                              ^

debug ビルドタイプでの buildConfigField の設定がそのまま testtarget に受け継がれて、testtarget ビルドタイプでも同名のフィールドを定義しているので問題が起こる、という感じでした。 で、どうすればいいのか結構悩んだのですが、単に debug ビルドタイプで buildConfigField メソッドを呼び出す前に initWith して testtarget ビルドタイプの初期化をしてやれば良いのでした。

android {
    buildTypes {
        // `initWith` した後に `debug` ビルドタイプで `buildConfigField` 呼び出しをすればよい
        testtarget.initWith(buildTypes.debug)
        debug {
            buildConfigField "boolean", "XXX_TEST_MODE", "false"
        }
        testtarget {
            buildConfigField "boolean", "XXX_TEST_MODE", "true"
        }

jar ファイルに含まれるリソースファイルがだぶる問題

ビルドを実行すると次のようなエラーが発生するかもしれません。 これは、libs ディレクトリの JAR ファイルを展開したときに、同じパスのリソースファイルが複数の JAR ファイルに存在すると発生するエラーのようです。

Error: duplicate files during packaging of APK ...\build\apk\App-testtarget-unaligned.apk
        Path in archive: META-INF/NOTICE.txt
        Origin 1: ...\App\libs\xxxx-4.0.0.jar
        Origin 2: ...\App\libs\yyyy-4.0.0.jar
You can ignore those files in your build.gradle:
        android {
          packagingOptions {
            exclude 'META-INF/NOTICE.txt'
          }
        }

どちらの JAR に含まれるリソースファイルも必要でない場合 (例えばパッケージに含めなくて良い NOTICE.txt など) は、そのリソースファイルをパッケージング時に除外するようにすることでこのエラーを回避できます。 エラーメッセージにも書かれていますが、次のようなコードを書けばよいです。

android {
    packagingOptions {
        exclude 'META-INF/NOTICE.txt'
    }
}

これは Android Gradle プラグインの 0.7.1 で導入された機能です。

lint でエラーになってもビルドに失敗しないようにする

おそらく Android Gradle プラグインの 0.7.0 からだと思うのですが、build タスクの一部として lint タスクも実行されるようになりました。 で、デフォルトでは lint タスクでエラーが発生するとビルドが止まるようになっています。 理想的には lint でエラーが発生しないようにするのが良いと思いますが、実際には問題ない箇所でも lint のエラーになることがあった *2 ので、とりあえず lint でエラーが出てもビルドを継続するようにしました。

android {
    lintOptions {
        abortOnError false
    }

参考資料

Android Studio 0.4.0 移行とは関係ないけど Windows で Gradle を使うときの文字コードではまった

Android Studio 0.4 移行とは関係ないのですが、Windows で Gradle を使っていると、行コメントの次の行までコメント扱いされる、ということがあってはまってしまいました。

Groovy 処理系が期待するファイルの文字エンコーディング (Windows なので CP932) と、実際のファイルの文字エンコーディング (UTF-8) が違っているために起こる問題のようです。 この問題については下記の記事が参考になりました。

結局のところ、Gradle ラッパーを使っているのであれば、gradlew.bat を次のように変更したら良さそうですね。 *3

diff --git a/gradlew.bat b/gradlew.bat
index 8a0b282..c56f988 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -10,6 +10,7 @@ if "%OS%"=="Windows_NT" setlocal

 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 set DEFAULT_JVM_OPTS=
+set JAVA_OPTS="-Dgroovy.source.encoding=UTF-8" "-Dfile.encoding=UTF-8"

 set DIRNAME=%~dp0
 if "%DIRNAME%" == "" set DIRNAME=.

コンソール出力のエンコーディングも変化してしまうことに気付いた (追記)

-Dfile.encoding によるデフォルトファイルエンコーディングの変更を行うと、コンソール出力のエンコーディングも変化してしまうことに気付きました。 いまのところビルドスクリプトのエンコーディングを指定するいい方法はなさそうですね。

次の記事に、Gradle に変更を加えてビルドスクリプトのエンコーディングを指定できるようにすることについて書きました。

*1:私は Gradle ラッパーの更新時には毎回 jar なども生成し直してます。 念のため。

*2:具体的には、API level を確認したうえで特定 API level 以降でしか使用できないメソッドを呼び出しているような場合。

*3:"-Dgroovy.source.encoding=UTF-8" は必要かどうかわかんないですが。