I’m a big fan of the new native activity functionality available for Android NDK users.
While it does limit your target audience to Android 2.3 and above (currently comprising less than 10% of the Android smartphone market, albeit covering 100% of the Android tablet market), it can represent a significant efficiency improvement for heavy users of the native development kit, removing most expensive JNI boundary crossings. Native activities empower and speed up native event processing.
I liberally use the NDK, and as it has been iteratively empowered by the Android developers I’ve found my motivation to build Android projects growing.
Despite years primarily dealing with higher languages and managed runtimes, I really love C++ and unmanaged code. Maybe it’s some early professional life trauma that has given me a C++ Stockholm syndrome.
The NDK importantly allows me to effectively share code across platforms, which is especially important given the state of the Android emulator: Aside from notoriously poor performance, the emulator is missing large swaths of functionality, including significant Open GL ES 2.0 functionality, ruling out its use for many graphically advanced applications.
So instead I build an application where most of the code is shared between an Android application NDK library, and a Win32 application. Conceivably an iOS port would not be that far removed, which was an important consideration.
On the Win32 front you just need to add an OpenGL ES 2.0 emulator to intercept and rewrite graphics calls and you have a world where you can build out most of your core functionality in Visual Studio, appropriately stubbing out platform specific code like mouse/touchscreen inputs, only occasionally building in the Android NDK and running it on an actual Android device just to make sure everything still works.
It is a solution that has worked wonders for my enjoyment of non-trivial Android development.
However the NDK remains incomplete. While each iteration has seen a much richer, more self-sustaining NDK, there are certain tasks that are better done, if not only possible from, Java in the Dalvik VM.
So you need to make a JNI call from the Native Activity to Java code, whether to the system class library, or to your own custom code.
The former is easy: From the ANativeActivity instance (the definition can be found in native_activity.h), which if you use the native app glue can be found in the activity member of the android_app structure you’re passed, use the vm member to obtain a thread-attached JNIEnv instance.
Attaching the thread is necessary given that — again if you use the native glue — your code is running in a separate thread.
Now that you have the JNIEnv you can use the normal FindClass functionality to get system classes, and normal JNI functionality to instantiate and call Java functionality.
Things get a little dicier when you want to use the class loader to load your own classes, however, which will often be the case given that normally you’ll want to limit to jumps between the NDK and the SDK, with entrypoints that do sets of functionality and then return finalized results after lots of chatter between Java objects. It would be a serious pain to do all of that from the NDK.
FindClass off of the JNIEnv will not give satisfaction. It won’t find your classes. The reasons are because the context of the JNIEnv FindClass has no awareness that your classes exist.
This seriously vexed me last night. It is one of those frustrating problems that I’m sure have a simple answer, but many searches offered no clues. I found one guy who had the same problem, apparently finding a solution, however they left the question open with an obtuse “found my solution” claim.
I believe that people in such situations often are so irritated by the lack of assistance that they decide to do the same in return to the collective. Alternately if the answer isn’t easily found, I think some are motivated by the perception that it’s a competitive advantage for it to remain so.
In the end it turned out to be painfully obvious. You need to call the getClassLoader method of the android.app.NativeActivity instance that you were given in the clazz member of the activity member previously discussed. This will give you a class loader that has contextual awareness of your own custom code.
jclass activityClass = jni->FindClass("android/app/NativeActivity");
jmethodID getClassLoader = jni->GetMethodID(activityClass,"getClassLoader", "()Ljava/lang/ClassLoader;");
jobject cls = jni->CallObjectMethod(state->activity->clazz, getClassLoader);
jclass classLoader = jni->FindClass("java/lang/ClassLoader");
jmethodID findClass = jni->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
jstring strClassName = jni->NewStringUTF("com/tewdew/ClassIWant");
jclass classIWant = (jclass)jni->CallObjectMethod(cls, findClass, strClassName);
Add in error handling, free the string and detach the thread, and you have a class loader that empowers your NDK code.
tl;dr; — you need to get the class loader from your native activity. The default class loader you get otherwise is effectively just a system class loader.
I hope this proves helpful for future searchers.
- tewdew posted this