トップ «前の日記(2009-09-09) 最新 次の日記(2009-09-28)» 編集

じじぃの日記、ツッコミ可

初心者が書いた OCaml 入門
Spotlight tips サイト内リンク集
1970|01|02|
2003|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|

2009-09-16 [長年日記]

% [Scala][Android] Scala on Android

Scala で Android の世界に "こんにちは" をするまで。 なかなか自分の環境にズバリな情報が見付けられず試行錯誤したが、ようやく何とかなったのでまとめ。

とりあえず参考にしたページは以下。

ref. http://robey.lag.net/2009/01/19/scala-on-android.html

ref. http://www.scala-lang.org/node/160

ref. http://chneukirchen.org/blog/archive/2009/04/programming-for-android-with-scala.html

Thanks these authors.

% [Scala][Android] Scala on Android - その1 - Android 用 scala-library.jar を作成する

最初に必要なもの。バージョンは今回わしが使ったもので、WindowsXP SP3 (cygwin 併用) で作業している。 あと空白入りパスでハマるのは嫌なので、あらかじめ全て空白が入らない場所に置いてある。

ProGuard はおそらく省略できそうなんだが、うちの貧弱マシンだといろいろと問題があって、これを使ったやり方しかうまくいかなかったので。 まあ、どっちにしろ使った方ができ上がるファイルは小さくなるのでおすすめ。 Android SDK はもたもたしてる間に 1.6 が出てしまったが、とりあえず 1.5 で。 1.6 で手順を変更する必要がある場合は後日フォローしたい。

Eclipse は貧弱マシンなので (というかそもそもあんまり好きじゃないし) 使わない。

  • JDK (1.6.0_16)
  • Apache Ant (1.7.1)
  • Scala (2.7.6.final)
  • ProGuard (4.4)
  • Android SDK (1.5)

まず Scala のソースを SVN か リポジトリスナップショット (ダウンロードページにある) で取得。 プリビルド版の Scala の src には android-library が含まれていないので注意。

% wget http://www.scala-lang.org/downloads/distrib/files/scala-2.7.6.final-sources.tgz
% tar zxf scala-2.7.6.final-sources.tgz

次に既存の scala-library.jar をどこかに展開し、android 用の部分だけ差し換える。 参考にしたページでは scala-library のソースを上書きして丸ごとビルドしろと書かれているが、 うちのボロマシンでは無理なのでビルド済みの jar をいじる。 実際に scala-android のディレクトリを見てみればわかるが、scala-library との差分はファイル二つだけ。

% mkdir workdir; cd workdir
% jar xf ${SCALA_HOME}/lib/scala-library.jar
% export SRC=${scala-src-path}/src/android-library
% scalac ${SRC}/scala/ScalaObject.scala
% scalac ${SRC}/scala/reflect/ScalaBeanInfo.scala
% jar cf ../scala-android.jar *

展開した場所で scalac を実行すればうまいこと該当するクラスファイルだけが上書きされるはず。 で、それを再度 jar で固めれば終了。

今回はこれを ${SCALA_HOME}/android-lib/scala-android.jar という場所に置くことにする。

% [Scala][Android] Scala on Android - その2 - プロジェクトを作成する

プロジェクトの作成に関してはマニュアルが日本語化されているので問題無いだろう。

ref. http://developer.android.com/intl/ja/guide/developing/other-ide.html

とりあえず例としてはこんな感じ。 あらかじめ Android SDK の tools ディレクトリを PATH に含めてある。

% android.bat create project --target 2 --path ./hello_scala --activity HelloScala --package com.azito.jijixi

こうすると、Android 1.5 用の (android.bat list target 参照) プロジェクトファイルが ./hello_scala 以下に作成される。 activity というのは Android アプリケーションのエントリーポイントになるクラス。 上記のように実行すると src/com/azito/jijixi/HelloScala.java というファイルができているはず。 まあ今回は必要無いのでさくっと消しちゃっても良い。

プロジェクトディレクトリに移り、build.xml に以下のパッチをあてる。 ほんとは完全に別ファイルにして build.xml から読み込むだけにしたかったんだけど、Ant の知識が足りなくて断念。

% cat ${where-place}/build.xml.diff
--- build.xml.orig	2009-09-15 19:35:12.781250000 +0900
+++ build.xml	2009-09-16 17:11:24.843750000 +0900
@@ -58,4 +58,89 @@
          targets are used.
     -->
     <setup />
