2013年1月4日金曜日

Androidでjava1.7をどうしても使いたい場合はこうしたらええで

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Androidでjava1.7は使えないらしい、けど、何とかする方法はありました。
結論からいうと別においしくない。

何故Androidでjava1.7が使えないか


javaをコンパイルして生成されるclassファイルの中には、「オレ、このバージョンでコンパイルされてるんでヨロ」という情報が入ってます。んでjavaの実行環境側でclassファイルをロードする時にサポートしているバージョンでコンパイルされたclassファイルかどうかをチェックして、もしサポート外ならエラーを投げる様になってます。

Androidだとclassを生成後、dexファイルに変換する所で以下の様に怒られます。
bad class file magic (cafebabe) or version (0033.0000)
「このきたならしい阿呆がァーッ!!」
classファイルのversionが間違ってると言われてますね。
0x33は51です。java1.7でコンパイルするとclassファイルのバージョンは51になるんすねー。1.6なら50。

で、どうする


dexで怒られるのでAndroidで使えないのは分かりました。じゃあどうしたらええのか。長いjavaの歴史、互換性に関しても色々ノウハウあるんちゃうん、という事でありました。下位互換のオプションが。

Java7シンタックスで レガシーコードを快適メンテナンス
とか
javac - Java プログラミング言語コンパイラ
を見ますと、javacに-target付けてコンパチなclassファイルが作れるようですね

Eclipse+ADTでは無理


結論から言うとEclipseでは無理です。
設定で以下の様に生成するclassファイルのバージョン指定とか出来ますが、



ADTに怒られます。

Android requires compiler compliance level 5.0 or 6.0. Found '1.7' instead. Please use Android Tools > Fix Project Properties.

無理です。

antならいける


antではどうでしょうか。

Android用のbuild.xmlを見る


とりあえずANDROID_HOME/tools/ant/build.xmlの-compileタスクを見てみます。このbuild.xmlはandroidコマンドなどで生成したAndroidプロジェクトに生成されるbuild.xmlでimportされているAndroidアプリケーションをビルドする為のタスクが詰まっているファイルです。
    <target name="-compile" depends="-build-setup, -pre-build, -code-gen, -pre-compile">
        <do-only-if-manifest-hasCode elseText="hasCode = false. Skipping...">
            <!-- merge the project's own classpath and the tested project's classpath -->
            <path id="project.javac.classpath">
                <path refid="project.all.jars.path" />
                <path refid="tested.project.classpath" />
            </path>
            <javac encoding="${java.encoding}"
                    source="${java.source}" target="${java.target}"
                    debug="true" extdirs="" includeantruntime="false"
                    destdir="${out.classes.absolute.dir}"
                    bootclasspathref="project.target.class.path"
                    verbose="${verbose}"
                    classpathref="project.javac.classpath"
                    fork="${need.javac.fork}">
                <src path="${source.absolute.dir}" />
                <src path="${gen.absolute.dir}" />
                <compilerarg line="${java.compilerargs}" />
            </javac>
~~略~~

javacタスクの所にtargetというのがありますね。
値は${java.target}という変数です。
${java.target}の定義を見ると
    <property name="java.target" value="1.5" />
    <property name="java.source" value="1.5" />

1.5になってますねー。
java.sourceの方はjavaファイルがどのバージョンかを表すオプション-sourceに渡す値です。こちらも1.5になっています。

これらを以下の様にするとどうでしょうか
    <property name="java.target" value="jsr14" />
    <property name="java.source" value="1.7" />

antでビルドしてテスト走らしてみる


上の設定にした状態で、以下のテストコードをBuild&Runしてみます。
ちなみにJUnit4 works on Androidあたりを参考にしてjunit4に対応させています。
    @Test
    public void Itemテーブルにレコードを1件追加する(){
        String moge = "ふがふが";
        switch(moge){
        case "ふがふが":
            Log.d("tag", "hogeeeeee!");
            break;
        case "moge":
            Log.d("tag", "moge");
            break;
        default:
            Log.d("tag", "あれえ");
        }
    }

はいビルド&実行
ant debug installd
ant test

キタ━━━━(゚∀゚)━━━━!!
I/System.out(13216): org.junit.runners.Suite
I/TestRunner(13216): started: Itemテーブルにレコードを1件追加する(jp.dip.sys1.yagi.amp.sample.dao.ItemDaoBaseTest)
D/tag     (13216): hogeeeeee!
I/TestRunner(13216): finished: Itemテーブルにレコードを1件追加する(jp.dip.sys1.yagi.amp.sample.dao.ItemDaoBaseTest)

いけましたね。

ANDROID_HOME/tools/ant/build.xmlをいじるのは嫌だよね


基本的にANDROID_HOME配下のヤツをいじって何かに対応するのは緊急の場合を除いてしたくないっすね。もろに環境依存だし。つーことで、なんとかANDROID_HOME/tools/ant/build.xmlのプロパティを外部からいじれねーかと、もちろんいじれます。
ant debug -Djava.target=jsr14 -Djava.source=1.7

こうです。-Dオプションです。debugの所はreleaseとかinstrumentでももちいけますです。これでjava.targetとかが書き換えられます。

終わりに


java1.7の機能でめぼしいのってtry-with-resourcesと例外のマルチキャッチくらいだなーと思ってるけどjsr14では

try-with-resourcesは使えないようなので

なんだかなーと。(try-with-resourcesはAutoCloseableというjava1.7で追加されたinterfaceで実現しているので当然1.6以前には存在しないから無理ぽという事。これ以外でもjava1.7で追加されたAPIとかはもち使えないと思う。)
あとEclipseで使えないので実用性はあんまりないなーと。
どーしても使いたい、という場合向けだけど微妙。

以上

1 件のコメント: