こんにちは、間藤です。
久しぶりとなってしまいましたが、今回はAndroid StudioのBuild TypeとかFlavorについて書いてみます。何を今更な話題かもしれませんが、自分のためにもちょっと整理しておきます。
なお、今回利用しているのは、以下のバージョンです。
Android Plugin
何はともあれ、GradleのAndroid PluginのUser Guideに目を通しておくのがよいでしょう。
Gradle Plugin User Guide - Android Tools Project Site
Build Typesのところを読むと、デフォルトでdebugとreleaseのtype(ガイドにはversionと書かれてます)があるということです。
特に説明は不要かと思いますが、debugは開発時用のビルド設定を、releaseは本番リリース用のビルド設定を行う目的で用意されたものだと理解できます。具体的には、
- apkファイルへの署名
- Proguardの適用
といった設定を切り替えるためのものと考えればよいのではないかと。
一方のFlavorも、ビルド設定を切り替える目的に利用するものです。これをどう活用するか(Build Typesとどう使い分けるか)は利用者次第ということなんだと思います。例えば、テスト環境や本番環境によって接続するサーバを変えたいなんていう場合、Flavorを環境別に用意するといった使い方が思いつきます。
そして、Build TypeとFlavorを掛け合わせたものが、Build Variantです。例えば、Build Typeはdebugとrelease、Flavorにdevelop/staging/productionの3つを用意したとすると、Build Variantは以下の6つということになります。
- developDebug
- stagingDebug
- productionDebug
- developRelease
- stagingRelease
- productionRelease
では、実際にFlavorを追加してみましょう。
Flavorの追加
Gradleのスクリプトを直接編集してFlavorを追加することもできますが、[Project Structure]の設定画面から行えば、スクリプトのほうに反映されます。
developとstagingについては、Version CodeとVersion Nameにそれぞれ異なる値を設定しておきます。そうすると、ビルド時にその設定がマニフェストに反映されるので、各環境にリリースするバージョンをここで管理できるようになります。(後で結果も確認してみます)
なお、productionについては指定をしませんが、その場合はdefaultConfig(これもFlavorです)に設定されている値が使われるようです。
結果、Gradleのスクリプトは以下のようになりました。(抜粋です)
productFlavors { develop { versionName '1.2' versionCode 3 } production { } staging { versionCode 2 versionName '1.1' } }
前述のようにBuild Variantが増えているか確認してみましょう。[Build Variants]のViewで確認できます。
これらをビルドするのは後回しにして、もう少し設定します。
Build Typeの設定
releaseの場合は、署名付きでapkファイルが作成されるようにします。
また、Proguardを適用するようにします。
まず、署名に利用するキーストアを作成しましょう。作成方法はいろいろありますが、Android Studioで作成するなら[Build]-[Generate Signed APK]を選び、途中の画面で[Create New...]を選択すると作成できます。
なお、この画面で生成すると、キーストアの拡張子はkeystoreではなくjksになるようです。
次に[Project Structure]でsigningConfigsブロックの設定を行います。
設定は以下のようにスクリプトに反映されます。
signingConfigs { sampleConfig { keyAlias 'sample' keyPassword 'fugahoge' storeFile file('/Users/matoh/sample.jks') storePassword 'hogefuga' } }
次にreleaseのSigning Configに作成したコンフィグレーションを指定します。
これでrelease版のBuild Variantでは署名付きでAPKが作成されるようになります。(後で確認をします)
ただ、上の書き方ですと、キーストアやキーのパスワードをスクリプトにベタ書きすることになります。実際の運用では、リリース用ビルドを行うマシンにあれば良い情報ですので、設定を外だしにします。環境変数にしたり、gradle.propertiesに書いたりといった方法もありますが、以下のサイトに紹介されているように別Gradleスクリプトに外出しするという方法も悪くないなと思います。
ですが、外出ししたスクリプトをソースコード管理対象にしてしまうと意味がありませんので、もう少し工夫が必要かと思います。例えば、release.gradleはホームディレクトリ配下に置くことし、debug版のビルドではそのファイルが存在しなくてもビルドがエラーにならないようにします。
String signingConfigFilePath = "${System.properties['user.home']}/signingConfigs/release.gradle" File signingConfigFile = new File(signingConfigFilePath) android { signingConfigs { sampleConfig { keyAlias 'sample' keyPassword 'xxxxx' storeFile file("${System.properties['user.home']}/sample.jks") storePassword 'xxxxx' } } if(signingConfigFile.exists()) apply from: signingConfigFilePath, to: android ・・・
このスクリプト上に記載するパスワードは出鱈目なものにしてしまいます。そして、ホームディレクトリ配下のrelease.gradleには正しい設定を施し、このファイルが存在する場合のみapply()メソッドで正しい設定を読み込むようにします。(このファイルはリリースビルド用マシンにだけ配置するという想定です)
次にProguardの設定です。Android Studioでプロジェクトを生成した直後では、proguardFilesが以下のように設定されていました。
release { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' }
ただ、これだけではProguadは適用されませんので、minifyEnabledプロパティにtrueを設定します。この設定も[Project Structure]画面で行えます。
release { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.sampleConfig minifyEnabled true }
debugのほうにも少しだけ設定を加えます。
debug {
applicationIdSuffix ".debug"
}
これでアプリケーションパッケージに".debug"が付与されるので、release版とdebug版が別アプリとしてインストールされるようになります。
ソースセットの追加
Build TypeやFlavorを追加すると、自動的にソースセットも追加されます。ただ、対応するディレクトリは自分で作成する必要があります。今回はそれぞれにJavaのソースコードを配置してみます。debugであれば、src/debug/javaディレクトリ配下にjavaのソースコードを配置します。
以下のように配置しました。
debugとdevelop配下が、それ以外と表示が違っていますが、これはBuild Variantに"developDebug"を選択しているからです。つまり、これらがビルド対象になっているということです。よって、debug(Build Type)とdevelop(Flavor)配下に同じクラスを配置することは出来ません。debugとreleaseであれば、同じクラス(この例だとBuildType)を配置できます。
BuildTypeクラスとFlavorクラスは、以下のように文字列定数を定義してあります。
public class BuildType { public static final String TYPE_NAME = "DEBUG"; }
public class Flavor { public static final String FLAVOR_NAME = "DEVELOP"; }
これら定数に個別の値を与えることで、例えば環境別に接続先サーバを変えるといったことが可能になるでしょう。
なお、ActivityにTextViewを3つ用意しておいて、これら定数の値を画面表示するようにしておきます。
※AndroidAnnotationsを利用しています。
@ViewById TextView buildType; @ViewById TextView flavor; @ViewById TextView version; @AfterViews protected void init() { String versionStr = ""; try{ PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0); versionStr = String.format("%s(%d)", packageInfo.versionName, packageInfo.versionCode); }catch(PackageManager.NameNotFoundException e){ e.printStackTrace(); } version.setText(versionStr); buildType.setText(BuildType.TYPE_NAME); flavor.setText(Flavor.FLAVOR_NAME); }
確認
ここまで仕込んだことを確認していきます。
まずはビルドしていきます。全てのBuild Variantを一括でビルドするには以下のコマンドをプロジェクト直下で実行します。
$ ./gradlew assemble
assembleが全てのBuild Variantをビルドするタスクになっています。
debug版すべてをビルドするなら、
$ ./gradlew assembleDebug
staging版すべてをビルドするなら、
$ ./gradlew assembleStaging
といったようなタスクも用意されています。
ビルドが成功すれば、app/build/outputs/apk配下に各Build Variantに対応したapkファイルが作成されています。
$ ls ./ app-production-debug.apk ../ app-production-release-unaligned.apk app-develop-debug-unaligned.apk app-production-release.apk app-develop-debug.apk app-staging-debug-unaligned.apk app-develop-release-unaligned.apk app-staging-debug.apk app-develop-release.apk app-staging-release-unaligned.apk app-production-debug-unaligned.apk app-staging-release.apk
AndroidManifest
build版ではパッケージに".debug"を付けるように設定しました。また、FlavorごとにVersion CodeとVersion Nameを変えるようにしました。ちゃんと結果に反映されているか確認してみます。
$ aapt dump xmltree app-develop-debug.apk AndroidManifest.xml N: android=http://schemas.android.com/apk/res/android E: manifest (line=2) A: android:versionCode(0x0101021b)=(type 0x10)0x3 A: android:versionName(0x0101021c)="1.2" (Raw: "1.2") A: package="iti.co.jp.gradlesample.debug" (Raw: "iti.co.jp.gradlesample.debug") A: platformBuildVersionCode=(type 0x10)0x16 (Raw: "22") A: platformBuildVersionName="5.1.1-1819727" (Raw: "5.1.1-1819727") E: uses-sdk (line=7) A: android:minSdkVersion(0x0101020c)=(type 0x10)0xf A: android:targetSdkVersion(0x01010270)=(type 0x10)0x16 E: application (line=11) A: android:theme(0x01010000)=@0x7f08006f A: android:label(0x01010001)=@0x7f060012 A: android:icon(0x01010002)=@0x7f030000 A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff A: android:allowBackup(0x01010280)=(type 0x12)0xffffffff E: activity (line=16) A: android:label(0x01010001)=@0x7f060012 A: android:name(0x01010003)="iti.co.jp.gradlesample.MainActivity_" (Raw: "iti.co.jp.gradlesample.MainActivity_") E: intent-filter (line=19) E: action (line=20) A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") E: category (line=22) A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
debug版なので、packageが"iti.co.jp.gradlesample.debug"となっています。
develop版なので、Version Codeが3、Version Nameが1.2となっています。
念のため、もう1つ確認してみます。
$ aapt dump xmltree app-production-release.apk AndroidManifest.xml N: android=http://schemas.android.com/apk/res/android E: manifest (line=2) A: android:versionCode(0x0101021b)=(type 0x10)0x1 A: android:versionName(0x0101021c)="1.0" (Raw: "1.0") A: package="iti.co.jp.gradlesample" (Raw: "iti.co.jp.gradlesample") A: platformBuildVersionCode=(type 0x10)0x16 (Raw: "22") A: platformBuildVersionName="5.1.1-1819727" (Raw: "5.1.1-1819727") E: uses-sdk (line=7) A: android:minSdkVersion(0x0101020c)=(type 0x10)0xf A: android:targetSdkVersion(0x01010270)=(type 0x10)0x16 E: application (line=11) A: android:theme(0x01010000)=@0x7f08006f A: android:label(0x01010001)=@0x7f060012 A: android:icon(0x01010002)=@0x7f030000 A: android:allowBackup(0x01010280)=(type 0x12)0xffffffff E: activity (line=16) A: android:label(0x01010001)=@0x7f060012 A: android:name(0x01010003)="iti.co.jp.gradlesample.MainActivity_" (Raw: "iti.co.jp.gradlesample.MainActivity_") E: intent-filter (line=19) E: action (line=20) A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") E: category (line=22) A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
想定通りになっています。
署名
debug版は、debug.keystoreで署名されています。
$ jarsigner -verify -verbose -certs app-staging-debug.apk sm 1904 Thu Jun 11 14:25:52 JST 2015 AndroidManifest.xml X.509, CN=Android Debug, O=Android, C=US [証明書は15/03/31 16:41から45/03/23 16:41まで有効です] [CertPathが検証されていません: Path does not chain with any of the trust anchors] ・・・(以下省略)
release版は、用意したキーストアで署名されています。
$ jarsigner -verify -verbose -certs app-staging-release.apk sm 1840 Thu Jun 11 14:26:30 JST 2015 AndroidManifest.xml X.509, O=iti [証明書は15/06/08 19:23から40/06/01 19:23まで有効です] [CertPathが検証されていません: Path does not chain with any of the trust anchors] ・・・(以下省略)
Proguard
少々面倒くさいですが、apkを解凍して、dex2jarでdexをjarに変換、JD-GUIでデコンパイルします。
$ cp app-production-release.apk app-production-release.zip $ open app-production-release.zip $ cd app-production-release/ $ d2j-dex2jar.sh classes.dex ※パスが通っているとして dex2jar classes.dex -> ./classes-dex2jar.jar
ざっくりな確認ですが、ちゃんと難読化されていそうです。
画面表示
想定通りに画面表示されているかをサンプリング確認してみます。
$ adb install -r app-staging-debug.apk
大丈夫そうです。
念のためもう1つ確認します。
$ adb install -r app-production-release.apk
よさそうです。
なお、debug版とrelease版は、別アプリとしてインストールされています。
以上で目論見通りを確認できたと思います。