+
+    <!-- for Scala settings. -->
+    <!-- set properties for your evnvironment. -->
+    <property name="scala-home"
+        value="c:/develop/scala" />
+    <property name="scala-android-library"
+        value="${scala-home}/android-lib/scala-android.jar" />
+    <property name="proguard-home"
+        value="c:/develop/proguard" />
+
+    <!-- override targets in platforms/android-1.5/templates/android_rules.xml -->
+    <target name="compile" depends="dirs, resource-src, aidl">
+        <javac encoding="ascii" target="1.5" debug="true" extdirs=""
+            includes="**/*.java"
+            destdir="${out-classes}"
+            bootclasspathref="android.target.classpath">
+            <src path="${source-folder}"/>
+            <src path="${gen-folder}"/>
+            <classpath>
+                <pathelement path="${scala-android-library}"/>
+                <fileset dir="${external-libs-folder}" includes="*.jar"/>
+                <pathelement path="${main-out-classes}"/>
+            </classpath>
+        </javac>
+
+        <taskdef resource="scala/tools/ant/antlib.xml">
+            <classpath>
+                <pathelement path="${scala-home}/lib/scala-compiler.jar"/>
+                <pathelement path="${scala-home}/lib/scala-library.jar"/>
+            </classpath>
+        </taskdef>
+
+        <scalac force="changed" deprecation="on"
+            includes="**/*.scala"
+            destdir="${out-classes}"
+            bootclasspathref="android.target.classpath">
+            <src path="${source-folder}"/>
+            <classpath>
+                <pathelement path="${scala-android-library}"/>
+                <fileset dir="${external-libs-folder}" includes="*.jar"/>
+                <pathelement path="${main-out-classes}"/>
+            </classpath>
+        </scalac>
+    </target>
+
+    <property name="stripped-jar-file"
+        value="${out-classes-location}/../classes.stripped.jar" />
+
+    <target name="dex" depends="proguard">
+        <echo>Converting compiled files and external libraries into ${out-folder}/${dex-file}...</echo>
+        <apply executable="${dx}" failonerror="true" parallel="true">
+            <arg value="--dex" />
+            <arg value="--output=${intermediate-dex-location}" />
+            <fileset file="${stripped-jar-file}" />
+        </apply>
+    </target>
+
+    <target name="proguard" depends="compile">
+        <taskdef resource="proguard/ant/task.properties"
+            classpath="${proguard-home}/lib/proguard.jar" />
+
+        <fileset id="external-jars"
+            dir="${external-libs-folder}" includes="**/*.jar" />
+        <pathconvert property="external-jars" refid="external-jars" />
+
+        <proguard>
+            <injar path="${out-classes-location}" />
+            <injar path="${scala-android-library}:${external-jars}"
+                filter="!META-INF/MANIFEST.MF,!library.properties" />
+            <outjar path="${stripped-jar-file}" />
+            <libraryjar path="${android-jar}" />
+            <keep name="**.R" />
+            <keep name="**.R$*" />
+            <keep name="org.xml.sax.EntityResolver" /> <!-- BK :-p -->
+            -dontwarn
+            -dontoptimize
+            -dontobfuscate
+            -keep public class * extends android.app.Activity
+        </proguard>
+    </target>
+
+    <!-- for debug. -->
+    <target name="show-prop">
+        <echoproperties />
+    </target>
 </project>
% cd hello_scala
% patch < ${where-place}/build.xml.diff
patching file build.xml

パッチをあてたら必要に応じて新しい build.xml の以下の箇所を編集。 まあパッチの方を直しちゃった方が早いかもしれないが、その辺は好き好きで。

<property name="scala-home"
    value="c:/develop/scala" />
<property name="scala-android-library"
    value="${scala-home}/android-lib/scala-android.jar" />
<property name="proguard-home"
    value="c:/develop/proguard" />

scala-home は Scala をインストールしてある場所。 scala-android-library は先に作った Android 用 scala-library.jar の場所。 proguard-home は ProGuard をインストールしてある場所。 他の場所については基本的にいじる必要が無いようになってるはず。 (だが、知識不足は否めないのであまり信用しないように)

% [Scala][Android] Scala on Android - その3 - Scala でソースを書いてビルドする

src/com/azito/jijixi/HelloScala.java を消して代わりに src/com/azito/jijixi/HelloScala.scala を作成。 とりあえず以下のような感じで。 ProGuard が必要無いクラスを消してしまうので、ある程度 Scala 的な操作を入れないと出来上がりがつまらなくなる。 ということで、ちょっと無駄に List とか使ってある。

% cat src/com/azito/jijixi/HelloScala.scala
package com.azito.jijixi

import _root_.android.app.Activity
import _root_.android.os.Bundle
import _root_.android.widget.TextView

class HelloScala extends Activity {
   override def onCreate(savedInstanceState: Bundle) {
      super.onCreate(savedInstanceState)
      val tv = new TextView(this)
      val lst = List("Hello", "Android,", "it's", "me,", "Scala!")
      tv.setText(lst mkString(" "))
      setContentView(tv)
   }
}

できたらこれをビルド。

% ant debug
Buildfile: build.xml
    [setup] Project Target: Android 1.5
    [setup] API level: 3

dirs:
     [echo] Creating output directories if needed...
    [mkdir] Created dir: c:\home\jijixi\tmp\android_test\hello_scala\bin\classes

