いさぽん部屋(isapon.com)

ゲーム系プログラマによる特に方針のないブログ。技術系とカレー、ラーメンネタ多めだったはずが、最近はダイエットネタ多め。

Android 5.0 (ART) NDK で CallObjectMethod や GetStaticMethod で落ちる

Android NDK でプログラムを組んでいたら、新規に実装した部分が4系までは動いていたものが、Android 5.0 ではクラッシュするようになった。結論から言えば「今までがたまたま動いていた」という感じ。

FindClass がクラスを見つけられない

com.isapon.MyClass を検索する場合 com/isapon/MyClass のように、"."を"/"に置き換えて指定するわけですが、これを com.isapon.MyClass のように "." のままでも今まではなんとなく動いていましたが、ARTでは失敗になります。

つまり

誤)

env->FindClass("java.lang.ClassLoader")

正)

env->FindClass("java/lang/ClassLoader")

気を付けないとうっかり "." で書いてしまうことがあるので要注意です。

logcat に CallObjectMethodV が出る。

art::CheckJNI::CallObjectMethodV(_JNIEnv*, _jobject*, _jmethodID*, std::__va_list)

logcat にこんな感じのログが流れたら、CallXXXXXMethodで指定する型が間違っている。例えば上記のエラーはJava側が戻り値voidのメソッドに対してCallObjectMethodを呼び出したため。本来ならCallVoidMethodを呼ばなければならない。

つまり、JNI側のメソッド呼び出しで指定した戻り値の型と、実際のJavaの戻り値が違うために発生したエラーです。

例えばJava側にこんなメソッドがあったとします。メソッドの中身はなんでも良いのですが、気を付ける点はあくまでも戻り値がvoidであるということ。

static void resetFlags(){ /*中身はてけとー*/ }

正しくは次のように、「VoidMethod」で呼び出します。まぁ当たり前ですが。

env->CallVoidMethod(clz, method)

ただ、dalvikの場合、次のように戻り値の指定が間違っていても呼び出せます。また、この時は必ず戻り値には null が返されます。

env->CallObjectMethod(clz, method)

Googleの開発者向けの説明を見ると、戻り値の型が違っても大丈夫というのが一応本来の仕様で、ARTにはこの仕様に互換性の不具合があると書いてあります。つまり、後者の誤った書き方でも本来問題が無いということらしいです。

が、これは普通に考えると後者はプログラマーのウッカリがそのまま動いている状態なのでARTの仕様のほうがありがたいところです。

GetStaticMethod で落ちる

JNIの場合、NewGlobalRefでグローバル参照を増やさない限りは取得したハンドルはJNIEnv::DettachThreadされると使えなくなる可能性があるのが一般的な仕様です。これは単にVM内でガベージコレクションが発生することでメモリの位置が変わるから。というのが大きな理由です。

が、dalvikではどうやらjclassに関してはガベージコレクションの対象にならないのか、DettachThreadをしようが使い続けることが可能です。

これもやはり、dalvikのメモリ管理が「たまたまそういうものだった」というものなので、Attachし直したら、再度取得しなおしましょう。jclassをNewGlobalRefするというのも考えられますが、ガベージコレクションのことを考えるとなるべくNewGlobalRefを行わないほうが良さそうです。

まとめ

Googleのページを読む限りでは「dalvikと一部互換性に問題がある」という書き方をしています。確かに互換性という意味では問題ではありますが、どちらかというと「よりしっかりエラーチェックをするようになった」と見たほうが良いでしょう。

間違いなく、ARTの仕様に合わせるほうがプログラムのミスが減ります。