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の仕様に合わせるほうがプログラムのミスが減ります。