Nativeスレッドattach方式

11758 ワード

概要
jniの知っていることを書いたことがあって、nativeスレッドからjavaメソッドをコールバックすることができます.Javaメソッドをコールバックする場合、必要な2つの標準ステップは、呼び出し前のattach nativeスレッド、呼び出し終了後のdetach nativeスレッドです.
後文の記述を容易にするために、コードが先行します.まずJavaクラスを定義します.
public class Foo {
    static {
        System.loadLibrary("foo");
    }
    public static native void startThread();
    public static void onRun(){
        Log.i("Foo", "onRun: thread run");
    }
}

このクラスがvmによってロードされるとstaticブロックはnativeライブラリlibfoo.soをロードし、startThreadはnativeスレッドを起動し、onRunメソッドをコールバックする.
次にjavahを使用してヘッダファイルを自動的に生成します.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_cy_myapplication_Foo */

#ifndef _Included_com_cy_myapplication_Foo
#define _Included_com_cy_myapplication_Foo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_cy_myapplication_Foo
 * Method:    startThread
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_cy_myapplication_Foo_startThread
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

自動生成されたヘッダファイルは簡単で、Javaだけです.com_cy_myapplication_Foo_startThreadメソッド.
次にfoo.cppでこの関数を実現します.
JNIEXPORT void JNICALL Java_com_cy_myapplication_Foo_startThread(JNIEnv * jenv, jclass jcls)
{
    jenv->GetJavaVM(&jvm);
    cls = (jclass)jenv->NewGlobalRef(jcls);
    method = jenv->GetStaticMethodID(cls, "onRun", "()V");

    pthread_t thd;
    __android_log_print(ANDROID_LOG_INFO, "jni", "create thread");
    pthread_create(&thd, NULL, myproc, NULL);
//    pthread_create(&thd, NULL, otherproc, (void*)java_callback1);
//    pthread_create(&thd, NULL, otherproc, (void*)java_callback);
    pthread_join(thd, NULL);
    __android_log_print(ANDROID_LOG_INFO, "jni", "join thread");

    if (detachKey!=0)
        pthread_key_delete(detachKey);
    jenv->DeleteGlobalRef(cls);
}

上記のコードはNewGlobalRefを使用してFooというクラスをグローバル変数便利スレッド関数に保存して使用します(onRun methodIDを取得する目的は同じです).ここではpthread_に触れていますkey、読者は無視できますが、後述します.
次に、attach nativeスレッドの詳細を分析します.
一般的な方法
一般的な場合、スレッド関数は制御可能で、つまり自分で修正することができ、jniコードを追加するのに便利です.この場合、以下のように実現できる.
static void* myproc(void* arg)
{
    JNIEnv* jenv = NULL;
    jvm->GetEnv((void**)&jenv, JNI_VERSION_1_2);

    __android_log_print(ANDROID_LOG_INFO, "jni", "attach thread");
    jvm->AttachCurrentThread(&jenv, NULL);

    __android_log_print(ANDROID_LOG_INFO, "jni", "run thread body");
    jenv->CallStaticVoidMethod(cls, method);

    __android_log_print(ANDROID_LOG_INFO, "jni", "detach thread");
    jvm->DetachCurrentThread();

    return NULL;
}

すなわち、GetEnvが現在のスレッドの環境(JNIEnv)を取得し、AttachCurrentThreadがjavaメソッドを呼び出した後、DetachCurrentThreadが呼び出される.
高度な方法
しかし、スレッド関数が制御されていない場合もあります.つまり、スレッド関数を変更できないか、jniコードをスレッド関数に「侵入式」に追加できない場合もあります.
たとえば、バックグラウンドスレッドを起動するエントリを提供する成熟したライブラリを考慮します.(libStartThread)は、バックグラウンドスレッドで関数ポインタにコールバックします.このライブラリのソースコードは変更できないか、java以外のプラットフォームにサービスを提供する必要があります.これにより、必要なAttach、Detachステップを完了することなくjniコードを注入できません.サンプルコードは次のとおりです.
typedef void(*Callback)(int);
static void* otherproc(void* arg)
{
    Callback func = (Callback)arg;
    //__android_log_print(ANDROID_LOG_INFO, "jni", "do callback first time");
    func(1);
    //__android_log_print(ANDROID_LOG_INFO, "jni", "do callback second time");
    func(2);
}
//void libStartThread(Callback callback);

