Kotlinからネイティブコードを呼んでAndroidで動かす

totechite.hatenablog.com 前回は共有ライブラリをつくって終わりました。

今回はAndroidで動かす編です。

はじめに

環境

  • Windows10 Home
  • Android Studio3.2.1
  • Kotlin 1.3.2

この記事のゴール

  • 共有ライブラリをAndroid/Kotlinで利用できるようになる

やることの流れ

  1. .soファイルの配置
  2. Kotlinでネイティブ関数を呼ぶ
  3. アプリで動かしてみる .soファイルの配置

.soファイルの配置

JavaやKotlinではSystem.loadLibrary()を使って共有ライブラリを読み込むことでネイティブコードを利用することができる。
JNIを利用しコンパイルした共有ライブラリをAndroidProjectフォルダ内に配置する。

まずAndroidStudioで新規プロジェクトを適当に立ち上げ、<ProjectName>\app\src\mainjniLibsフォルダを新規作成する。(<ProjectName>は適宜読み替えてほしい)
作成したら jniLibs\arm64 jniLibs\arm64-v8a jniLibs\armeabi jniLibs\armeabi-v7a jiniLibs\x86と各プラットフォームのフォルダをjniLibsディレクトリ下に作成し、前回生成した.soファイルをそれぞれのフォルダにコピペする。
f:id:totechite:20190106225027p:plain

Kotlinでネイティブ関数を呼ぶ

共有ライブラリを読み込むため、クラスの初期化メソッドでSystem.loadLibrary()をする。
実行時に前節で準備した、各プラットフォームの.soファイルを読み込んでいる様子。

class MainActivity : AppCompatActivity() {

    companion object {
        init {
            System.loadLibrary("greetings")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
           // anything
    }

}

System.loadLibrary()の引数には文字列を取り、利用する共有ライブラリの名前を入れる。今回はlibgreetings.soなので"greetings"とした。

次に、読み込んだ共有ライブラリに定義されたネイティブ関数を利用するため、Kotlinコードに外部関数を定義する。
適当に呼び出し専用のクラス等をつくり、その中で外部関数と別に、間接的に呼び出させるためにpublicなメソッドを用意してみる。

class RustGreetings {

    private external fun greeting(to: String): String;

    fun sayHello(to: String): String {
        return greeting(to);
    };

    .........

}

これにより外部関数はネイティブ関数のJNIにバインドされ、Kotlin側からはいい感じに使えるらしい…(?) なるほどわからん

アプリで動かしてみる

本当に動くのかみてみよう。

新規のAndroidプロジェクトを作成し、minSdkVersionは26にする。
MainActivityがonCreateした際、
TextViewにRustGreetings.sayHello()のStringを表示するアプリをつくってみる。

class MainActivity : AppCompatActivity() {

    companion object {
        init {
            System.loadLibrary("greetings")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val g = RustGreetings()
        val hello: String = g.sayHello("Rust!")
        findViewById<TextView>(R.id.rustgreeting).text = hello
    }
}

sayHello()(もとい呼び出し先のgreeting())は文字列を取り、文字列を返す関数で、return "Android meets" + arg ;みたいな文字列が返る。
なので今回はAndroid meets Rust!という文字列が表示されれば成功だ。

するとこんな画面になる。
f:id:totechite:20190106233547p:plain
どうやらちゃんと動いてる様子。やったね

フィボナッチ数をKotlinとネイティブ関数で求める

ベンチマークとかで親の顔より見るあれです。
プログラムの実行時間を計測して表示させるアプリをつくってみましょう。

RustGreeting.kt

class RustGreetings {

    private external fun greeting(to: String): String;

    private external fun fibo(to: Int): Int;

    fun sayHello(to: String): String {
        return greeting(to);
    };

    fun rsfibo(to: Int): Long {
        val elapsed = measureTimeMillis {
            fibo(to);
        }
        return elapsed
    }

    fun ktfibo(to: Int): Long {
        val elapsed = measureTimeMillis {
            ktfibo_re(to)
        }
        return elapsed
    }

    fun ktfibo_re(n: Int): Int {
        if (n <= 1) {
            return n
        } else {
            val x = n - 2
            val y = n - 1
            return ktfibo_re(x) + ktfibo_re(y)
        }
    }

}

rsfibo()とktfibo()はそれぞれ実行時間を返す。

MainActivity.kt

class MainActivity : AppCompatActivity() {

    companion object {
        init {
            System.loadLibrary("greetings")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val g = RustGreetings()
        val hello: String = g.sayHello("Rust!")
        findViewById<TextView>(R.id.rustgreeting).text = hello

        val button: Button = findViewById(R.id.excute) as Button
        button.setOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                val fibo_num = 
Integer.parseInt(findViewById<TextView>(R.id.editfibo).text.toString())
                val k = g.ktfibo(fibo_num)
                findViewById<TextView>(R.id.kotlinfibo).text = k.toString() + "ms"
                val r = g.rsfibo(fibo_num)
                findViewById<TextView>(R.id.rustfibo).text = r.toString() + "ms"
            }
        })
    }

}

ボタンのonClickイベントでfiboが実行される感じです。

出来上がったものはこちら

f:id:totechite:20190107004026j:plain

fibo(40)だと4倍ぐらいはやい

総括

知見がとぼしすぎて、はやーい!すごーい!以外の感想がない

使い所だけど、GoogleのAndroidNDKのサイトにCGとか機械学習の推論につかう例があってなるほどと思った。

なにかうまいこと応用したいですね