ScalaでAndroid 2012年冬

このエントリはAndroid Advent Calendarの6日目裏です。今日の表は@ngsw_taroさんです。

さて、AndroidといえばScalaやKotlin、Haxeなど*1で書くことが多いと思いますが、今回はその中でも割とメジャーなScalaでのやり方についてまとめてみます。
Scalaで書くための手法はいくつかありますが、今回は現在最も主流である(と思われる) sbt android plugin (https://github.com/jberkel/android-plugin) を使った方法について書きます。
開発の流れとしてはターミナルでアプリケーションの作成とテスト、ビルドをして、コードはIDEとかで書く感じになります。

環境準備

何はともあれ開発環境を整える。入れなければならないのは以下の通り。

Scala本体とandroid sdkはいいとして、sbtはMavenっぽいScalaのビルドツール、giter8はプロジェクトのひな形を作るためのツールです。
いずれもTypesafe — Stack: Downloadに沿ってインストールしておきます。Macなら全てhomebrewで入るのでぬるげーです。ちなみに今回使用したバージョンは以下になります。

λ ~/sandbox/ → scala -version
Scala code runner version 2.9.2 -- Copyright 2002-2011, LAMP/EPFL
λ ~/sandbox/ → sbt sbt-version
[info] Loading global plugins from /Users/takashabe/.sbt/plugins
[info] Set current project to default-171b7b (in build file:/Users/takashabe/sandbox/)
[info] 0.12.1
λ ~/sandbox/ → g8

giter8 0.4.5

android sdkは普通にインストールしておき、pathを通して環境変数を作っておきます。r21を使っています。

λ ~/ → cat .zshrc_custom 
## いろいろ略

# ANDROID-SDK
export ANDROID_SDK_HOME=/Applications/android-sdk-macosx/
export PATH=/Applications/android-sdk-macosx/platform-tools:/Applications/android-sdk-macosx/tools:$PATH

プロジェクトを作る

何はともあれgiter8でひな形を作っておきます。注意点としてはProguardをtrueにしないとコンパイルに失敗します。

λ ~/sandbox/ → g8 jberkel/android-app

Template for Android apps in Scala 

package [my.android.project]: com.takashabe.sample
name [My Android Project]: sample
main_activity [MainActivity]: 
scala_version [2.9.2]: 
api_level [10]: 
useProguard [true]: 
scalatest_version [1.8]: 

Applied jberkel/android-app.g8 in sample

λ ~/sandbox/ → cd sample 
λ ~/sandbox/sample/ → ls
project src     tests

この状態で一度動かしてみます。テストとかビルドとかは全てsbt経由で行います。
android:package-debugコマンドでコンパイルして、android:start-deviceでapkを流し込むっぽいことをやってます。
なお、エミュレータで動かす場合はandroid:start-deviceをandroid:start-emulatorとすれば良いだけです。

λ ~/sandbox/sample/ → sbt
[info] Loading global plugins from /Users/takashabe/.sbt/plugins
[info] Loading project definition from /Users/takashabe/sandbox/sample/project
[info] Updating {file:/Users/takashabe/sandbox/sample/project/}default-883c40...
[info] Resolving org.scala-sbt#precompiled-2_10_0-m7;0.12.1 ...
[info] Done updating.
[info] Compiling 1 Scala source to /Users/takashabe/sandbox/sample/project/target/scala-2.9.2/sbt-0.12/classes...
[info] Set current project to sample (in build file:/Users/takashabe/sandbox/sample/)
> ;android:package-debug;android:start-device

## 略

[info] Dexing /Users/takashabe/sandbox/sample/target/classes.dex
[info] Packaging /Users/takashabe/sandbox/sample/target/sample-0.1.apk
[success] Total time: 19 s, completed Dec 5, 2012 2:15:11 AM
[info] Wrote /Users/takashabe/sandbox/sample/target/scala-2.9.2/src_managed/main/scala/com/takashabe/sample/TR.scala
ProGuard, version 4.6
ProGuard is released under the GNU General Public License. You therefore
must ensure that programs that link to it (scala, ...)
carry the GNU General Public License as well. Alternatively, you can
apply for an exception with the author of ProGuard.
The output seems up to date
[info] Packaging /Users/takashabe/sandbox/sample/target/sample-0.1.apk
[info] 	pkg: /data/local/tmp/sample-0.1.apk
[info] Success
[info] 3127 KB/s (177013 bytes in 0.055s)
[info] Starting: Intent { act=android.intent.action.MAIN cmp=com.takashabe.sample/.MainActivity }
[success] Total time: 3 s, completed Dec 5, 2012 2:15:14 AM
> 

Hello, world!
f:id:takashabe:20121205023134p:plain
この時点でビルドに失敗する場合はproject/plugin.sbtとかBuild.scalaで指定されているpluginのバージョンが間違っている可能性があります。っていうか以前はよくありました。

IDEとの連携

ビルド出来ることは分かったので、次はIDE上にプロジェクトをimportする方法について見ていきます。
sbtでは各種IDE用の設定ファイルを生成するためのPluginが存在するので、まずはそのPluginを使うようにします。
以下のようにplugin.sbtに下4行を追加すればいいだけですが、空行を開ける必要があるので注意です。1.2.0-SNAPSHOTの部分は適宜最新バージョンを指定してください。*2
ちなみにプロジェクトごとに毎回書くのがだるい場合は~/.sbt/plugins/build.sbtあたりに書いておけばプロジェクト全体に勝手に適用されます。

diff --git a/project/plugins.sbt b/project/plugins.sbt
index e3163f8..a7580b7 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,3 +1,7 @@
 resolvers += Resolver.url("scalasbt releases", new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)

 addSbtPlugin("org.scala-sbt" % "sbt-android-plugin" % "0.6.2")
+
+resolvers += "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
+
+addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0-SNAPSHOT")

次にsbtを起動した時にPluginのインストールが始まるので、以下のコマンドでIDE用のファイルが生成されます。

> gen-idea

## 色々生成している

> exit
λ ~/sandbox/sample/ → λ git master* → l
total 0
drwxr-xr-x   9 takashabe  staff  306 Dec  6 20:12 .
drwxr-xr-x   3 takashabe  staff  102 Dec  5 02:07 ..
drwxr-xr-x  13 takashabe  staff  442 Dec  6 20:16 .git
drwxr-xr-x  11 takashabe  staff  374 Dec  6 20:12 .idea
drwxr-xr-x   5 takashabe  staff  170 Dec  6 20:12 .idea_modules
drwxr-xr-x   6 takashabe  staff  204 Dec  6 19:58 project
drwxr-xr-x   4 takashabe  staff  136 Dec  5 02:07 src
drwxr-xr-x  12 takashabe  staff  408 Dec  6 20:12 target
drwxr-xr-x   4 takashabe  staff  136 Dec  6 20:12 tests

あとはIDEA上でプロジェクトをopenすればOKです。Scala Pluginはインストールしておく必要があります。
syntax highlightされなかったりpathが見つからないとか言われた時はProject StructureでJDK, Android jar, Scalaのpathを編集すれば何とかなるはずです。。。この記事書いてる時点でクリーンな環境が無かったのであばばば
とりあえず今日発表されたばかりのIDEA 12でも動作してます。

f:id:takashabe:20121206204737p:plain

継続的にビルドする

sbtではファイルを監視して変更があれば任意のコマンドを実行する機能があります。先頭に ~ つけるだけです。ちなみにセミコロンはコマンドの区切りです。

> ~;android:package-debug;android:start-device

実行するコマンドは何でも良いので ~test とかやれば自動的にテストを回せて素敵です。
ただし、project配下のファイルの変更は監視しないのでplugin.sbtとかに設定を追加した場合は手動でupdate/reloadする必要があります。

Google Playにデプロイする

ではこのsampleアプリもGoolge Playでヒットするアプリに育ったのでデプロイしてみようと思います。
証明書を作る工程は通常と同じです。*3 今回はalias_nameで証明書を作っているものと仮定して進めます。
証明書を作ったらproject/Build.scalaのkey値を証明書に合わせて変更します。

diff --git a/project/Build.scala b/project/Build.scala
index 13dc305..423ec0c 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -23,7 +23,7 @@ object General {
     proguardSettings ++
     AndroidManifestGenerator.settings ++
     AndroidMarketPublish.settings ++ Seq (
-      keyalias in Android := "change-me",
+      keyalias in Android := "alias_name",
       libraryDependencies += "org.scalatest" %% "scalatest" % "1.8" % "test"
     )
 }

次にsbt上で署名してrelease用のapkを生成します。
target/sample-0.1-market.apkがrelease用のapkです。

> ;android:package-release;android:prepare-market

## 略

Enter password for keystore/alias_name: **********
[info] Signed /Users/takashabe/sandbox/sample/target/sample-0.1.apk
[info] Aligned /Users/takashabe/sandbox/sample/target/sample-0.1-market.apk
[success] Ready for publication: 
[success] /Users/takashabe/sandbox/sample/target/sample-0.1-market.apk
[success] Total time: 6 s, completed Dec 6, 2012 9:24:50 PM

ただ、この状態でGoogle Playにデプロイしようとするとエラー内容もなく怒られちゃいます。
f:id:takashabe:20121206221318j:plain

これは以前どハマりしたのですが、どうやらデフォルトのiconのままだとダメっぽい。なので適当な画像を用意してmanifestを書き換えます。
画像を置く場所はいつもどおりres以下に置きます。

λ ~/sandbox/sample/src/main/res/ → λ git master* → l
total 0
drwxr-xr-x    8 takashabe  staff   272 Dec  6 21:33 .
drwxr-xr-x    6 takashabe  staff   204 Dec  6 21:35 ..
drwxr-xr-x@ 145 takashabe  staff  4930 Dec  6 21:33 drawable-hdpi
drwxr-xr-x    3 takashabe  staff   102 Dec  6 21:33 drawable-ldpi
drwxr-xr-x    3 takashabe  staff   102 Dec  6 21:33 drawable-mdpi
drwxr-xr-x    3 takashabe  staff   102 Dec  6 21:33 drawable-xhdpi
drwxr-xr-x    3 takashabe  staff   102 Dec  5 02:07 layout
drwxr-xr-x    3 takashabe  staff   102 Dec  5 02:07 values

AndroidManifest.xmlも上記の画像を読み込むように変更。

diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 62f45c8..f7f1b26 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -2,7 +2,7 @@
     package="com.takashabe.sample">

     <application
-        android:icon="@drawable/android:star_big_on"
+        android:icon="@drawable/ic_launcher"
         android:label="@string/app_name"
         android:debuggable="true">

もう一度release用のapkを作成して、再度デプロイ!
f:id:takashabe:20121206221314j:plain

そういえばバージョン0.1のままでしたので怒りを抑えて整数に直します。
ここで普通ならばAndroidManifest.xmlを直接編集しますが、sbtプロジェクトの場合はproject/Build.scalaのkeyを編集します。*4
でもさっきiconを変えるときにmanifest見たけどバージョンかかれてなかったぞ・・・?とか思われるかもしれませんが、src/main/AndroidManifest.xmlは最終的なmanifestではなくてコンパイル時にsbtがパースして別のmanifestを生成します。*5

diff --git a/project/Build.scala b/project/Build.scala
index 423ec0c..2bfbd80 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -6,8 +6,8 @@ import AndroidKeys._
 object General {
   val settings = Defaults.defaultSettings ++ Seq (
     name := "sample",
-    version := "0.1",
-    versionCode := 0,
+    version := "1.0",
+    versionCode := 1,
     scalaVersion := "2.9.2",
     platformName in Android := "android-10"
   )

これでもう一度apkを生成してデプロイします。
f:id:takashabe:20121206221258j:plain

やりました。メガヒット間違いなしですね。*6


ほんとはテストとかjenkinsとかも書きたかったのですが長くなってきたのと体調がひどい感じなのでここで終わります。テストとかはまた今後書くかも。
明日のAdvent Calendarは表 @chun_ryoさん、裏 @Arigata3さんです。たのしみ!

*1:最近はJavaで書くことも多いようだ

*2:https://github.com/mpeltonen/sbt-idea

*3:http://techbooster.org/android/environment/1445/

*4:sbtでのバージョンとapkのバージョンを合わせたかったのだと思います。たぶん。

*5:target/scala-x.x.x/resource_managed/main/AndroidManifest.xml

*6:大人の事情により消しました。