Kotlin/NativeでAndroidのNativeメソッドを実装してみた

この記事はAndroid Advent Calendar 20185日目21日目の記事です。

概要

普段C++/Cで実装しているAndroidのNativeメソッドをKotlin/Nativeを使って実装できないか試してみたというお話です。

サンプル

GitHubにリポジトリを上げました。 Android Studio 3.2.1上でビルドできるのを確認しました。

プロジェクトの構成は

  • app: Androidプロジェクト
  • greeting: Kotlin MultiPlatformプロジェクト。ここでJNI関数を実装

です。

Dynamic Libraryのビルド

ドキュメントを参考に設定できました。 fromPreset関数の第一引数にpresets.androidNativeArm32(64bit版をビルドしたい場合はpresets.androidNativeArm64)を設定し、 ブロックの中でcompilations.main.outputKindsdynamicに指定するとAndroid向けのsoファイルが生成できます。

plugins {
    id 'kotlin-multiplatform'
}

kotlin {
    targets {
        fromPreset(presets.androidNativeArm32, 'arm32') {
            compilations.main.outputKinds('dynamic')
        }
    }
    // (省略)
}

JNI関数の定義

Kotlinの関数をトップレベルのC関数として定義するには以下のように@CNameアノテーションを利用します。 また、関数のシグネチャーは後述するPlatform Libraryの定義を参考にしました。

ちなみにJNIEnvVar, jobject, jstringなどはすべてPlatform Library(platform.androidパッケージ)に定義されています。

@CName("Java_io_github_tsuyosh_kotlinnativejni_Greeting_say")
fun Java_io_github_tsuyosh_kotlinnativejni_Greeting_say(
    env: CPointer<JNIEnvVar>?,
    obj: jobject?,
    name: jstring?
): jstring? {
    // (省略)
}

型の相互変換

INREROP.mdを読んで雰囲気で理解しただけですがこんな感じで相互変換しました。

jstringString

Kotlin/Nativeのサンプルコード等を見ながら変換用のユーティリティークラスを作りました。

(追記) 実はJNIのなんとかUTF関数が扱う文字コードはmodified UTF-8であることが発覚したので修正する予定ですorz

jbooleanBoolean

typealias jboolean = UByte

と定義されています。trueが1.toUByte()でfalseが0.toUByte()になります。

jintInt

typealias jint = Int

と定義されていますので特に変換処理はいらないです。

感想

C言語由来の型を扱うのに慣れるまで時間がかかる

INTEROP.mdを読むのが辛かった... この辺はいずれまとめたいと思います(忘れてなければ)

Platform Libraryのドキュメント少ない

ドキュメントを見ても概要しか書いていないのでどんなAPIがあるのか調べるのに苦労しました。。。

わかったことをまとめるとPlatform Libraryの定義が知りたい場合はkotlin-nativeの配布パッケージのklib/platform/<target>以下を見て、

$ <KOTLIN_NATIVE_DIR>/bin/klib contents <KOTLIN_NATIVE_DIR>/klib/platform/android_arm32/android

の様に確認するのが手っ取り早いと思います。

(Platform Libraryはよく使われるライブラリをcinteropを使わなくても扱えるようにしただけという感じなのかも)

まだIDEの補完機能が使えないクラスがあるのが辛い

kotlinx.cinteropplatform以下のパッケージがまだコード補完できないし、型の定義を見ることもできません。 ひょっとしてCLionを使えばいいのかもしれないけどAndroid Studioでも対応して欲しい。。

Kotlin/Nativeのターゲットにandroid-x86がない

Androidエンジニアが普段使っているだろうx86ベースのエミュレーターでは確認できないので実機が必須です。 (armベースのエミュレーターは実用的じゃないので除外) android x86もターゲットに加えてほしい🙏

現状ではC++言語のライブラリが利用できない

公式サイトを見るとC++のライブラリを利用できない状態です。 JNIの組み込み関数もC言語方式の書き方で利用しないといけません。

On the other hand, Kotlin/Native supports interoperability to use existing libraries directly from Kotlin/Native:


- static or dynamic C Libraries
- C, Swift, and Objective-C frameworks

今後の展望

今回のサンプルはJava/Kotlinでも実装できる簡単なものでしたがcinteropツールを使えばC言語のライブラリを使うこともできると思います。 機会があればその辺も触って感想を書いてみたいと思います。 また、公開するシンボルをJNI関連に絞れなかったのでビルドオプションを直したいです。