Android: Exception when calling a static java method from cpp thread - android

When my java class loads, I call this jni method to set "listener" from cpp to java (I record audio using cpp and want to pass its bytes to java) :
MyJava.class
setListener(JNIEnv *env, jclass thiz);
myCpp.cpp
setListener(JNIEnv *env, jclass thiz) {
envMyClass = env;
classMyClass = thiz;
// I read that I need these 2 lines in order to connect the java thread to the cpp thread
env->GetJavaVM(&javavm);
GetJniEnv(javavm, &envCamera);
return 0;
}
bool GetJniEnv(JavaVM *vm, JNIEnv **env) {
bool did_attach_thread = false;
*env = nullptr;
// Check if the current thread is attached to the VM
auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
did_attach_thread = true;
} else {
// Failed to attach thread. Throw an exception if you want to.
}
} else if (get_env_result == JNI_EVERSION) {
// Unsupported JNI version. Throw an exception if you want to.
}
return did_attach_thread;
}
and the in myCpp.cpp thread I'm trying to call:
if (envMyClass != nullptr && classMyClass != nullptr && javavm != nullptr) {
LOGD("000");
jmethodID javaMethod = envMyClass->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
LOGD("001");
envMyClass->CallStaticVoidMethod(classMyClass, javaMethod);
}
and it crashes on the line of "jmethodId javaMethod.."
myJavaFunction is a method in MyJava class:
public static void myJavaFunction() {
Log.d("my_log", "jni callback");
}
the crash:
Abort message: 'JNI DETECTED ERROR IN APPLICATION: a thread (tid 12346 is making JNI calls without being attached
in call to GetStaticMethodID'
Any idea how to fix it?

I've noticed a few things:
In setListener, I assume you are assigning jclass thiz to a global variable classMyClass. To do so, you should use classMyClass = env->NewGlobalRef(thiz), otherwise the class reference will not be valid (reference).
You are calling GetJniEnv but you do not check its result and therefore you do not actually know whether the current thread has attached successfully.
I assume you are attaching the current thread in setListener, save the env to a global variable, and then use that variable at another point in your code. Could it be that you are switching thread context somewhere in between? You could do the following to make sure the current thread is really attached:
JNIEnv *thisEnv;
int getEnvStat = jvm->GetEnv((void **)&thisEnv, JNI_VERSION_1_6 /* or whatever your JNI version is*/);
if (getEnvStat == JNI_EDETACHED)
{
if (jvm->AttachCurrentThread(&thisEnv, NULL) != 0)
{
// throw error
}
else
{
jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
thisEnv->CallStaticVoidMethod(classMyClass, javaMethod);
}
}
else if (getEnvStat == JNI_OK)
{
jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
thisEnv->CallStaticVoidMethod(classMyClass, javaMethod);
}
else if (getEnvStat == JNI_EVERSION)
{
// throw error
}
}
You can make this more efficient if you call jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V"); only once initially and store the jmethodID in a global variable. jmethodIDs are valid across envs.

Related

How to properly and simply use callbacks in JNI NDK Android

