Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Using JNI from NativeActivity

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.

like image 587
Peter Jankuliak Avatar asked Nov 06 '12 10:11

Peter Jankuliak


2 Answers

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 :) )

like image 118
Peter Jankuliak Avatar answered Nov 02 '22 11:11

Peter Jankuliak


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.

like image 36
Zharf Avatar answered Nov 02 '22 10:11

Zharf