We are developing an OpenGL game on android using the NativeActivity class. So far everything went OK, but now we need to access some functionality that only seems to be available from Java.
There are more, but the first one we thought would be useful was accessing the display DPI. As described here the Java code looks like this:
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
And here is the unfortunate corresponding C++ code:
// My checking routine.
#define JNI_ASSERT(jni, cond) { \
if (!(cond)) {\
std::stringstream ss; \
ss << __FILE__ << ":" << __LINE__; \
throw std::runtime_error(ss.str()); \
} \
if (jni->ExceptionCheck()) { \
std::stringstream ss; \
ss << __FILE__ << ":" << __LINE__; \
throw std::runtime_error("Exception: " + ss.str()); \
} \
}
void print_dpi(android_app* app) {
JNIEnv* jni;
app->activity->vm->AttachCurrentThread(&jni, NULL);
jclass activityClass = jni->FindClass("android/app/NativeActivity");
JNI_ASSERT(jni, activityClass);
jmethodID getWindowManager = jni->GetMethodID
( activityClass
, "getWindowManager"
, "()Landroid/view/WindowManager;");
JNI_ASSERT(jni, getWindowManager);
jobject wm = jni->CallObjectMethod(app->activity->clazz, getWindowManager);
JNI_ASSERT(jni, wm);
jclass windowManagerClass = jni->FindClass("android/view/WindowManager");
JNI_ASSERT(jni, windowManagerClass);
jmethodID getDefaultDisplay = jni->GetMethodID( windowManagerClass
, "getDefaultDisplay"
, "()Landroid/view/Display;");
JNI_ASSERT(jni, getDefaultDisplay);
jobject display = jni->CallObjectMethod(wm, getDefaultDisplay);
JNI_ASSERT(jni, display);
jclass displayClass = jni->FindClass("android/view/Display");
JNI_ASSERT(jni, displayClass);
// Check if everything is OK so far, it is, the values it prints
// are sensible.
{
jmethodID getWidth = jni->GetMethodID(displayClass, "getWidth", "()I");
JNI_ASSERT(jni, getWidth);
jmethodID getHeight = jni->GetMethodID(displayClass, "getHeight", "()I");
JNI_ASSERT(jni, getHeight);
int width = jni->CallIntMethod(display, getWidth);
JNI_ASSERT(jni, true);
log("Width: ", width); // Width: 320
int height = jni->CallIntMethod(display, getHeight);
JNI_ASSERT(jni, true);
log("Height: ", height); // Height: 480
}
jclass displayMetricsClass = jni->FindClass("android/util/DisplayMetrics");
JNI_ASSERT(jni, displayMetricsClass);
jmethodID displayMetricsConstructor = jni->GetMethodID( displayMetricsClass
, "<init>", "()V");
JNI_ASSERT(jni, displayMetricsConstructor);
jobject displayMetrics = jni->NewObject( displayMetricsClass
, displayMetricsConstructor);
JNI_ASSERT(jni, displayMetrics);
jmethodID getMetrics = jni->GetMethodID( displayClass
, "getMetrics"
, "(Landroid/util/DisplayMetrics;)V");
JNI_ASSERT(jni, getMetrics);
jni->CallVoidMethod(display, getMetrics, displayMetrics);
JNI_ASSERT(jni, true);
{
jfieldID xdpi_id = jni->GetFieldID(displayMetricsClass, "xdpi", "F");
JNI_ASSERT(jni, xdpi_id);
float xdpi = jni->GetFloatField(displayMetricsClass, xdpi_id);
JNI_ASSERT(jni, true);
log("XDPI: ", xdpi); // XDPI: 0
}
{
jfieldID height_id = jni->GetFieldID( displayMetricsClass
, "heightPixels", "I");
JNI_ASSERT(jni, height_id);
int height = jni->GetIntField(displayMetricsClass, height_id);
JNI_ASSERT(jni, true);
log("Height: ", height); // Height: 0
}
// TODO: Delete objects here.
app->activity->vm->DetachCurrentThread();
}
The code outputs:
Width: 320
Height: 480
XDPI: 0
Height: 0
It's as if the displayMetrics object did not get set in the call
jni->CallVoidMethod(display, getMetrics, displayMetrics);
Is it the case that JNI wouldn't allow me to use an argument as a return value? If so, how can we go around it given that we're using the NativeActivity glue.
Ech, I've been staring at the code for couple of hours and didn't see it. Then left the desk, came back and there it was:
Instead of these two lines
float xdpi = jni->GetFloatField(displayMetricsClass, xdpi_id);
int height = jni->GetIntField(displayMetricsClass, height_id);
I should have used:
float xdpi = jni->GetFloatField(displayMetrics, xdpi_id);
int height = jni->GetIntField(displayMetrics, height_id);
Doh :)
(at least it can serve as an example if someone wants to get DPI the hard way :) )
First off, I must note that I'm not very good with JNI :)
That said, I suspect the problem would be that the displayMetrics
variable must be made a global reference with
displayMetrics = jni->NewGlobalRef(displayMetrics);
or something like that. Remember to unref it with DeleteGlobalRef
. LocalRef might work too...
But if I were to go about solving this problem, I'd wrap the whole thing in a java function and just call that from native and let the java side do most of the function calling - there's also less hopping the java-native fence involved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With