I am trying to have Java call a C function in Android and some caveats that I am met with is the documentation for callbacks in general seem very unclear and most answers on SO are just talking about theory and posting broken links with no actual code. So please post code in your answer.
I am trying to create a joystick in java and have it send input to the NDK to perform actions.
if I can just have Java call a C function and pass a int to it ,
that would be good. but I think that wont work because C in running
it's own main loop.
perhaps I need to use C to call a java
function to get controller state and execute code from there.
I did manage to find some code Here but it seems the code while it does compile by itself, but doesn't appear to work when moved to modern code .
How should I implement call backs in a Java C relationship on Android? It seems that from the code example above that it is C that is calling Java functions and these need to be used like getters functions.
Here is some code that I have and I appear to be missing somethings because it is unclear to me how I should have these call backs performed. I will mark this code with function name and where is it located, if java or C++ (CPP) .
I think there are two ways that I would like this to work.
My Javafunction that I want to call
public int Cat(){
Log.e("JniHandler", "CAT WAS CALLED!!!");
return 333;
}
Structure information CPP . Not sure if it is needed.
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
#include <assert.h>
typedef struct tick_context {
JavaVM *javaVM;
jclass jniHelperClz;
jobject jniHelperObj;
jclass mainActivityClz;
jobject mainActivityObj;
pthread_mutex_t lock;
int done;
} TickContext;
TickContext g_ctx;
This section works. It looks that as soon as the JVM is launched, this will initialize.
JNI_OnLoad CPP
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
memset(&g_ctx, 0, sizeof(g_ctx));
SDL_Log(" DEBUG -- JNI INIT!!!");
g_ctx.javaVM = vm;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
jclass clz = env->FindClass("org/foo/bar/mainactivity");
g_ctx.jniHelperClz = (jclass)env->NewGlobalRef(clz); // hack
jmethodID jniHelperCtor = env->GetMethodID(g_ctx.jniHelperClz,"<init>", "()V");
jobject handler = env->NewObject(g_ctx.jniHelperClz, jniHelperCtor);
g_ctx.jniHelperObj = env->NewGlobalRef(handler);
//queryRuntimeInfo(env, g_ctx.jniHelperObj); // This when getting version returns null.
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
queryRuntimeInfo CPP
void queryRuntimeInfo(JNIEnv *env, jobject instance) {
SDL_Log("DEBUG QUERYRUNTIME INTO");
// Find out which OS we are running on. It does not matter for this app
// just to demo how to call static functions.
// Our java JniHelper class id and instance are initialized when this
// shared lib got loaded, we just directly use them
// static function does not need instance, so we just need to feed
// class and method id to JNI
jmethodID versionFunc = env->GetStaticMethodID(g_ctx.jniHelperClz, "getBuildVersion", "()Ljava/lang/String;"); // This returns null.
if (!versionFunc) {
SDL_Log("DEBUG versionFunc INTO");
//LOGE("Failed to retrieve getBuildVersion() methodID # line %d",__LINE__);
return;
}
SDL_Log("DEBUG BUILD VERSION INTO");
jstring buildVersion = (jstring)env->CallStaticObjectMethod(g_ctx.jniHelperClz, versionFunc); // hack
const char *version = env->GetStringUTFChars(buildVersion, NULL);
if (!version) {
SDL_Log("DEBUG buildVersion INTO");
//LOGE("Unable to get version string # line %d", __LINE__);
return;
}
//LOGI("Android Version - %s", version);
SDL_Log("DEBUG ANDROID VERSION %s", version);
env->ReleaseStringUTFChars(buildVersion, version);
// we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
env->DeleteLocalRef(buildVersion);
// Query available memory size from a non-static public function
// we need use an instance of JniHelper class to call JNI
jmethodID memFunc = env->GetMethodID(g_ctx.jniHelperClz, "getRuntimeMemorySize", "()J");
if (!memFunc) {
//LOGE("Failed to retrieve getRuntimeMemorySize() methodID # line %d",__LINE__);
SDL_Log("DEBUG Failed to retrieve memory size");
return;
}
jlong result = env->CallLongMethod(instance, memFunc);
//LOGI("Runtime free memory size: %" PRId64, result);
SDL_Log("DEBUG RUN TIME FREE MEMORY SIZE");
(void)result; // silence the compiler warning
}
This function isn't ever called and I am not sure who is the caller. CPP
extern "C" JNIEXPORT void JNICALL InvokeFunction(JNIEnv *env, jobject instance) {
SDL_Log("JNI CALL START TICKET");
pthread_t threadInfo_;
pthread_attr_t threadAttr_;
pthread_attr_init(&threadAttr_);
pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);
pthread_mutex_init(&g_ctx.lock, NULL);
jclass clz = env->GetObjectClass(instance);
g_ctx.mainActivityClz = (jclass)env->NewGlobalRef(clz); // hack
g_ctx.mainActivityObj = env->NewGlobalRef(instance);
int result = pthread_create( &threadInfo_, &threadAttr_, UpdateTicks, &g_ctx);
assert(result == 0);
pthread_attr_destroy(&threadAttr_);
(void)result;
}
updateticks CPP // This code is never actually called.
void* UpdateTicks(void* context) {
SDL_Log("DEBUG -- UPDATE TICKS IS CALLED");
TickContext *pctx = (TickContext*) context;
JavaVM *javaVM = pctx->javaVM;
JNIEnv *env;
jint res = javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = javaVM->AttachCurrentThread(&env, NULL);
if (JNI_OK != res) {
//LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
SDL_Log("FAILED TO ATTACH THREAD");
return NULL;
}
}
jmethodID statusId = env->GetMethodID(pctx->jniHelperClz, "updateStatus", "(Ljava/lang/String;)V");
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: initializing...");
// get mainActivity updateTimer function
jmethodID timerId = env->GetMethodID(pctx->mainActivityClz, "updateTimer", "()V");
struct timeval beginTime, curTime, usedTime, leftTime;
const struct timeval kOneSecond = {
(__kernel_time_t)1,
(__kernel_suseconds_t) 0
};
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: start ticking ...");
while(1) {
gettimeofday(&beginTime, NULL);
pthread_mutex_lock(&pctx->lock);
int done = pctx->done;
if (pctx->done) {
pctx->done = 0;
}
pthread_mutex_unlock(&pctx->lock);
if (done) {
break;
}
env->CallVoidMethod(pctx->mainActivityObj, timerId);
gettimeofday(&curTime, NULL);
timersub(&curTime, &beginTime, &usedTime);
timersub(&kOneSecond, &usedTime, &leftTime);
struct timespec sleepTime;
sleepTime.tv_sec = leftTime.tv_sec;
sleepTime.tv_nsec = leftTime.tv_usec * 1000;
if (sleepTime.tv_sec <= 1) {
nanosleep(&sleepTime, NULL);
} else {
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread error: processing too long!");
}
}
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: ticking stopped");
javaVM->DetachCurrentThread();
return context;
}

