2

pthreadC++ クラスで作成されたクラスから JNI を介してコールバックしようとしています。次のコードを使用します。Android アプリからボタンを押すと、正常にcallbackStringJNI()動作します。の作成は、pthreadJNI を介して渡されるボタンの押下によってトリガーされます。によって作成された関数/スレッドがpthreadの非同期呼び出しを試みた場合callbackStringJNI()、Android アプリで渡された文字列を取得しますmessageMe()(デバッガーで中断した場合にのみ表示されます) が、文字列を使用しようとすると (IE、UI に何かを表示します)、アプリケーションが Android ソースの 'Handler.class' 関数joinThreadPoolで中断し、再度再開すると、以下の Android コードで「例外」が発生します。

Android API エラー コード:

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might    occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(//////////Hits here - this message is never actually displayed though.
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

に見られるように、文字列を別の変数にコピーできますが、文字列を aまたはmessageMe()a で使用することはできません。からコールバックできない理由について、現時点で非常に困惑しています。提案は大歓迎です。ToastTextViewpthread

アンドロイド Java:

 package com.example.somecontrol1;

public class MainActivity extends Activity {

// load the library - name matches jni/Android.mk
static {
    System.loadLibrary("ndkfoo");
    }

    private native String inintNativeClass();
    private native String SetUpSocketNC();
    private native void initJNICallback();

 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    initJNICallback();
    inintNativeClass();  


  public void messageMe(String text) {             
    String teststr = text;
    Toast.makeText(getApplicationContext(), teststr, Toast.LENGTH_LONG).show();
    //appendTextAndScroll(text, gtv);
  } 

}

以下のコードでは、getJNIEnv()が呼び出され、実行がボタンの押下によって行われた場合、JNI_OKが結果になります。呼び出しが pthread のコールバックから来るJNI_EDETACHEDと、結果が返されてAttachCurrentThread()成功します。

JNI C ndkfoo.c

#include <jni.h>   
#include "testSocketClassWrapper.hpp"

static JavaVM* cachedJVM;
static jobject g_javaObj;
static jclass cachedclassID;
void *m_GBLpmyCSocket = NULL;


 jint JNI_OnLoad(JavaVM *jvm, void *reserved)
 {
  cachedJVM = jvm;

  JNIEnv* env;
  if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
    LOGD("GETENVFAILEDONLOAD");
    return -1;
  }
  return JNI_VERSION_1_4;
 }   

void Java_com_example_somecontrol1_MainActivity_initJNICallback(JNIEnv* env, jobject jobj) {
     //LOGD("Java_org_test_games_Wrapper_initJNIBridge()");

        g_javaObj = (*env)->NewGlobalRef(env, jobj);


        jclass storeclassID = (*env)->FindClass(env, "com/example/somecontrol1/MainActivity");
        if ( (*env)->ExceptionCheck(env) == JNI_TRUE ){
            (*env)->ExceptionDescribe(env);
              LOGD("got into exception describe");
        }
        cachedclassID = (jclass)(*env)->NewGlobalRef(env, storeclassID);
        if ( (*env)->ExceptionCheck(env) == JNI_TRUE ){
                (*env)->ExceptionDescribe(env);
                  LOGD("got into exception describe");
        }
}

jstring Java_com_example_somecontrol1_MainActivity_inintNativeClass(JNIEnv * env, jobject object){
    m_GBLpmyCSocket = (void *)MyClass_create();
return (*env)->NewStringUTF(env, "launched class");
}

typedef struct JniMethodInfo_
{
    JNIEnv*    env;
    jclass      classID;
    jmethodID   methodID;
} JniMethodInfo;

static JNIEnv* getJNIEnv()//was JNIEnv
{
//JavaVM* jvm = cocos2d::JniHelper::getJavaVM();
if (NULL == cachedJVM) {
    LOGD("Failed to get JNIEnv. JniHelper::getJavaVM() is NULL");
    return NULL;
}

JNIEnv *env = NULL;
// get jni environment
jint ret = (*cachedJVM)->GetEnv(cachedJVM, (void**)&env, JNI_VERSION_1_4);

switch (ret) {
    case JNI_OK :
        // Success!
        LOGD("getenv successA");
        return env;

    case JNI_EDETACHED :
        // Thread not attached
        LOGD("thread not attached");
        // TODO : If calling AttachCurrentThread() on a native thread
        // must call DetachCurrentThread() in future.
        // see: http://developer.android.com/guide/practices/design/jni.html

        if ((*cachedJVM)->AttachCurrentThread(cachedJVM, &env, NULL) < 0)
        {
            LOGD("Failed to get the environment using AttachCurrentThread()");
            return NULL;
        } else {
            // Success : Attached and obtained JNIEnv!
            LOGD("getenv successB");
            return env;
        }

    case JNI_EVERSION :
        // Cannot recover from this error
        LOGD("JNI interface version 1.4 not supported");
    default :
        LOGD("Failed to get the environment using GetEnv()");
        return NULL;
    }
}    

