Posted at

Eclipse+Ant勢にオススメする、Android Studio+Gradleに移行すると手に入る便利機能

More than 5 years have passed since last update.

@eaglesakuraです。2013年にAndroid Studioが発表されて、だいたい1年ちょいが経過しました。最初は不安定でアレな感じだったAndroid StudioとGradleプラグインもそれなりに安定してきて、普通のAndroidアプリを開発する分にはあまり困らなくなりました。

現在は移行期にあたるため、2014年のEclipseを使っている人も多いと思いますので、「Android Studioって正直どうよ?」っていう人たちのためにEclipse+Antにはない利点を列挙していきます。


ビルドがGradleに統合される

最大の利点で、コレが移行するための大きな動機であり障壁です。Gradleはそこそこ新しいビルドシステムで、Groovy言語でビルド内容を記述します。

ビルド用ファイルであるbuild.gradleは、Android Studio(GUI環境)からもコマンドライン(CUI環境)からもビルドが行えます。また、ビルド結果は基本的に等しくなります。そのため、Jenkins等のCIと非常に相性が良く、簡単にビルド自動化を行えます。

Eclipseの場合、GUIビルドとCUIビルドが分離してしまっているため、ant用build.xml出力やShell Scriptを記述する等、かなりの労力が必要になります。それがほぼなくなると言えます。

Android StudioのGUI的な視点はネット上にいろいろ情報が出ているため、ビルドシステム側の利点で語ります。


プロジェクト構成が一目瞭然となる

GradleではRootディレクトリにsettings.gradleファイルがあり、その中で依存関係のあるプロジェクト(ライブラリプロジェクト等)が記述されているため、どのディレクトリが使用されているのかが一発でわかります。

Android Studioは自動的にsettings.gradleの内容を読み取り、ワークスペースを構築してくれます。そのため、リポジトリからpull -> import projectを行えばそれだけで開発環境が出来上がります。

Eclipseのように「え、これどのプロジェクトをimportすればいいの?」となることはありません。ideaプラグインを追加しておけば、*.imlや.idea/といったプロジェクト定義用ファイルをコミットする必要もなくなるでしょう。

なので、settings.gradleはちゃんとメンテしましょう。


依存関係の解決が楽になる

twitter4jやAndroid Annotations等の便利なライブラリを使用する際、必須となるJarの配置はどのように行っていましたか? Eclipseの場合はいくつかの手段が考えられます。


  1. POM職人になってXMLをしこしこ書く

  2. リポジトリに直接Jarをコミット

  3. 「直接ネットの海から拾って来い」といって放り出す

POM職人が一番スマートですが、「職人」と言われるように超複雑で可読性に難のあるXMLをガンバって書くしかありませんでした。しかもPOM職人のいないプロジェクトでは恐らく採用出来ません。そのため2/3の方法を取る場合もあったのではないでしょうか。

Android StudioではビルドシステムがGradleに統合されているので、依存関係の解決が非常に簡単です。具体的には、Maven Centralで検索可能なライブラリは全てbuild.gradleのdependenciesブロックに記述するだけで終了します。

dependencies {

// Twitter4j
compile 'org.twitter4j:twitter4j-core:4.0.+'
// Volley
compile 'com.mcxiaoke.volley:library-aar:1.0.0'
}

この時に記述する"org.twitter4j:twitter4j-core:4.0.+"も最初は黒魔術に見えるかもしれませんが、法則を知っていると簡単に記述できます。


Maven Centralのおまじない

MavenCentralで使用したいライブラリ名を検索すると、配布されている「GroupId」「ArtifactId」「バージョン名」等が一覧表示されます。それらを:(コロン)で区切って記述するだけです。

また、バージョン名は"4.0.+"のように+(プラス)記号を記述しておくと最新版を取りに行ってくれます。

「開発中はとりあえず+を付けて最新版を使用して、終了時にバージョンを固定しておく」という使い方をすれば、仮にライブラリに破壊的なアップデートや「なんか最新版にしたら動かないんだけど」なことをされたとしても開発当時のバージョンを使用することが出来るでしょう。

とりあえず検索で見つかった「GroupId」「ArtifactId」「バージョン名」をコロンで区切って書くと覚えておけばなんとかなります。

compile "${MavenCentralのGroupId}:${MavenCentralのArtifactId}:${使用したいバージョン名}"


AndroidManifestのビルド時制御が行える

EclipseではAndroidManifestは開発者が直接記述し、それをビルドパイプラインに放り込むしかありませんでした。

私の場合はJenkinsでビルドした場合、テストバージョンでは"0.1.${Jenkinsのビルド番号}"をバージョン名に指定するということが多いですが、Antでは動的にAndroidManfiestを書き換えられないため、Shell Scriptのテキスト置換等を駆使して頑張っていました。

GradleにはAndroidManifest.xmlに最低限の情報だけ記述し、ビルド時に最終的な情報を追加・修正行う仕組みが備えられています。

また、GradleはGroovyで記述できるビルドシステムのため、動的な制御が得意です。