JNI on Android: Callback from native with Integer parameter works, with String or Void it doesn't. Why?

I have a callback class for doing callbacks from native C++ code to Kotlin (not sure if Kotlin/Java makes a difference here, if so, is there any documentation on that?).
I have a working callback with an integer parameter, that I call from different native threads without a problem. Now I want to add a second one that sends a String, but for some reason that doesn't work.
My callback class implementation looks like this:
jclass target;
jmethodID id;
Callback::Callback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (g_env != NULL) {
target = g_env->GetObjectClass(g_object);
id = g_env->GetMethodID(target, "integerCallback", "(I)V");
}
}
void Callback::call(int integerValue, const char *stringValue) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
LOGD("GetEnv: Failed to attach");
}
} else if (getEnvStat == JNI_OK) {
LOGD("GetEnv: JNI_OK");
} else if (getEnvStat == JNI_EVERSION) {
LOGD("GetEnv: version not supported");
}
g_env->CallVoidMethod(g_object, id, (jint) integerValue);
}
It gets instantiated in my native-lib.cpplike this:
extern "C" {
std::unique_ptr<Callback> callback;
JavaVM *g_jvm = nullptr;
static jobject myJNIClass;
jint JNI_OnLoad(JavaVM *pJvm, void *reserved) {
g_jvm = pJvm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_app_common_jni_JniBridge_loadNative(JNIEnv *env, jobject instance,
jstring URI, jboolean isWaitMode) {
myJNIClass = env->NewGlobalRef(instance);
if (callback == nullptr) {
callback = std::make_unique<Callback>(*g_jvm, myJNIClass);
}
}
The callback method it talks to is in my JniBridge.kt (the threading part is probbaly irrelevant to the problem):
object JniBridge {
init {
System.loadLibrary("native-lib")
Timber.d("Native Lib loaded!")
}
fun load(fileName: String, isWaitMode: Boolean) {
loadNative(fileName, isWaitMode)
}
fun integerCallback(value: Int) {
someListener?.invoke(value)
}
private external fun loadNative(fileName: String, isWaitMode: Boolean)
}
So now if my native code triggers the call() method in my callback, my integerCallback() in JniBridge.kt gets called correctly with an integer.
But here's what I don't get: If I change my callback to send a String it doesn't work. If I change it like this:
// in JniBridge.kt
fun stringCallback(value: String) {
someListener?.invoke(value)
}
// in Callback.cpp
//getting the method
id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String)V");
//making the call
g_env->CallVoidMethod(g_object, id, (jstring) stringValue);
Now my app crashes with this error:
JNI DETECTED ERROR IN APPLICATION: JNI GetStringUTFChars called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/my/app/common/jni/JniBridge;.stringCallback(Ljava/lang/String)V"
The same happens if I try calling a void method (like this: id = g_env->GetMethodID(target, "voidCallback", "(V)V");or calling one that takes two integers (like this: id = g_env->GetMethodID(target, "twoIntegerCallback", "(I;I)V");, of course with having the corresponding methods in JniBridge.kt in place.
Why does this happen and how can I fix it?
Note: For clarity I have omitted all parts of my code that I believe are not related to the problem, if something crucial is missing, please let me know and I fix it.
You're missing a semi-colon in the method signature that you pass to GetMethodID.
It should be:
id = g_env->GetMethodID(target, "stringCallback", "(Ljava/lang/String;)V");
Note the semi-colon after Ljava/lang/String.
See the part about Type Signatures in Oracle's JNI documentation.
Regarding your other variations:
A Java void function() would have the signature ()V.
A Java void function(int i, int j) would have the signature (II)V.
As a side note, you really ought to verify the return value and check for exceptions after every JNI call.

C++ Callback to Java -> Why can't I retrieve JNIEnv in callback class when coming from a different thread?

I'm trying to build a callback class to make a call to a Java method from different threads in my native code, on Android. I have read a lot about how to that, and as long as I'm on the same thread, it all works. But from a different thread I can't retrieve the JNIEnv properly, and I can't figure out what I'm doing wrong.
I'm not very experienced with C++ and the JNI so it's quite possible this is some beginner's problem...but I've spetd days on it and can't see what it is.
This is my Callback class, .h and .cpp file:
class AudioCallback {
public:
explicit AudioCallback(JavaVM&, jobject);
void playBackProgress(int progressPercentage);
private:
JavaVM& g_jvm;
jobject g_object;
};
jclass target = NULL;
jmethodID id = NULL;
AudioCallback::AudioCallback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (g_env != NULL) {
target = g_env->GetObjectClass(g_object);
id = g_env->GetMethodID(target, "integerCallback", "(I)V");
//This is a test call to see if I can call my java method. It works.
g_env->CallVoidMethod(g_object, id, (jint) 103);
}
}
// this method is calles from other threads, so I want to attach to the current thread once I got my JNIEnv, but I can't since it's null...
void AudioCallback::playBackProgress(int progressPercentage) {
JNIEnv *g_env;
// This is null and I don't know why!
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (g_env == NULL) {
LOGE("JNIEnv in callback method is null");
} else {
LOGD("Env Stat: %d", getEnvStat);
JavaVMAttachArgs vmAttachArgs;
if (getEnvStat == JNI_EDETACHED) {
LOGD("GetEnv: not attached - attaching");
if (g_jvm.AttachCurrentThread(&g_env, &vmAttachArgs) != 0) {
LOGD("GetEnv: Failed to attach");
}
} else if (getEnvStat == JNI_OK) {
LOGD("GetEnv: JNI_OK");
} else if (getEnvStat == JNI_EVERSION) {
LOGD("GetEnv: version not supported");
}
g_env->CallVoidMethod(g_object, id, (jint) progressPercentage);
//thread gets detached elsewhere
}
}
This is my native_lib, where I get the JavaVM and instantiate the callback class:
std::unique_ptr<AudioEngine> audioEngine;
std::unique_ptr<AudioCallback> callback;
JavaVM *g_jvm = nullptr;
static jobject myJNIClass;
jint JNI_OnLoad(JavaVM *pJvm, void *reserved) {
g_Jvm = pJvm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_appy_common_jni_JniBridge_playFromJNI(JNIEnv *env, jobject instance,jstring URI) {
myJNIClass = env->NewGlobalRef(instance);
callback = std::make_unique<AudioCallback>(*gJvm, myJNIClass);
// this test call to my callback works
callback->playBackProgress(104);
const char *uri = env->GetStringUTFChars(URI, NULL);
//... urelated code is left out here ...
//audioEngine gets the callback and uses it from threads it creates
audioEngine = std::make_unique<AudioEngine>(*extractor, *callback);
audioEngine->setFileName(uri);
audioEngine->start();
}
I've shortened the code and removed all unrelated/unnecessary parts. If something crucial is missing, please comment and I'll add it.
Solution: As per the suggestions #Michael made in his answer, I made these edits to the playbackProgressmethod in my callback class to make it work:
void AudioCallback::playBackProgress(int progressPercentage) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
LOGD("GetEnv: not attached - attaching");
if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
LOGD("GetEnv: Failed to attach");
}
} else if (getEnvStat == JNI_OK) {
LOGD("GetEnv: JNI_OK");
} else if (getEnvStat == JNI_EVERSION) {
LOGD("GetEnv: version not supported");
}
g_env->CallVoidMethod(g_object, id, (jint) progressPercentage);
// mJvm.DetachCurrentThread();
}
Now it checks for the value of getEnvStat directly, the null check of g_env before was wrong. I also had to replace the JavaVMAttachArgswith NULLto make it work.
Your logic in playBackProgress wrong.
The only time your try to attach the current thread is when g_env is non-NULL. But if g_env is non-NULL then GetEnv probably succeeded (you should of course also check that getEnvStat == JNI_OK) and AttachCurrentThread isn't necessary.
The case in which you need to call AttachCurrentThread is when g_env is NULL and getEnvStat is JNI_EDETACHED.
You also need to keep track of whether you did call AttachCurrentThread, since you should call DetachCurrentThread at some point in those cases. See this answer for more info on that.

