From f291a4ed3a64dd95bf8136f352e5d0554ada4a7c Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Mon, 15 Apr 2024 10:30:18 -0700 Subject: [PATCH] Fix leakage of JNI object references Fixes https://github.com/godotengine/godot/issues/87548 --- .../platform/openxr_android_extension.cpp | 22 +++++++++---------- .../platform/openxr_android_extension.h | 4 ++++ platform/android/api/java_class_wrapper.h | 3 --- platform/android/api/jni_singleton.h | 11 ++++++++++ platform/android/dir_access_jandroid.cpp | 8 +++++++ platform/android/dir_access_jandroid.h | 1 + platform/android/file_access_android.cpp | 17 ++++++++++++++ platform/android/file_access_android.h | 10 +++++++-- .../file_access_filesystem_jandroid.cpp | 8 +++++++ .../android/file_access_filesystem_jandroid.h | 1 + platform/android/java_class_wrapper.cpp | 18 +++++++++------ platform/android/java_godot_io_wrapper.cpp | 11 ++++++++-- platform/android/java_godot_lib_jni.cpp | 12 ++++++---- platform/android/java_godot_view_wrapper.cpp | 1 + platform/android/java_godot_wrapper.cpp | 20 ++++++++++++++--- platform/android/net_socket_android.cpp | 8 +++++++ platform/android/net_socket_android.h | 1 + platform/android/os_android.cpp | 4 ++++ platform/android/tts_android.cpp | 10 +++++++++ platform/android/tts_android.h | 1 + 20 files changed, 139 insertions(+), 32 deletions(-) diff --git a/modules/openxr/extensions/platform/openxr_android_extension.cpp b/modules/openxr/extensions/platform/openxr_android_extension.cpp index de542828c34..04404923ef4 100644 --- a/modules/openxr/extensions/platform/openxr_android_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_android_extension.cpp @@ -36,7 +36,6 @@ #include "os_android.h" #include "thread_jandroid.h" -#include #include #include @@ -48,6 +47,12 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { OpenXRAndroidExtension::OpenXRAndroidExtension() { singleton = this; + + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->GetJavaVM(&vm); + activity_object = env->NewGlobalRef(static_cast(OS::get_singleton())->get_godot_java()->get_activity()); } HashMap OpenXRAndroidExtension::get_requested_extensions() { @@ -66,11 +71,6 @@ void OpenXRAndroidExtension::on_before_instance_created() { } loader_init_extension_available = true; - JNIEnv *env = get_jni_env(); - JavaVM *vm; - env->GetJavaVM(&vm); - jobject activity_object = env->NewGlobalRef(static_cast(OS::get_singleton())->get_godot_java()->get_activity()); - XrLoaderInitInfoAndroidKHR loader_init_info_android = { .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, .next = nullptr, @@ -93,11 +93,6 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void return nullptr; } - JNIEnv *env = get_jni_env(); - JavaVM *vm; - env->GetJavaVM(&vm); - jobject activity_object = env->NewGlobalRef(static_cast(OS::get_singleton())->get_godot_java()->get_activity()); - instance_create_info = { .type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR, .next = p_next_pointer, @@ -109,4 +104,9 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void OpenXRAndroidExtension::~OpenXRAndroidExtension() { singleton = nullptr; + + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(activity_object); } diff --git a/modules/openxr/extensions/platform/openxr_android_extension.h b/modules/openxr/extensions/platform/openxr_android_extension.h index e51b5824e85..61f4b02ab61 100644 --- a/modules/openxr/extensions/platform/openxr_android_extension.h +++ b/modules/openxr/extensions/platform/openxr_android_extension.h @@ -34,6 +34,8 @@ #include "../../util.h" #include "../openxr_extension_wrapper.h" +#include + class OpenXRAndroidExtension : public OpenXRExtensionWrapper { public: static OpenXRAndroidExtension *get_singleton(); @@ -49,6 +51,8 @@ public: private: static OpenXRAndroidExtension *singleton; + JavaVM *vm; + jobject activity_object; bool loader_init_extension_available = false; bool create_instance_extension_available = false; diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index b1481ebf7ba..e21a331ab9d 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -209,8 +209,6 @@ class JavaClassWrapper : public Object { #ifdef ANDROID_ENABLED RBMap> class_cache; friend class JavaClass; - jclass activityClass; - jmethodID findClass; jmethodID getDeclaredMethods; jmethodID getFields; jmethodID getParameterTypes; @@ -229,7 +227,6 @@ class JavaClassWrapper : public Object { jmethodID Long_longValue; jmethodID Float_floatValue; jmethodID Double_doubleValue; - jobject classLoader; bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig); #endif diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index a2d1c081680..5b30c392e7f 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -239,6 +239,17 @@ public: JNISingleton() { #ifdef ANDROID_ENABLED instance = nullptr; +#endif + } + + ~JNISingleton() { +#ifdef ANDROID_ENABLED + if (instance) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(instance); + } #endif } }; diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 972a7dbe6af..ab90527bfaf 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -321,6 +321,14 @@ void DirAccessJAndroid::setup(jobject p_dir_access_handler) { _current_is_hidden = env->GetMethodID(cls, "isCurrentHidden", "(II)Z"); } +void DirAccessJAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(dir_access_handler); +} + DirAccessJAndroid::DirAccessJAndroid() { } diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index 9aaa78f38cc..68578b0fa92 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -89,6 +89,7 @@ public: virtual uint64_t get_space_left() override; static void setup(jobject p_dir_access_handler); + static void terminate(); DirAccessJAndroid(); ~DirAccessJAndroid(); diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index f56eda46944..ae336d6f9d6 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -31,8 +31,12 @@ #include "file_access_android.h" #include "core/string/print_string.h" +#include "thread_jandroid.h" + +#include AAssetManager *FileAccessAndroid::asset_manager = nullptr; +jobject FileAccessAndroid::j_asset_manager = nullptr; String FileAccessAndroid::get_path() const { return path_src; @@ -257,3 +261,16 @@ void FileAccessAndroid::close() { FileAccessAndroid::~FileAccessAndroid() { _close(); } + +void FileAccessAndroid::setup(jobject p_asset_manager) { + JNIEnv *env = get_jni_env(); + j_asset_manager = env->NewGlobalRef(p_asset_manager); + asset_manager = AAssetManager_fromJava(env, j_asset_manager); +} + +void FileAccessAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(j_asset_manager); +} diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index ec613b6687b..6d32d6291db 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -35,9 +35,13 @@ #include #include +#include #include class FileAccessAndroid : public FileAccess { + static AAssetManager *asset_manager; + static jobject j_asset_manager; + mutable AAsset *asset = nullptr; mutable uint64_t len = 0; mutable uint64_t pos = 0; @@ -48,8 +52,6 @@ class FileAccessAndroid : public FileAccess { void _close(); public: - static AAssetManager *asset_manager; - virtual Error open_internal(const String &p_path, int p_mode_flags) override; // open a file virtual bool is_open() const override; // true when file is open @@ -92,6 +94,10 @@ public: virtual void close() override; + static void setup(jobject p_asset_manager); + + static void terminate(); + ~FileAccessAndroid(); }; diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp index 46d9728632d..9b253f823c2 100644 --- a/platform/android/file_access_filesystem_jandroid.cpp +++ b/platform/android/file_access_filesystem_jandroid.cpp @@ -385,6 +385,14 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) { _file_last_modified = env->GetMethodID(cls, "fileLastModified", "(Ljava/lang/String;)J"); } +void FileAccessFilesystemJAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(file_access_handler); +} + void FileAccessFilesystemJAndroid::close() { if (is_open()) { _close(); diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index f33aa64ebe0..80cf1b1122e 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -95,6 +95,7 @@ public: virtual bool file_exists(const String &p_path) override; ///< return true if a file exists static void setup(jobject p_file_access_handler); + static void terminate(); virtual uint64_t _get_modified_time(const String &p_file) override; virtual BitField _get_unix_permissions(const String &p_file) override { return 0; } diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index d6455cbf1c7..a309a6ab740 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -1157,50 +1157,54 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL(env); - jclass activity = env->FindClass("android/app/Activity"); - jmethodID getClassLoader = env->GetMethodID(activity, "getClassLoader", "()Ljava/lang/ClassLoader;"); - classLoader = env->CallObjectMethod(p_activity, getClassLoader); - classLoader = (jclass)env->NewGlobalRef(classLoader); - jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); - findClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - jclass bclass = env->FindClass("java/lang/Class"); getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"); getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;"); Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Method"); getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;"); getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;"); getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Field"); Field_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); Field_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); Field_get = env->GetMethodID(bclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Boolean"); Boolean_booleanValue = env->GetMethodID(bclass, "booleanValue", "()Z"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Byte"); Byte_byteValue = env->GetMethodID(bclass, "byteValue", "()B"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Character"); Character_characterValue = env->GetMethodID(bclass, "charValue", "()C"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Short"); Short_shortValue = env->GetMethodID(bclass, "shortValue", "()S"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Integer"); Integer_integerValue = env->GetMethodID(bclass, "intValue", "()I"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Long"); Long_longValue = env->GetMethodID(bclass, "longValue", "()J"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Float"); Float_floatValue = env->GetMethodID(bclass, "floatValue", "()F"); + env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/Double"); Double_doubleValue = env->GetMethodID(bclass, "doubleValue", "()D"); + env->DeleteLocalRef(bclass); } diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 10716a5c79c..49913b9c30a 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -70,7 +70,11 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc } GodotIOJavaWrapper::~GodotIOJavaWrapper() { - // nothing to do here for now + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(godot_io_instance); } jobject GodotIOJavaWrapper::get_instance() { @@ -82,7 +86,9 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, ERR_UNAVAILABLE); jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); - return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; + Error result = env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; + env->DeleteLocalRef(jStr); + return result; } else { return ERR_UNAVAILABLE; } @@ -220,6 +226,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_type, int p_max ERR_FAIL_NULL(env); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_type, p_max_input_length, p_cursor_start, p_cursor_end); + env->DeleteLocalRef(jStr); } } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 2215f706c59..6cab7e74fd2 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -95,6 +95,13 @@ static void _terminate(JNIEnv *env, bool p_restart = false) { if (godot_io_java) { delete godot_io_java; } + + TTS_Android::terminate(); + FileAccessAndroid::terminate(); + DirAccessJAndroid::terminate(); + FileAccessFilesystemJAndroid::terminate(); + NetSocketAndroid::terminate(); + if (godot_java) { if (!restart_on_cleanup) { if (p_restart) { @@ -125,10 +132,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv init_thread_jandroid(jvm, env); - jobject amgr = env->NewGlobalRef(p_asset_manager); - - FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr); - + FileAccessAndroid::setup(p_asset_manager); DirAccessJAndroid::setup(p_directory_access_handler); FileAccessFilesystemJAndroid::setup(p_file_access_handler); NetSocketAndroid::setup(p_net_utils); diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index a95f762e016..04424c1179c 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -95,6 +95,7 @@ void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data()); env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y); + env->DeleteLocalRef(jImagePath); } } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 3c950bb1b1d..61be6fc5db3 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -172,6 +172,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); + env->DeleteLocalRef(jStrMessage); + env->DeleteLocalRef(jStrTitle); } } @@ -231,6 +233,7 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) { ERR_FAIL_NULL(env); jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); env->CallVoidMethod(godot_instance, _set_clipboard, jStr); + env->DeleteLocalRef(jStr); } } @@ -253,7 +256,9 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, false); jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); - return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); + bool result = env->CallBooleanMethod(godot_instance, _request_permission, jStrName); + env->DeleteLocalRef(jStrName); + return result; } else { return false; } @@ -340,7 +345,9 @@ int GodotJavaWrapper::create_new_godot_instance(const List &args) { ERR_FAIL_NULL_V(env, 0); jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); for (int i = 0; i < args.size(); i++) { - env->SetObjectArrayElement(jargs, i, env->NewStringUTF(args[i].utf8().get_data())); + jstring j_arg = env->NewStringUTF(args[i].utf8().get_data()); + env->SetObjectArrayElement(jargs, i, j_arg); + env->DeleteLocalRef(j_arg); } return env->CallIntMethod(godot_instance, _create_new_godot_instance, jargs); } else { @@ -355,6 +362,8 @@ void GodotJavaWrapper::begin_benchmark_measure(const String &p_context, const St jstring j_context = env->NewStringUTF(p_context.utf8().get_data()); jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_context, j_label); + env->DeleteLocalRef(j_context); + env->DeleteLocalRef(j_label); } } @@ -365,6 +374,8 @@ void GodotJavaWrapper::end_benchmark_measure(const String &p_context, const Stri jstring j_context = env->NewStringUTF(p_context.utf8().get_data()); jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_context, j_label); + env->DeleteLocalRef(j_context); + env->DeleteLocalRef(j_label); } } @@ -374,6 +385,7 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { ERR_FAIL_NULL(env); jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data()); env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file); + env->DeleteLocalRef(j_benchmark_file); } } @@ -383,7 +395,9 @@ bool GodotJavaWrapper::has_feature(const String &p_feature) const { ERR_FAIL_NULL_V(env, false); jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data()); - return env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + bool result = env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + env->DeleteLocalRef(j_feature); + return result; } else { return false; } diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index a2befdc9bed..8f0ee51face 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -49,6 +49,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) { _multicast_lock_release = env->GetMethodID(cls, "multicastLockRelease", "()V"); } +void NetSocketAndroid::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(net_utils); +} + void NetSocketAndroid::multicast_lock_acquire() { if (_multicast_lock_acquire) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h index e5f46d3236e..26cb2d4e3d4 100644 --- a/platform/android/net_socket_android.h +++ b/platform/android/net_socket_android.h @@ -63,6 +63,7 @@ protected: public: static void make_default(); static void setup(jobject p_net_utils); + static void terminate(); virtual void close(); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index bf6b7a73724..dcbb84ae8f2 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -736,6 +736,10 @@ void OS_Android::benchmark_dump() { } bool OS_Android::_check_internal_feature_support(const String &p_feature) { + if (p_feature == "macos" || p_feature == "web_ios" || p_feature == "web_macos" || p_feature == "windows") { + return false; + } + if (p_feature == "system_fonts") { return true; } diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp index 93517d80453..be85e47972b 100644 --- a/platform/android/tts_android.cpp +++ b/platform/android/tts_android.cpp @@ -77,6 +77,14 @@ void TTS_Android::setup(jobject p_tts) { } } +void TTS_Android::terminate() { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(cls); + env->DeleteGlobalRef(tts); +} + void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) { ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); if (ids.has(p_id)) { @@ -170,6 +178,8 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum jstring jStrT = env->NewStringUTF(p_text.utf8().get_data()); jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data()); env->CallVoidMethod(tts, _speak, jStrT, jStrV, CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, p_interrupt); + env->DeleteLocalRef(jStrT); + env->DeleteLocalRef(jStrV); } } diff --git a/platform/android/tts_android.h b/platform/android/tts_android.h index 39efef6ed11..4cc7c128461 100644 --- a/platform/android/tts_android.h +++ b/platform/android/tts_android.h @@ -57,6 +57,7 @@ class TTS_Android { public: static void setup(jobject p_tts); + static void terminate(); static void _java_utterance_callback(int p_event, int p_id, int p_pos); static bool is_speaking();