resource-src:
     [echo] Generating R.java / Manifest.java from the resources...

aidl:
     [echo] Compiling aidl files into Java classes...

compile:
    [javac] Compiling 1 source file to c:\home\jijixi\tmp\android_test\hello_scala\bin\classes
   [scalac] Compiling 1 source file to c:\home\jijixi\tmp\android_test\hello_scala\bin\classes
   [scalac] Element 'c:\home\jijixi\tmp\android_test\bin\classes' does not exist.

proguard:
 [proguard] ProGuard, version 4.4
 [proguard] Reading program directory [C:\home\jijixi\tmp\android_test\hello_scala\bin\classes]
 [proguard] Reading program jar [C:\develop\scala\android-lib\scala-android.jar] (filtered)
 [proguard] Reading library jar [C:\develop\android-sdk\platforms\android-1.5\android.jar]
 [proguard] Note: You're ignoring all warnings!
 [proguard] Preparing output jar [C:\home\jijixi\tmp\android_test\hello_scala\bin\classes.stripped.jar]
 [proguard]   Copying resources from program directory [C:\home\jijixi\tmp\android_test\hello_scala\bin\classes]
 [proguard]   Copying resources from program jar [C:\develop\scala\android-lib\scala-android.jar] (filtered)

dex:
     [echo] Converting compiled files and external libraries into bin/classes.dex...

package-resources:
     [echo] Packaging resources
 [aaptexec] Creating full resource package...

debug:
[apkbuilder] Creating HelloScala-debug.apk and signing it with a debug key...
[apkbuilder] Using keystore: C:\Documents and Settings\jijixi\.android\debug.keystore

BUILD SUCCESSFUL
Total time: 46 seconds

エラーが出なければ完了。 一応 ProGuard が出力したファイルの中身なんかを見てみると、

% jar tf bin/classes.stripped.jar
com/azito/jijixi/HelloScala.class
com/azito/jijixi/R$attr.class
com/azito/jijixi/R$layout.class
com/azito/jijixi/R$string.class
com/azito/jijixi/R.class
scala/$colon$colon.class
scala/Array$Array0$class.class
scala/Array$Array0.class
scala/Array$ArrayLike.class
scala/Array.class
scala/collection/mutable/Buffer$$anonfun$$plus$plus$eq$1.class
scala/collection/mutable/Buffer$class.class
scala/collection/mutable/Buffer.class
scala/collection/mutable/CloneableCollection$class.class
scala/collection/mutable/CloneableCollection.class
scala/collection/mutable/ListBuffer$$anon$1.class
scala/collection/mutable/ListBuffer$$anonfun$equals$1.class
scala/collection/mutable/ListBuffer.class
scala/Collection$class.class
scala/Collection.class
scala/compat/Platform$.class
scala/Function1$class.class
scala/Function1.class
scala/Iterable$class.class
scala/Iterable.class
scala/Iterator$$anon$17.class
scala/Iterator$class.class
scala/Iterator.class
scala/List$$anon$2.class
scala/List$.class
scala/List.class
scala/MatchError.class
scala/Math$.class
scala/Nil$.class
scala/PartialFunction$class.class
scala/PartialFunction.class
scala/Product$class.class
scala/Product.class
scala/Product2$class.class
scala/Product2.class
scala/RandomAccessSeq$class.class
scala/RandomAccessSeq$Mutable$class.class
scala/RandomAccessSeq$Mutable.class
scala/RandomAccessSeq.class
scala/runtime/BoxedArray$AnyIterator.class
scala/runtime/BoxedArray.class
scala/runtime/BoxedObjectArray.class
scala/runtime/BoxedUnit.class
scala/runtime/BoxesRunTime.class
scala/runtime/Nothing$.class
scala/runtime/ScalaRunTime$.class
scala/Seq$class.class
scala/Seq.class
scala/StringBuilder$.class
scala/StringBuilder.class
scala/Tuple2.class

List を使っただけでこんなに関係するのか…… と思いつつも、元の jar が 3.7MB くらいあるのが 60KB 弱にまで減るんだからありがたいことだ。 最終的にはこれがさらに圧縮されて 18KB ほどになる (apk ファイル)。 ProGuard を使わない場合は 800KB くらいらしい。 (うちでは dx が謎のエラーで死んでしまうので実際には試せていないが)

% [Scala][Android] Scala on Android - その4 - エミュレータで実行

省略。 すでに紹介したが、

ref. http://developer.android.com/intl/ja/guide/developing/other-ide.html#Running

こちらのページの「アプリケーションの実行」という項を見ればとてもよくわかるはず。 インストールするとアプリケーションタブに HelloScala というアイコンが現れているはずなので、 そいつを起動して "Hello Android, it's me, Scala!" と表示されれば成功。

お名前:
E-mail:
コメント:

トップ «前の日記(2009-09-09) 最新 次の日記(2009-09-28)» 編集

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com