Android’s JNI tips page mentions this FAQ: Why didn’t FindClass find my class?
They mention multiple solutions and the last option there is this one:
Cache a reference to the ClassLoader object somewhere handy, and issue
loadClass calls directly. This requires some effort.
So, I tried to get it working and it seems that no matter what, this method simply does not work for me. Eventually, I figured how to use ClassLoader but it won’t work if from a native thread I try to loadClass that hasn’t been touched/loaded yet. Essentially, it’s the identical to env->FindClass in behavior when called from a native thread, with the exception that it won’t return 0 for classes that were already use in the app. Any idea if I didn’t get it right, or it’s impossible to access classes from a native thread that weren’t used/loaded yet.
EDIT: I’ll give more info to explain what exactly I mean. There is regular JNI env->FindClass(className)
, and another one that I wrote myFindClass(env, className)
that uses cached ClassLoader->loadClass
.
The class that I’m trying to access from native c/c++ is “com/noname/TestClient”. Inside myFindClass I also use env->FindClass and log value that it returns:
jclass myFindClass(JNIEnv * env, const char* name)
{
...
jclass c0 = env->FindClass(name);
jclass c1 = (jclass)env->CallObjectMethod(ClassLoader,
MID_loadClass, envNewStringUTF(name));
dlog("myFindClass("%s") => c0:%p, c1:%p, c0 and c1 are same: %d",
name, c0, c1, env->IsSameObject(c0, c1));
...
}
Then, I have these 3 combinations to explain the issue.
1)
//inside JNI_OnLoad thread
myFindClass(env, "com/noname/TestClient");
...
//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
I get this logcat:
myFindClass(“com/noname/TestClent”) => c0:0x41b64558, c1:0x41b64558,
c0 and c1 are same: 1
…
myFindClass(“com/noname/TestClent”) => c0:0,
c1:0x41b64558, c0 and c1 are same: 0
2)
//inside JNI_OnLoad thread
env->FindClass("com/noname/TestClient");
...
//inside native thread created by pthread_create
myFindClass("com/noname/TestClient");
I get this logcat:
myFindClass(“com/noname/TestClent”) => c0:0, c1:0x41b64558, c0 and c1 are same: 0
3)
//inside JNI_OnLoad thread
//"com/noname/TestClient" isn't touched from JNI_OnLoad.
...
//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
I get this logcat:
myFindClass(“com/noname/TestClent”) => c0:0, c1:0, c0 and c1 are same: 1
Basically, my issue is that ClassLoader doesn’t find my class in the 3rd case. Is it a bug? What can be done to fix the problem?
EDIT2:
On top of that, it seems that ClassLoader::loadClass is plainly buggy. If I ask myFindClass(“noname/TestClent”) then it returns some garbage, and when I use that returned jclass in any way the app crashes.
3
2 Answers
After much trying and crashing of my app, a colleague and I managed to cache and succesfully use the class loader in another, native, thread. The code we used is shown below (C++11, but easily converted to C++2003), posted here since we couldn’t find any examples of the aforementioned “Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.”. Calling findClass worked perfectly when called from a thread different from the one of JNI_OnLoad. I hope this helps.
JavaVM* gJvm = nullptr;
static jobject gClassLoader;
static jmethodID gFindClassMethod;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
gJvm = pjvm; // cache the JavaVM pointer
auto env = getEnv();
//replace with one of your classes in the line below
auto randomClass = env->FindClass("com/example/RandomClass");
jclass classClass = env->GetObjectClass(randomClass);
auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
"()Ljava/lang/ClassLoader;");
gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
return JNI_VERSION_1_6;
}
jclass findClass(const char* name) {
return static_cast(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
}
JNIEnv* getEnv() {
JNIEnv *env;
int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if(status < 0) {
status = gJvm->AttachCurrentThread(&env, NULL);
if(status < 0) {
return nullptr;
}
}
return env;
}
15
-
7
ClassLoader.loadClass()
does not return null when the class is not found. It throws an exception, which means the return value is undefined. You must check for exceptions after calling CallObjectMethod
, and you must not use the return value if an exception has been raised. (This is true in general — any Call*Method
invocation should be followed by ExceptionCheck
or ExceptionOccurred
, and you can see the exception in the log with ExceptionDescribe
.)
– fadden
Commented
May 9, 2013 at 16:37
-
6
I understand; I just wanted to make sure that anybody who did copy it added the necessary checks to JNI_OnLoad
and findClass
(especially the latter — you could convert an exception to NULL there, and clear the exception, to get the semantics you want). Note also you don’t need to call FindClass
to find java.lang.Class
; you can just use GetObjectClass
on the jclass
you already have (it’s faster and never fails on a valid object). Have you tried this on Android >= 4.0? Looks like you need a NewGlobalRef
on gFindClassMethod
.
– fadden
Commented
May 13, 2013 at 16:35
-
1
Cool. FWIW, I meant gClassLoader — gFindClassMethod is a jmethodID.
– fadden
Commented
May 28, 2013 at 16:32
-
14
Excelent answer… Just a comment, this didn’t worked for me because of this : stackoverflow.com/questions/14765776/…. After calling NewGlobalRef() on the gClassLoader object, this solved my problem. Thanks!
– LarryPel
Commented
Sep 24, 2013 at 2:22
-
7
thanks for the solution. note you have to call NewGlobalRef()
BEFORE this gets stored globally. calling NewGlobalRef()
from a different thread isn’t going to work. Do it like such: gClassLoader = env->NewGlobalRef(env->CallObjectMethod(myClass, getClassLoaderMethod));
For more details check here: android-developers.blogspot.kr/2011/11/…
Commented
Oct 14, 2015 at 2:00
Try attaching your native thread to the JVM first.
The pointer to jvm you can obtain first thing in JNI_OnLoad
env->GetJavaVM(&jvm);
Then from your native thread
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
Then use that env
for FindClass
1
-
9
Alex, off course I do that, without it 1) and 2) wouldn’t work 🙂
– Pavel P
Commented
Nov 7, 2012 at 6:27
Not the answer you’re looking for? Browse other questions tagged
文章来源于互联网:FindClass from any thread in Android JNI
Yes, that’s normal, Android apps do not use the system class loader by default. Just cache all that you need in
JNI_OnLoad()
and that will take care of that.Commented
Nov 7, 2012 at 13:24
Re: EDIT2: sounds like the method threw an exception, at which point the return value is undefined.
loadClass()
never returns null; it either returns the class reference or it throws an exception.Commented
May 9, 2013 at 16:39
@fadden it could be. I don’t remember exactly what was the issue, but I solved it eventually.
Commented
May 9, 2013 at 19:41
|