1. Qiita
  2. 投稿
  3. Android

Retropiler: AndroidでJava8の機能を使うもう一つの方法

  • 1
    いいね
  • 0
    コメント

Retropiler: AndroidでJava8の機能を使うもう一つの方法

FUJI Goro (@__gfx__), SpeeeKaigi 2017 Early


自己紹介

  • 藤吾郎(@__gfx__)
  • 技術顧問(主にモバイルアプリ)
  • 最近はAndroid, Ruby on Rails, Reactあたりをやってます
  • 最近熱中していることは自炊
    • 「短時間(平日の夜なので)、栄養バランスよし、おいしい」を目指したタイムアタックゲームとして楽しんでいる

Retropiler

https://github.com/gfx/retropiler

Screen Shot 2017-02-11 at 11.42.10.png


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なので

🤔


Androd 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のどのバージョンに近いかというのが「Anroidにおける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);
});

  • 7.0より前のAndroid OSには含まれていないので実行すると例外になる
  • この Iterable#forEach() がJava8で追加されたメソッドで、Android 7.0でやっと利用可能になったAPIなので

デモ


なぜこれができるか

これを…

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> を使えるとコードの書き方がわりと変わるので面白そうではある

リポジトリ

https://github.com/gfx/retropiler