otherprocは成熟したライブラリ内部のバックグラウンドスレッドで、Callback関数ポインタをコールバックします.あいにくjavaに移植した後、javaでコールバックを提供しなければなりません.
簡単なパッケージを作成するには、次のいずれかのオプションがあります.
static void java_callback(int time)
{
    JNIEnv* jenv = NULL;
    jvm->GetEnv((void**)&jenv, JNI_VERSION_1_2);

    __android_log_print(ANDROID_LOG_INFO, "jni", "attach thread");
    jvm->AttachCurrentThread(&jenv, NULL);

    __android_log_print(ANDROID_LOG_INFO, "jni", "run thread body %d", time);
    jenv->CallStaticVoidMethod(cls, method);

    __android_log_print(ANDROID_LOG_INFO, "jni", "detach thread");
    jvm->DetachCurrentThread();
}

nativeはjava_callbackを実現し、javaメソッド(onRun)のエージェントとして、コールバックが発生するたびに現在のスレッドをAttachし、javaメソッドを呼び出し、その後Detachすればよい.
別のシナリオでは、上記のシナリオでコールバックのたびにattach、detachとは異なり、nativeスレッドに対してのみattach、detachを1回行う.これはpthread_key_tを用いる必要がある.pthread_key_tについてはlinux manpageを参照することができる.ここで、関心のある特性は以下の通りである.
An optional destructor function may be associated with each key value. At thread exit, if a key value has a non-NULL destructor pointer, and the thread has a non-NULL value associated with that key, the value of the key is set to NULL, and then the function pointed to is called with the previously associated value as its sole argument.
単純に理解すると、値が空でないpthread_key_tは、スレッドが終了したときにpthread_setspecificによって設定された「構造関数」をコールバックする.
これに基づいてjava_callback 1を実装できます.
static pthread_key_t detachKey=0;
static void detachKeyDestructor(void* arg)
{
    pthread_t thd = pthread_self();
    JavaVM* jvm = (JavaVM*)arg;
    __android_log_print(ANDROID_LOG_INFO, "jni", "detach thread");
    jvm->DetachCurrentThread();
}
static void java_callback1(int time)
{
    JNIEnv* jenv = NULL;
    int status = jvm->GetEnv((void**)&jenv, JNI_VERSION_1_2);
    __android_log_print(ANDROID_LOG_INFO, "jni", "getenv: %d", status);
    if (status == JNI_EDETACHED)
    {
        if (detachKey == 0)
        {
            __android_log_print(ANDROID_LOG_INFO, "jni", "create thread key");
            pthread_key_create(&detachKey, detachKeyDestructor);
        }

        __android_log_print(ANDROID_LOG_INFO, "jni", "attach thread");
        jvm->AttachCurrentThread(&jenv, NULL);
        pthread_setspecific(detachKey, jvm);
    }

    __android_log_print(ANDROID_LOG_INFO, "jni", "run thread body %d", time);
    jenv->CallStaticVoidMethod(cls, method);
}

まずJNIEnvを取得しますが、今回は戻り値を判断する必要があります.戻り値がJNI_EDETACHEDであれば、これまでスレッドがdetached状態であり、attachしたことがないことを示します.この場合、pthread_key_tの値、すなわちpthread_setspecific(detachKey, jvm);を設定してjvmを「解析関数数」に渡す必要があります.解析関数detachスレッドを簡単に作成できます.もちろん、detachKeyがまだ作成されていない場合は、作成して「解析関数」をバインドする必要があります.次は同じAttachCurrentThreadとCallStaticVoidMethodです.(もちろん、読者は別の方法で最初のAttachかどうかを判断することができますが、GetEnvの戻り値を判断する必要はありません)
「構造関数」は、pthread_selfを介して「構造関数」を呼び出す現在のスレッドを取得し、入力されたjvmを用いてDetachCurrentThread detachスレッドを呼び出す.これにより、ハイエンドの「眩しい技」が完成する.
pthread_key_t+DetachCurrentThreadの背後原理
もちろん、上記の「炫技」は公式ドキュメントでサポートされています.
Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific to store the JNIEnv in thread-local-storage; that way it’ll be passed into your destructor as the argument.)