What is the best way to save JNIEnv*

I have an Android project with JNI. In the CPP file which implements a listener class, there is a callback x() . When x() function is called, I want to call another function in a java class. However, in order to invoke that java function, I need to access JNIEnv*.
I know that in the same cpp file of the callback, there is a function:
static jboolean init (JNIEnv* env, jobject obj) {...}
Should I save in the cpp file JNIEnv* as member variable when init(..) is called? and use it later when the callback happens?
Sorry but I am a beginner in JNI.
Caching a JNIEnv* is not a particularly good idea, since you can't use the same JNIEnv* across multiple threads, and might not even be able to use it for multiple native calls on the same thread (see http://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html)
Writing a function that gets the JNIEnv* and attaches the current thread to the VM if necessary isn't too difficult:
bool GetJniEnv(JavaVM *vm, JNIEnv **env) {
bool did_attach_thread = false;
*env = nullptr;
// Check if the current thread is attached to the VM
auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
did_attach_thread = true;
} else {
// Failed to attach thread. Throw an exception if you want to.
}
} else if (get_env_result == JNI_EVERSION) {
// Unsupported JNI version. Throw an exception if you want to.
}
return did_attach_thread;
}
The way you'd use it is:
JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
vm->DetachCurrentThread();
}
You could wrap this in a class that attaches upon construction and detaches upon destruction, RAII-style:
class ScopedEnv {
public:
ScopedEnv() : attached_to_vm_(false) {
attached_to_vm_ = GetJniEnv(g_vm, &env_); // g_vm is a global
}
ScopedEnv(const ScopedEnv&) = delete;
ScopedEnv& operator=(const ScopedEnv&) = delete;
virtual ~ScopedEnv() {
if (attached_to_vm_) {
g_vm->DetachCurrentThread();
attached_to_vm_ = false;
}
}
JNIEnv *GetEnv() const { return env_; }
private:
bool attached_to_env_;
JNIEnv *env_;
};
// Usage:
{
ScopedEnv scoped_env;
scoped_env.GetEnv()->SomeJniFunction();
}
// scoped_env falls out of scope, the thread is automatically detached if necessary
Edit: Sometimes you might have a long-ish running native thread that will need a JNIEnv* on multiple occasions. In such situations you may want to avoid constantly attaching and detaching the thread to/from the JVM, but you still need to make sure that you detach the thread upon thread destruction.
You can accomplish this by attaching the thread only once and then leaving it attached, and by setting up a thread destruction callback using pthread_key_create and pthread_setspecific that will take care of calling DetachCurrentThread.
/**
* Get a JNIEnv* valid for this thread, regardless of whether
* we're on a native thread or a Java thread.
* If the calling thread is not currently attached to the JVM
* it will be attached, and then automatically detached when the
* thread is destroyed.
*/
JNIEnv *GetJniEnv() {
JNIEnv *env = nullptr;
// We still call GetEnv first to detect if the thread already
// is attached. This is done to avoid setting up a DetachCurrentThread
// call on a Java thread.
// g_vm is a global.
auto get_env_result = g_vm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (g_vm->AttachCurrentThread(&env, NULL) == JNI_OK) {
DeferThreadDetach(env);
} else {
// Failed to attach thread. Throw an exception if you want to.
}
} else if (get_env_result == JNI_EVERSION) {
// Unsupported JNI version. Throw an exception if you want to.
}
return env;
}
void DeferThreadDetach(JNIEnv *env) {
static pthread_key_t thread_key;
// Set up a Thread Specific Data key, and a callback that
// will be executed when a thread is destroyed.
// This is only done once, across all threads, and the value
// associated with the key for any given thread will initially
// be NULL.
static auto run_once = [] {
const auto err = pthread_key_create(&thread_key, [] (void *ts_env) {
if (ts_env) {
g_vm->DetachCurrentThread();
}
});
if (err) {
// Failed to create TSD key. Throw an exception if you want to.
}
return 0;
}();
// For the callback to actually be executed when a thread exits
// we need to associate a non-NULL value with the key on that thread.
// We can use the JNIEnv* as that value.
const auto ts_env = pthread_getspecific(thread_key);
if (!ts_env) {
if (pthread_setspecific(thread_key, env)) {
// Failed to set thread-specific value for key. Throw an exception if you want to.
}
}
}
If __cxa_thread_atexit is available to you, you might be able to accomplish the same thing with some thread_local object that calls DetachCurrentThread in its destructor.
#Michael, gives a good overview of how best to retrieve the JNI by caching the JVM.
For those that dont want to use pthread (or cant' because you are on Windows system), and you are using c++ 11 or highter, then thread_local storage is the way to go.
Bellow is rough example on how to implement a wrapper method that properly attaches to a thread and automatically cleans-up when the thread exits
JNIEnv* JNIThreadHelper::GetJniEnv() {
// This method might have been called from a different thread than the one that created
// this handler. Check to make sure that the JNI is attached and if not attach it to the
// new thread.
// double check it's all ok
int nEnvStat = m_pJvm->GetEnv(reinterpret_cast<void**>(&m_pJniEnv), JNI_VERSION_1_6);
if (nEnvStat == JNI_EDETACHED) {
std::cout << "GetEnv: not attached. Attempting to attach" << std::endl;
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6; // choose your JNI version
args.name = NULL; // you might want to give the java thread a name
args.group = NULL; // you might want to assign the java thread to a ThreadGroup
if (m_pJvm->AttachCurrentThread(&m_pJniEnv, &args) != 0) {
std::cout << "Failed to attach" << std::endl;
return nullptr;
}
thread_local struct DetachJniOnExit {
~DetachJniOnExit() {
m_pJvm->DetachCurrentThread();
}
};
m_bIsAttachedOnAThread = true;
}
else if (nEnvStat == JNI_OK) {
//
}
else if (nEnvStat == JNI_EVERSION) {
std::cout << "GetEnv: version not supported" << std::endl;
return nullptr;
}
return m_pJniEnv;
}

How to call Java API from NDK C++ thread?

I want to call Java API from NDK C++ thread, but env->FindClass() return 0. But when I call Java API in main thread, it works well. I've already call AttachCurrentThread() in the thread, can anyone help me?
Here is the source code:
JAVA CODE:
public class simple_test extends Activity {
...
// This functin will be called in C++
public void PrintNdkLog(String slog) {
Log.e(logTagNDK, slog);
return;
}
}
C++ CODE:
static JavaVM* g_JavaVM = NULL;
jobject getInstance(JNIEnv *env, jclass obj_class)
{
jmethodID c_id = env->GetMethodID(obj_class, "<init>", "()V");
jobject obj = env->NewObject(obj_class, c_id);
return obj;
}
// JNI OnLoad
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
g_JavaVM = jvm;
return JNI_VERSION_1_6;
}
// Call JAVA API "PrintNdkLog" in this function
void PrintNdkLog(char *lpLog)
{
if (g_JavaVM == NULL)
return;
JNIEnv *env = NULL;
g_JavaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (env == NULL)
return;
jclass cls = env->FindClass("com/myndk/simple_test");
if (cls != 0) // **cls will be 0 when PrintNdkLog() is called in thread**
{
LOGE("FindClass error %p", cls);
}
else
{
jmethodID mid;
jobject obj;
obj = getInstance(env, cls);
mid = env->GetMethodID(cls, "PrintNdkLog", "(Ljava/lang/String;)V");
if (mid != 0)
{
jstring jstrMSG = env->NewStringUTF(lpLog);
env->CallVoidMethod(obj, mid, jstrMSG);
}
}
}
// Call JAVA API in thread
static void* thread_test(void* ptr)
{
JNIEnv *envLocal;
int status = g_JavaVM->GetEnv((void **) &envLocal, JNI_VERSION_1_6);
if (status == JNI_EDETACHED)
{
status = g_JavaVM->AttachCurrentThread(&envLocal, NULL);
if (status != JNI_OK)
LOGE("AttachCurrentThread failed %d",status);
}
PrintNdkLog("bbb"); // This JAVA callback failed, and printed "FindClass error"
}
// Create thread
int NdkThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority)
{
PrintNdkLog("aaa"); // This JAVA callback runs well
pthread_t pid;
pthread_create(&pid, NULL, thread_test, pParam);
}
I have solved it now.
In NDK native thread, only can call static Java API. If you call env->FindClass(), it would trigger an exception.
http://android.wooyd.org/JNIExample gived the detail info.
Suggest to take a look on AttachCurrentThread.
Here is a sample code to do that:
// Global variable
JavaVM *g_jvm = NULL; //Get g_jvm from jni main thread use env->GetJavaVM(&g_jvm);
jobject g_obj = NULL; //Where the java function exist. (some activity)
//Get env in thread function and attach the env
JNIEnv *env;
if(g_jvm->AttachCurrentThread(&env, NULL) != JNI_OK)
{
LOGD("%s: AttachCurrentThread() failed", __FUNCTION__);
}
const char * fnName ="somFunctionInYourJava"; //which should be "pulic void somFunctionInYourJava(String input);"
jstring retStr = env->NewStringUTF(str);
jclass cls = env->GetObjectClass(thiz);
jmethodID messageMe = env->GetMethodID(cls, fnName, "(Ljava/lang/String;)V");
env->CallVoidMethod(thiz, messageMe, retStr);
//Detach thread and release related resource
if(g_jvm->DetachCurrentThread() != JNI_OK)
{
LOGD("%s: DetachCurrentThread() failed", __FUNCTION__);
}

Categories

Resources