static bool getMethodInfo(JniMethodInfo *methodinfo, const char *methodName, const char *paramCode)
{
jmethodID methodID = 0;
JNIEnv *pEnv = 0;
bool bRet = false;

do
{
    pEnv = getJNIEnv();
    if (! pEnv)
    {
        LOGD("getJNIEnv Break Called");
        break;
    }

    //jclass classID = getClassID(pEnv);
    //jclass classID = cachedclassID;

    //methodID = (*pEnv)->GetMethodID(pEnv, classID, methodName, paramCode);
    methodID = (*pEnv)->GetMethodID(pEnv, cachedclassID, methodName, paramCode);
    if (! methodID)
    {
        LOGD("Failed to find method id of %s", methodName);
        break;
    }

    //methodinfo->classID = classID;
    methodinfo->env = pEnv;
    methodinfo->methodID = methodID;


    bRet = true;
} while (0);

return bRet;
}

void callbackStringJNI(const char *newstr)
{
    LOGD("callbackStringJNI");    


    JniMethodInfo methodInfo;
    if (! getMethodInfo(&methodInfo, "messageMe", "(Ljava/lang/String;)V"))
    {
        LOGD("Cannot find method!");
        return;
    }

    jstring jstr = (*methodInfo.env)->NewStringUTF(methodInfo.env, newstr);    
    (*methodInfo.env)->CallVoidMethod(methodInfo.env, g_javaObj, methodInfo.methodID, jstr);
}

クラスラッパー

//file testSocketClassWrapper.cpp
#include "testSocketClassWrapper.hpp"
#include "testSocketClass.hpp"


extern "C" void* MyClass_create() {
   return new mYNewClass;
}
extern "C" void MyClass_release(void* myclass) {
   delete static_cast<mYNewClass*>(myclass);
}
extern "C" void MyClass_sendCommandToSerialDevice(void* myclass, int cmd, int params, int id) {
   static_cast<mYNewClass*>(myclass)->sendCommandToSerialDevice(cmd,params,id);
}
extern "C" void SetUpSocket(void* myclass, int cmd, int params, int id) {
   static_cast<mYNewClass*>(myclass)->SetUpSocket(cmd,params,id);
}
extern "C" void Startcntl(void* myclass, int cmd, int params, int id) {
   static_cast<mYNewClass*>(myclass)->Startcntl();
}
extern "C" void Stopcntl(void* myclass, int cmd, int params, int id) {
   static_cast<mYNewClass*>(myclass)->Stopcntl();
}

C++ クラス

//file testSocketClass.cpp
void mYNewClass::SetUpSocket(int Command, int Parameters, int DeviceID){
    thread1 = 0;
    iret1 = 0;

    iret1 = pthread_create( &thread1, NULL, &mYNewClass::thread_helper, this);
}
void mYNewClass::Startcntl(){

    callbackStringJNI("sent start");//////this works.
}

C++ ヘッダー

//file testSocketClass.hpp
extern "C" {
    void callbackStringJNI(const char *);
}

class mYNewClass{

public:

void *threadfunc(void)
    {    
    LOGD("in thread");
     while(1){
        LOGD("thread looping");
        callbackStringJNI("readata start");

        LOGD("sleep");
        sleep(30);
        }
    LOGD("after callbackStringJNI");////this does not work
        return(0);
    }

static void *thread_helper(void *context)
    {
        return ((mYNewClass *)context)->threadfunc();
    }
4

1 に答える 1

1

解決策が見つかりました。根本的な原因についての洞察をいただければ幸いです。

どうやらコールバックは Android ワーカー スレッドを使用しており、UI スレッドにアクセスできません。

Android の Java クラスmessageMe関数を修正しました。

public void messageMe(final String text) {
   runOnUiThread(new Runnable() {
        public void run()
        {
          Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
        }
    });
}

オーバーヘッドを削減するために、別の変更を加えました。そのようにメソッドIDをキャッシュしましたinitJNICallback

static jmethodID cachedmethodID;

initJNICallback(){

    cachedmethodID  = (*env)->GetMethodID(env, storeclassID, "messageMe", "(Ljava/lang/String;)V");//no NewGlobalRef needed.
}
于 2013-11-08T22:37:53.747 に答える