Jenkinsでビルドした場合とローカルPCでビルドした場合でバージョン名・バージョンコードを書き換えることが出来ます。下の例では、ビルド時に環境変数"BUILD_NUMBER"を取得し、!=nullであればJenkinsでのビルド、==nullであればローカルPCでのビルドと判断してバージョン名・バージョンコードを設定しています。

ちなみに、rootにあるbuild.gradleの"ext"ブロックにプロパティを適当に定義しておけば、他のbuild.gradleからでも参照できます。

// rootのbuild.gradle

ext {
// build number
BUILD_VERSION_CODE = System.getenv("BUILD_NUMBER");
BUILD_VERSION_NAME = "0.01.${BUILD_VERSION_CODE}.jks";
BUILD_DATE = System.getenv("BUILD_ID");

// Jenkins以外から実行されている場合、適当な設定を行う
if (BUILD_VERSION_CODE == null) {
println("not jenkins");
BUILD_VERSION_CODE = "1";
BUILD_DATE = new Date().toLocaleString();
BUILD_VERSION_NAME = "build.${System.getenv("USER")}.${BUILD_DATE}";
}

println("setup ext(${ext.RELEASE_DIRECTORY.absoluteFile})");
println("setup BUILD_DATE(${BUILD_DATE})");
println("setup BUILD_VERSION_CODE(${BUILD_VERSION_CODE})");
println("setup BUILD_VERSION_NAME(${BUILD_VERSION_NAME})");
}

// android/build.gradle

android {
defaultConfig {
applicationId "com.example.android.app"
minSdkVersion 19
targetSdkVersion 19
versionCode BUILD_VERSION_CODE as int;
versionName BUILD_VERSION_NAME
}
}


複数の環境を同時に構築できる

アプリによっては、「リリース用」「デバッグ用」「デモ用」「リリース用だけどデモ環境につながる」等、1つのアプリを複数の環境や条件下でビルドする必要があるでしょう。

EclipseではLibraryProjectにする等の工夫に工夫を重ねて実現していましたが、ヒューマンエラーが入り込む余地が多すぎてあまり効率的とは言えませんでした。また、「ちょっと明日デモだから専用環境とpackage名でapk作ってよ」とか言われてしまうと突然の死を迎えてしまいます(開発者が)。


debug版とrelease版の環境を切り替える

Gradleでビルドする場合、android.buildTypesブロックを設定することでdebug環境とrelease環境の切り分けを行えます。更に、debug版とrelease版でアプリのpackage名を変更することも可能であるため、複数環境の同時インストールも可能です。

署名鍵をdebug版とrelease版で明示的に記述することも出来ます。Google Maps APIを使用する等で署名鍵に制限がある、もしくは複数PCで開発していて署名鍵を統一したいという場合に非常に便利です。

andorid {

signingConfigs {
debug {
storeFile file('../sign/debug.jks')
storePassword 'example'
keyAlias 'example'
keyPassword 'example'
}

release {
storeFile file('../sign/release.jks')
storePassword 'example'
keyAlias 'example'
keyPassword 'example'
}
}

buildTypes {
debug {
// デバッグビルド時は"package名+.debug"を入れる
applicationIdSuffix ".debug"

// デバッグ版署名
signingConfig signingConfigs.debug
}

release {
// 署名設定
signingConfig signingConfigs.release
}
}
}


demo版等の特殊なバージョンをビルドする

アプリによっては前述のようにdebug/releaseだけでなく、「本番環境」「デモ環境」「次期バージョン環境」等等、いろいろなバージョンを開発する必要もあるでしょう。それどころか、「バグあってもいいから古い端末にインストールさせてよ」というぶん投げもあるかもしれません。さらにJenkinsで自動化していると、さらに変更箇所が……と、目眩がしますね。

そんな時に便利なのがandroid.productFlavorsブロックです。productFlavorsは特定環境向けのビルド設定を記述することが出来ます。また、フレーバー名は自由に指定できます。例では"mobile"フレーバーでAPIレベル19向けのビルド、"demo"フレーバーでAPIレベル15向けに無理やりビルドといったことを行っています。

andorid {

productFlavors {
mobile {
compileSdkVersion 19
applicationId "com.example.mobile"
}

demo {
compileSdkVersion 19
targetSdkVersion 15
applicationId "com.example.demo"
}
}
}


特定環境のみのプログラムやリソースを指定する

demo版でのみ必要になるファイルやプログラムも、中にはあるでしょう。その場合、特定ディレクトリを作成することで自動的にそれらを使用してビルドしてくれます。"mobile"フレーバーを例に見てみましょう。

通常のGradleビルド構成ではsrc/main/配下にプログラムやAndroidManifest.xmlを配置します。mobileフレーバーを追加し、そのフレーバーでしか使用しないプログラム(例えばDebug専用Activity等)は、次のようなディレクトリを作成することで自動的に使用してビルドしてくれます。

それだけでなく、android/src/mobile/AndroidManifest.xml(便宜上、差分Manifestと呼びます)置いておくと、自動的にmain側のAndroidManifest.xmlと統合してくれます。mobileフレーバーでしか使用しないActivity等はこの差分Manifestに記述しておくことで他のフレーバーに影響されること無く専用apkを作成できます。


  • android


    • andorid/src


      • android/src/main


        • ここに全フレーバーで使用する*.javaやリソースなどを入れる



      • android/src/mobile


        • ここにmobileフレーバーでしか使用しない*.javaやリソース等を入れる







