Gradle の ProductFlavors で APK を作って生活を豊かにする

小倉唯さんブログ再開おめでとうございます。

今日は、これまでの Android 開発でおきがちなビルド時の問題と、 Gradle を使ってアプリケーションのビルドをすると生活が豊かになる、ということについて書きたいと思います。

Gradle 以前のビルドツール (Ant とか Maven とか) で起こりがちな問題

ビルド時に DEBUG とか RELEASE のフラグを切り替えたいけど出来ない!!

定数クラスを作って手動で切り替える

  • 利点
    • 猿でも使い方が瞬時に分かる
  • 問題点
    • オペミスが起きやすい
    • チェックアウトしてきた直後にビルドすると何が出来あがるか分からない
      • リポジトリには DEBUG = true しかコミットしてはいけないというルールを作ることに
      • でもだれかが DEBUG = false にしたままうっかりコミットして崩壊する
public final Constants {
    // リリース時には false にして!!
    public static final boolean DEBUG = true;
}

Java にはマクロが無いのがいけないんだ

  • C や C++ 開発からやって来た人たちが陥りがち
#ifdef DEBUG
setApiEndpoint("http://api.dev.example.com/");
#else
setApiEndpoint("https://api.example.com/");
#endif
ant debug -DDEBUG
  • こういうことがしたいんだ!! (実際には出来ない)
  • m4 を使い始める人とか出てくる
    • IDE の三角ボタンが押せなくなる

スクリプトで (sed とかを使って) ソースコードを書き換える

  • 利点
    • 自動化されてる
    • CI する時に便利
  • 問題点
    • ビルドすると手元の状態が書き変わってしまう
      • だれかがうっかり書き換えたコードをコミットしてしまう危険性
    • 特定のツールに依存したビルドツールが出来る
    • 正規表現のミスでソースコードが壊れてしまう
    • リリースの自動化には有用だけど開発時には使えない
./build_myproject.sh release
#!/bin/sh
...
sed -i "s/boolean DEBUG = true/boolean DEBUG = false/g" src/**/*.java
...
ant $1

この後続けて開発してコミットをするといつの間にか DEBUG = false な状態になり、だれが DEBUG = false にしたんだ!! と喧嘩が起きる。

AndroidManifest.xmlandroid:debuggable で切り替えるようにする

  • 利点
    • この設定はだいたいみんな知ってるから使いやすい
  • 問題点
    • android:debuggable の判定は (当たり前だけど) 実行時に行なわれる
      • コンパイルするとデバッグ情報が出てくる
      • AndroidManifest.xml を差し替えるだけで開発用のホストに繋がるようになってしまう
if ((info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE) {
    isDebug = true;
}
...
if (isDebug) {
    setApiEndpoint("http://api.dev.example.com/");
} else {
    setApiEndpoint("https://api.example.com/");
}

ユーザは apktool などを使って AndroidManifest.xml を書き換えることによって api.dev.example.com に繋がるアプリを作ることが可能になってしまう。

BuildConfig を使うようにする (ADT 17 以降)

  • 利点
    • コンパイル時に指定される定数なので不要なコードは最適化で消える
  • 問題点
    • だんだん複雑な条件を作りたくなる
if (BuildConfig.DEBUG) {
    setApiEndpoint("http://api.dev.example.com/");
} else {
    setApiEndpoint("https://api.example.com/");
}

Gradle で BuildTypes と ProductFlavors を使い分けて APK を作り分ける

Gradle で ProductFlavor を使うとビルド時に簡単にソースコードを差し替えられる。

ProductFlavors は有料版と無料 (広告) 版の切り替えとかに使うと便利、みたいなことを書いているけど、実際には API エンドポイントとか API トークンとかを切り替えるときに便利だと思う。

DEBUG と RELEASE の切り替えは BuildTypes というやつで別途設定できる。

apply plugin: 'android'

android {
    signingConfigs {
        debug {
            storeFile file('keys/debug.keystore')
        }
        release {
            storeFile file('keys/release.keystore')
            storePassword 'STORE_PASSWORD'
            keyAlias 'KEY_ALIAS'
            keyPassword 'KEY_PASSWORD'
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
    productFlavors {
        beta {}
        real {}
    }
}

Release を指定したときには以下をやればよい。

  • リリース用証明書で署名する
  • ProGuard を有効にする
  • パッケージ名の変更もできる
    • 開発版とリリース版を両方一つの端末にインストールすることができる

エンドポイントや API トークンは ProductFlavors で切り替える。 Gradle のディレクトリ構成はちょっと特殊だけど、以下のようなディレクトリ構成にする。

+ src
    + main
    |    + java
    |    + res
    |    + ...
    + beta
    |    + java
    |    + res
    |    + ...
    + real
         + java
         + res
         + ...

そして、 例えば API のエンドポイントを変えたい場合は以下のようにする。

  • src/beta/res/values/config.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="api_endpoint">http://api.dev.example.com/</string>
</resources>
  • src/real/res/values/config.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="api_endpoint">http://api.example.com/</string>
</resources>

開発してるときには以下のコマンドで、

# beta 版 API の場合
./gradlew clean assembleBetaDebug

# real 版 API の場合
./gradlew clean assembleRealDebug

リリース物を作るときは以下のコマンドを使えばよい。

./gradlew clean assembleRealRelease

gradlew を使っておけば、 Java さえあれば様々な環境ですぐにビルドできるようになる。

まとめ

Gradle を使えば Android 開発で起きがちだった DEBUGRELEASE の切り替え、さらには API エンドポイントや API トークンの切り替えが気軽に出来るようになる。

さらに詳しいことは

Gradle Plugin User Guide を読もう