Android: использование JNI от NativeActivity

Мы разрабатываем игру OpenGL на android, используя класс NativeActivity . До сих пор все прошло хорошо, но теперь нам нужно получить доступ к некоторым функциям, которые, как представляется, доступны только на Java.

Есть больше, но первый, который, по нашему мнению, будет полезен, – это доступ к дисплею DPI. Как описано здесь, код Java выглядит следующим образом:

DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); 

И вот неудачный соответствующий код на C ++:

 // 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(); } - // 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(); } 

Выходы кода:

 Width: 320 Height: 480 XDPI: 0 Height: 0 

Это как если бы объект displayMetrics не был установлен в вызове

 jni->CallVoidMethod(display, getMetrics, displayMetrics); 

Это тот случай, когда JNI не разрешил мне использовать аргумент как возвращаемое значение? Если да, то как мы можем обойти это, учитывая, что мы используем клей NativeActivity.

Solutions Collecting From Web of "Android: использование JNI от NativeActivity"

Эх, я смотрел на код пару часов и не видел его. Затем вышел из-за стола, вернулся и вот он:

Вместо этих двух линий

 float xdpi = jni->GetFloatField(displayMetricsClass, xdpi_id); int height = jni->GetIntField(displayMetricsClass, height_id); 

Я должен был использовать:

 float xdpi = jni->GetFloatField(displayMetrics, xdpi_id); int height = jni->GetIntField(displayMetrics, height_id); 

Дох 🙂

(По крайней мере, это может служить примером, если кто-то хочет получить ДОИ на жестком пути :))

Прежде всего, я должен отметить, что я не очень хорошо разбираюсь в JNI 🙂

Тем не менее, я подозреваю, что проблема заключается в том, что переменная displayMetrics должна быть сделана глобальной ссылкой с

 displayMetrics = jni->NewGlobalRef(displayMetrics); 

или что-то типа того. Не забудьте удалить его с помощью DeleteGlobalRef . LocalRef тоже может работать …

Но если бы я решил решить эту проблему, я бы обернул все это в java-функцию и просто назову это из native, и пусть сторона java выполняет большую часть вызова функции – там также меньше перескакивает java-native fence ,