更に「mobileフレーバーのdebug版のみ」「mobileフレーバーのrelease版のみ」のリソースをそれぞれ入れたい場合があります。特に、AndroidManifestにGCMのpermissionを記述する場合でかつdebug版とrelease版を共存する(別なpackage名にする)場合は必須となります。

その場合、さらに"mobileDebug""mobileRelease"のようにビルドタイプごとにディレクトリを作ることで切り分けや統合を行ってくれます。


  • android


    • andorid/src


      • android/src/main


        • ここに全フレーバーで使用する*.javaやリソースなどを入れる



      • android/src/mobile


        • ここにmobileフレーバーでしか使用しない*.javaやリソース等を入れる



      • android/src/mobileDebug


        • ここにmobileフレーバーのDebug版でしか使用しない*.javaやリソース等を入れる



      • android/src/mobileRelease


        • ここにmobileフレーバーのRelease版でしか使用しない*.javaやリソース等を入れる








Android Studio上でビルド対象のフレーバー/ビルドタイプを切り替える

Android Studioには(多分)左下のほうに"Build Variants"というボタンがあります。それをクリックすると、Build Variantというメニューが表示されるので、その中から好きなフレーバーとビルドタイプの組み合わせ"mobileDebug""demoRelease"等を切り替えることが出来ます。


自分用プラグインがそこそこ簡単に作れる

Gradleでプロジェクトをいくつか作っていると、定形処理が出てきます。例えば「成果物を特定ディレクトリにまとめる」とかそういうのです。GradleプラグインはGroovy言語をある程度覚えれば簡単に開発することが出来ます。

私の場合は毎度毎度定形っぽくなりがちな「アプリ用の設定クラスを出力する」「greenDaoのクラスを出力する」「Jenkinsの成果物収集」といった内容をプラグインにまとめて使用しています。

例えばこんなかんじでbuild.gradleに記述すると、Androidアプリで使用する設定保存クラスを吐き出してくれます。こういう「だいたい自分しかつかわねーよなー。けど俺氏はよく使うよなー」という機能はpluginにしておくと楽です。自分が。

// build.gradle

task generateProps(type: com.eaglesakura.gradle.tasks.AndroidPropsGenTask) {
outDirectory file('src/main/generated/props')

/**
* ユーザーの基本設定生成
*/
def PersonalDataSettings = newProps("${android.defaultConfig.applicationId}.db.PersonalDataSettings")
PersonalDataSettings.intProperty "maxHeartrate", 190
PersonalDataSettings.enumProperty "wheelType", "com.eaglesakura.andriders.db.user.WheelType", "Std700x23c"
PersonalDataSettings.stringProperty "zoneSettingId", "nil"
}

// 出力されたクラス

public class PersonalDataSettings extends com.eaglesakura.android.db.BasePropertiesDatabase {

public static final String ID_MAXHEARTRATE = "PersonalDataSettings.maxHeartrate";
public static final String ID_WHEELTYPE = "PersonalDataSettings.wheelType";
public static final String ID_ZONESETTINGID = "PersonalDataSettings.zoneSettingId";

public PersonalDataSettings(Context context){ super(context, "props.db"); _initialize(); }
public PersonalDataSettings(Context context, String dbFileName){ super(context, dbFileName); _initialize(); }
protected void _initialize() {

addProperty("PersonalDataSettings.maxHeartrate", "190");
addProperty("PersonalDataSettings.wheelType", "Std700x23c");
addProperty("PersonalDataSettings.zoneSettingId", "nil");

load();

}
public void setMaxHeartrate(int set){ setProperty("PersonalDataSettings.maxHeartrate", set); }
public int getMaxHeartrate(){ return getIntProperty("PersonalDataSettings.maxHeartrate"); }
public void setWheelType(com.eaglesakura.andriders.db.user.WheelType set){ setProperty("PersonalDataSettings.wheelType", set != null ? set.name() : ""); }
public com.eaglesakura.andriders.db.user.WheelType getWheelType(){ try{ return com.eaglesakura.andriders.db.user.WheelType.valueOf(getStringProperty("PersonalDataSettings.wheelType")); }catch(Exception e){ return null; } }
public void setZoneSettingId(String set){ setProperty("PersonalDataSettings.zoneSettingId", set); }
public String getZoneSettingId(){ return getStringProperty("PersonalDataSettings.zoneSettingId"); }

}

慣れてきたらプラグインを作ることで効率的に作業が行えるようになるでしょう。


死を招く使い方

記事はAndroid Studioすげー!Gradleすげー!で通してきましたがAndroid Studioは無敵ではありません。特にNDKサポートが貧弱!貧弱ゥ!なので、Eclipseが見捨てられ、Android Studioが未成熟な現在はゲーム開発者にとって今はとてもつらい時期といえます。

年単位で「いつか」は解決されると思いますが、その「いつか」が来るまでEclipseとAndroid Studioの適材適所が続きそうです。