Retropiler: AndroidでJava8の機能を使うもう一つの方法
FUJI Goro (@__gfx__), SpeeeKaigi 2017 Early
自己紹介
- 藤吾郎(@__gfx__)
- 技術顧問(主にモバイルアプリ)
- 最近はAndroid, Ruby on Rails, Reactあたりをやってます
- 最近熱中していることは自炊
- 「短時間(平日の夜なので)、栄養バランスよし、おいしい」を目指したタイムアタックゲームとして楽しんでいる
Retropiler
Install
build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'io.github.retropiler:retropiler-gradle-plugin:0.0.2'
}
}
apply plugin: 'io.github.retropiler'
Retropiler
- retro + compiler からの造語
- Android用ビルドツールのプラグイン
- 古いAndroid向けにJava8の標準ライブラリの機能をバックポートする
古いAndroid向けにJava8の標準ライブラリの機能をバックポートする??🤔
Retropilerができること
List<String> array = Arrays.asList("foo", "bar");
array.forEach((item) -> {
System.out.println(item);
});
- 7.0より前のAndroid OSで実行すると例外になる
- この
Iterable#forEach()
がJava8で追加されたメソッドで、Android 7.0でやっと利用可能になったAPIなので
🤔
Android OSの開発言語
- よく「AndroidアプリはJavaで書く」とか「AndroidのアレはJavaではない」とかいわれる
- それは、AndroidがJVMのバイトコードを変換した別のバイトコード(dex)を実行するから
- さらに、Javaの標準ライブラリがOracle JDK由来ではないのでJDKとは挙動が微妙に違ったりする
- なので「ほぼJava」くらいのイメージ(個人の印象です)
Androidアプリが起動するまで
- AndroidアプリをJavaで書く
- JDKの
javac(1)
でJVMバイトコードにコンパイルする - Android SDKの
dx(1)
でJVMバイトコードをdexに変換する - dexや画像リソースをまとめてzipで圧縮して署名してapkにする
- apkをAndroidデバイスにインストールする
- アプリを起動すると、OSがdexを読み込んで実行する
「AndroidにおけるJavaのバージョン」 #1
- JDKが含むのはJavaの「言語機能(=構文)」と「標準ライブラリ」
- 「Java8」という場合、その言語機能(たとえばラムダ式)と標準ライブラリ(たとえばStream API)を一緒に言及されるが、この二つの区別は必ずしも自明ではない
- 言語機能の一部はretrolambdaなどのツールでバイトコードを変換することでAndroidでも利用可能
「AndroidにおけるJavaのバージョン」 #2
- Androidは OSはJava標準ライブラリをバンドルしている
- apkはアプリケーションコード+サードパーティライブラリのみバンドルしており、標準ライブラリはバンドルしていない
- つまり、Android OSがバンドルしている標準ライブラリがJDKのどのバージョンに近いかというのが「AndroidにおけるJavaのバージョン」の意味のひとつ
Retropilerを正確にいうと
Java8で書いたJVMバイトコードを編集し、Java8の標準ライブラリの参照箇所を、相当する独自実装のクラスライブラリに置き換えるツール
…のproof of concept
バイトコードの編集 / Bytecode Weaving
- Javaの世界でバイトコードを編集する技術で、わりと枯れている(!)
- メタプログラミングの一種
- 実行時メタプログラミングであるリフレクションと比べると自由度が非常に高く実行時のオーバーヘッドもない
- そのかわり開発・保守が難しい
- 「スーパークラスを置き換える」「メソッドを削除したり追加したりする」「メソッドの中身を書き換える」などが可能
Retropilerができること
List<String> array = Arrays.asList("foo", "bar");
array.forEach((item) -> {
System.out.println(item);
});
- Retropilerにより実行可能なバイトコードに変換されるため、これがAndroid 7.0以前でも実行できるようになる
デモ
なぜこれができるか
これを…
array.forEach((item) -> {
System.out.println(item);
});
こうじゃ!
import io.github.retropiler.runtime.JavaUtilIterable;
JavaUtilIterable.forEach(array, (item) -> {
System.out.println(item);
});
※ 実際にはlambda式はretrolambdaによってdownpileされる
何ができるか
- 「ソースコードを自由に書き換えて実現できる」ものならほぼ実現できる
- Stream API / Optionalとかも技術的にはできそう
- 見た目のシグネチャを変えられるわけではないので、Android FrameworkのAPIが
Optional<T>
を返すようにできるという意味ではない - とはいえ
Optional<T>
を使えるとコードの書き方がわりと変わるので面白そうではある
- 見た目のシグネチャを変えられるわけではないので、Android FrameworkのAPIが
リポジトリ