iOS では Swift での開発が本格化して関数型っぽい言語による開発が世間一般に浸透し始めた2015年ですが、Android 開発といえばクソみたいなのは相変わらずで Java6 でもうね。という感じですがいかがお過ごしでしょうか。
そんななか本日は Android を Scala で開発する方法をまとめた話。
Android を Java ではなく Scala で書く理由
Android は未だに Java7 (実質 Java6) なので、「え〜、Java6〜! Java6 が許されるのは2013までだよね〜」と言われる。
のは冗談としても、iOS 開発では Swift への移行が少しづつ進んで世のエンジニアが「関数型〜」とか「Stream〜」とかいう感じになっているなか、ジャバ6というのは技術的トレンドを追う側面でちょっとつらい。
そこで Android で動作する JVM 方言で関数型っぽい言語での開発が視野に入ってくるわけだけど、現在 Android 開発となると有力なのは Scala, Kotlin, Clojure あたりなんじゃないでしょうか。
この中でも、( )が大好きでたくさん( )を書きたい人や目が5個ある宇宙人が素敵だと思う人は Clojure がいいと思うけど、たいていの Android / Java エンジニアは手続き型っぽい見た目の Scala, Kotlin がとっつきやすいのではないかと思う(思った)。
Androidを Kotlin ではなく Scala で書く理由
Scala と Kotlin はぱっと見の言語仕様はかなり似ているけれど、Kotlin はつい最近 ver. 1.0 が公開されたぐらい新しい言語で、Scala に比べてより一層モダンな仕様になっているという差がある(らしい)。
自分はどちらも詳しくないので、素人基準で Scala / Kotlin のよさそうなところよくなさそうなところを書いてみた。
Scala のよいところ
- 関数型っぽい?(関数リテラル/高階関数がある)
- 超強力なパターンマッチとCase class
- JVM 方言としてはそれなりに古く、言語仕様が枯れてきている(安定している)
- Java のライブラリも普通に扱える = 過去の資産を引き継げる
- それなりに古いので、ライブラリも充実してきており、Java ライブラリの Scala ラッピングが行われているものもチラホラあって、それらは Scala らしく使うことができる (cf. Jackson scala)
- Play Framework という Django ライクな Web Framework が、Scala をリリースしている Lightbend 社から提供されており、サーバーとクライアントでロジックを共通化できる
- SBT というビルドツールが、Scala をリリースしている Lightbend 社(ry
- Akka という非同期ライブラリが、Scala をリリースしている(ry
- Akka はアクターモデルという非同期の管理方法を実現しており、既存のLock Free より安全お手軽(らしい)
Scala のよくないところ
- 言語仕様というかライブラリが重厚長大。ものすごい量?の糖衣構文があり、それなりに慣れてこないとコードを読むことが困難な場合もあるかもしれない
- 同じくライブラリが大きすぎて、Android で使おうとすると Proguard でメソッド数を削り込まないと 65535 名前空間に入りきらない
Kotlin のよいところ
- 関数型っぽい?
- Swift ライクなモダンな言語仕様で iOS との相互開発に向いているらしい
- フローベース型キャストとかおしゃれ
- JetBrains が開発しているので IDE とかビルド環境が快適
- Java との連携がスムーズ
kotlin のよくないところ
-
言語が新しすぎてまだ正式版がリリースされてない(破壊的修正が入るかもしれない)リリースされました! - 言語が新しすぎてまだライブラリが充実していない
- 言語が新しすぎて Web Framework も決定的なのがない
- Java との連携がスムーズとは言え、java のライブラリをつかうとジャバジャバした感じにはなってしまう
上記のような特徴を鑑みて、保守的な私は Scala を選択したのでした。
とくに、Scala 向けライブラリがわりと充実していること、Play Framework によるサーバーサイド開発に興味があったこと、そしてそのロジックが Android でも共通化できるというところが気になって Scala を選択しました。
また、Kotlin が Android にフォーカスしていているものの今後の展開が不明瞭なのに対し、Scala はすでにサーバーサイドで採用実績が結構増えていて実用言語になっていて、転職に有利これをサーバ/クライアント両方で使いこなせるようになれば強いかなと。
プロジェクトの作成・ビルド環境の設定
Kotlin は JetBrains がお膳立てしてくれるのでビルドまで楽ちんとのことだが、Scala も SBT というモダンなビルドツールがあるのでそんなに苦労しない(ことになっている)。そうは言ってもつまづいたことがたくさんあったので、まとめておく。
※困ったことが最後にまとめてあるので、詰まったらそこを見てください。
SBT のインストール
前章でも書いたとおり、Scala では SBT がビルドツールとしてデファクトスタンダードです。SBT は Apache Ivy を利用して依存解決してくれるらしいので、Maven リポジトリにある既存の Java ライブラリとか Scala ライブラリとかが簡単に使えますよっと。
Windows の人は SBT のサイトからインストールパッケージをダウンロードするか、PowerShell + Chocolatey でインストールできる。
choco install sbt
Mac とか Linux で Apt 使える人は
brew install sbt
sudo apt-get install sbt
とかしてみてください。
Android プロジェクトの作成
pfn/android-sdk-plugin を使います。
めんどうだったら、以下の感じのことがひと通りやってある作成済みプロジェクトもあるので参考にしてくだしあ。
$ mkdir HogeAndroidProject
$ cd HogeAndroidProject
$ mkdir project
$ touch project/plugins.sbt
作成した plugins.sbt を好きなテキストエディタで以下のように編集。
addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.6.1")
この状態で、プロジェクトのルートディレクトリ(HogeAndroidProject)で、sbt コマンドで SBT を起動。プラグインとか Scala のライブラリ等が自動的に解決されるので、初回起動はちょっと時間がかかるかも。
SBT が起動したら、gen-android コマンドで Android プロジェクトを作成する。
> gen-android android-22 com.biacco.app.hoge HogeApp
パラメタは前から順に Android ターゲットバージョン、パッケージ、プロジェクト名。
これで Android プロジェクトがよしなに作成される。めでたい。
プロジェクトの Scala 化とビルド設定
これで全て OK と言いたいところだけど、この段階では普通の Java ソースのAndroid プロジェクトが作成されている。ちなみに SBT は Java ソースが混在したプロジェクトでもよしなにビルドしてくれるので、このままでも動くけど、Scala に変更します。
HogeAndroidProject
├ project
├ target
├ src
│ ├ AndroidTest
│ └ main
│ ├ res
│ ├ java
│ │ └ com.biacco.app.hoge
│ │ └ MainActivity.java
│ └ AndroidManifest.xml
├ build.sbt
︙
となっているのを
HogeAndroidProject
├ project
├ target
├ src
│ ├ AndroidTest
│ └ main
│ ├ res
│ ├ scala
│ │ └ com.biacco.app.hoge
│ │ └ MainActivity.scala
│ └ AndroidManifest.xml
├ build.sbt
︙
に書きかえる。たとえば
$ mv src/main/java src/main/scala
$ mv src/main/scala/com/biacco/app/hoge/MainActivity.java src/main/scala/com/biacco/app/hoge/MainActivity.scala
とか。
さらに、まだ中身が Java コードのままなので Scala のコードに変更する。下のコピペでもいいです。
package com.biacco.app.hoge
import android.app.Activity
import android.os.Bundle
class MainActivity extends Activity with TypedFindView {
/** Called when the activity is first created. */
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
}
}
最後に build.sbt を編集する。Scala のライブラリが膨大で Android の dex を作成する際に 65535 名前空間を超えてしまうので、debug build でも Proguard を有効にする必要がある。それにともなって、Akka とか その他のライブラリについても Proguard 設定をする必要がある。これもコピペで大丈夫です。
name := "HogeApp"
scalaVersion := "2.11.7"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.4" % "test"
libraryDependencies += "com.android.support" % "support-v4" % "23.1.1"
libraryDependencies += "com.android.support" % "support-v13" % "23.1.1"
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.9"
// Override the run task with the android:run
run <<= run in Android
// Activate proguard for Scala
proguardScala in Android := true
// Activate proguard for Android
useProguard in Android := true
// Set proguard options
proguardOptions in Android ++= Seq(
"-ignorewarnings",
"-keep class scala.Dynamic",
"-keep class com.android.support.**",
"-keep class com.typesafe.**",
"-keep class akka.**",
"-keep class scala.collection.immutable.StringLike { *; }",
"-keepclasseswithmembers class * { public <init>(java.lang.String, akka.actor.ActorSystem$Settings, akka.event.EventStream, akka.actor.Scheduler, akka.actor.DynamicAccess); }",
"-keepclasseswithmembers class * { public <init>(akka.actor.ExtendedActorSystem); }",
"-keep class scala.collection.SeqLike { public protected *; }",
"-keep class scala.concurrent.**",
"-keep class scala.reflect.ScalaSignature.**",
"-keep class akka.actor.LightArrayRevolverScheduler { *; }",
"-keep class akka.actor.LocalActorRefProvider { *; }",
"-keep class akka.actor.CreatorFunctionConsumer { *; }",
"-keep class akka.actor.TypedCreatorFunctionConsumer { *; }",
"-keep class akka.dispatch.BoundedDequeBasedMessageQueueSemantics { *; }",
"-keep class akka.dispatch.UnboundedMessageQueueSemantics { *; }",
"-keep class akka.dispatch.UnboundedDequeBasedMessageQueueSemantics { *; }",
"-keep class akka.dispatch.DequeBasedMessageQueueSemantics { *; }",
"-keep class akka.actor.LocalActorRefProvider$Guardian { *; }",
"-keep class akka.actor.LocalActorRefProvider$SystemGuardian { *; }",
"-keep class akka.dispatch.UnboundedMailbox { *; }",
"-keep class akka.actor.DefaultSupervisorStrategy { *; }",
"-keep class akka.event.Logging$LogExt { *; }",
)
// Exclude duplicates
packagingOptions in Android := PackagingOptions(excludes = Seq(
"META-INF/DEPENDENCIES.txt",
"META-INF/LICENSE.txt",
"META-INF/NOTICE.txt",
"META-INF/NOTICE",
"META-INF/LICENSE",
"META-INF/DEPENDENCIES",
"META-INF/notice.txt",
"META-INF/license.txt",
"META-INF/dependencies.txt",
"META-INF/LGPL2.1",
"META-INF/services/com.fasterxml.jackson.databind.Module"
))
Android support library は使わないなら消しちゃってOK。
逆に他のライブラリが使いたい場合には
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.6.3
とか書いてあげる。Maven リポジトリにあれば勝手に解決してくれる。ライブラリがリフレクションとかを使ってる場合は Proguard 設定をしてあげる必要があるかもしれない。
これではれて Scala で Android するプロジェクトができました!
実行
プロジェクトルートディレクトリで sbt コマンドで SBT を起動。
Android エミュレータを起動した状態もしくは Android デバイスを USB デバッグで接続して
> run
でプロジェクトがビルドされて実行される。
これでワイワイ Scala で Android 開発ができます!お疲れ様でした。
Scala で具体的に Android を開発する Tips についてはまたまとめたい(希望的観測)。
とりあえず Java っぽいのをちょっとずつ Scala に置き換えてけばよいと思います。
関数型っぽく書きたい〜〜〜 Java のリスナーとかボイラープレート削りたい〜〜〜 って方は Scala の implicit がいまいちよくわかってなかったけど Android の Listener を小綺麗にかけたらちょっとたのしかったけど黒魔術っぽい話 読んでみてください。ちょっと Scala 感が味わえるかも。
詰まった・引っかかった点・こんなことできないの
UNEXPECTED TOP-LEVEL EXCEPTION / bad class file magic (cafebabe) or version (0034.0000) とか言われるけどなんなん
Android は Java7 までのバイナリしか互換性がないので JDK8 でビルドするとこのエラーを吐きます。javac -version でJDKバージョンを確認してビルドで使われる JDK を 7 以前にしてください。Android 残念…
なんか Run Time にリフレクションとか ClassNotFound で Exception する
Proguard が原因かもしれません。追加したライブラリについて Proguard の設定法がないか調べてみてください。もしくは強引に
-keep class com.piyo.library.** { *; }
とかでもいけるかもしれない。
proguardOptions in Android ++= Seq(
...
"ここに書く"
)
なんか突然ビルド失敗するようになった
ゴミがたまってるか、Proguard のキャッシュが悪さをしているかもしれない。SBT コンソールで
> clean
> proguard:clean
とかしてみる。
build.sbt にライブラリとか書いたけど解決されないだが〜〜〜〜〜〜〜
SBT 起動しっぱなしで build.sbt を編集した場合は
> reload
してください。解決されるはず。
ライブラリ追加したら Duplicate files in copied 〜 とか言ってビルドできなくなったんだけど
Android のビルドツールがクソなので、ライブラリルートからの相対パスが同じファイルがあるとブチギレ始める。クソ。
build.sbt でともかく Exclude する。
IDE とかないの
IntelliJ IDEA がおすすめです。
IntelliJ IDEA に Scala プラグインをインストールして、Import project => import project from external model => SBT で後はいい感じに読み込んでくれるはず。
IntelliJ IDEA 上から Run したい場合は、Run Configuration をする必要がある。
Run Configuration => 一番下の make を削除
=> + ボタンを押して SBT を追加
=> コマンド欄に android:package
でいけます。ただ、IntelliJ に JDK7 の使用を強制する方法が OS 毎に違うし結構めんどうだしうまくいったりいかなかったりするので、実行だけはコンソールの SBT を使うのが楽かもしれない。
Vim でやりたい
やりましょう!
.vimrc とか用意してあります。
テストやりたい
Play Framework 2.4 Scala + Specs2 + Mockito + Guice DI でテスト素人がテストに挑戦した話 を見てみてください
おしまい。