Browse Source

Fix leakage of JNI object references

Fixes https://github.com/godotengine/godot/issues/87548
Fredia Huya-Kouadio 1 year ago
parent
commit
f291a4ed3a

+ 11 - 11
modules/openxr/extensions/platform/openxr_android_extension.cpp

@@ -36,7 +36,6 @@
 #include "os_android.h"
 #include "os_android.h"
 #include "thread_jandroid.h"
 #include "thread_jandroid.h"
 
 
-#include <jni.h>
 #include <openxr/openxr.h>
 #include <openxr/openxr.h>
 #include <openxr/openxr_platform.h>
 #include <openxr/openxr_platform.h>
 
 
@@ -48,6 +47,12 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() {
 
 
 OpenXRAndroidExtension::OpenXRAndroidExtension() {
 OpenXRAndroidExtension::OpenXRAndroidExtension() {
 	singleton = this;
 	singleton = this;
+
+	JNIEnv *env = get_jni_env();
+	ERR_FAIL_NULL(env);
+
+	env->GetJavaVM(&vm);
+	activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
 }
 }
 
 
 HashMap<String, bool *> OpenXRAndroidExtension::get_requested_extensions() {
 HashMap<String, bool *> OpenXRAndroidExtension::get_requested_extensions() {
@@ -66,11 +71,6 @@ void OpenXRAndroidExtension::on_before_instance_created() {
 	}
 	}
 	loader_init_extension_available = true;
 	loader_init_extension_available = true;
 
 
-	JNIEnv *env = get_jni_env();
-	JavaVM *vm;
-	env->GetJavaVM(&vm);
-	jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
-
 	XrLoaderInitInfoAndroidKHR loader_init_info_android = {
 	XrLoaderInitInfoAndroidKHR loader_init_info_android = {
 		.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR,
 		.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR,
 		.next = nullptr,
 		.next = nullptr,
@@ -93,11 +93,6 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void
 		return nullptr;
 		return nullptr;
 	}
 	}
 
 
-	JNIEnv *env = get_jni_env();
-	JavaVM *vm;
-	env->GetJavaVM(&vm);
-	jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
-
 	instance_create_info = {
 	instance_create_info = {
 		.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR,
 		.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR,
 		.next = p_next_pointer,
 		.next = p_next_pointer,
@@ -109,4 +104,9 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void
 
 
 OpenXRAndroidExtension::~OpenXRAndroidExtension() {
 OpenXRAndroidExtension::~OpenXRAndroidExtension() {
 	singleton = nullptr;
 	singleton = nullptr;
+
+	JNIEnv *env = get_jni_env();
+	ERR_FAIL_NULL(env);
+
+	env->DeleteGlobalRef(activity_object);
 }
 }

+ 4 - 0
modules/openxr/extensions/platform/openxr_android_extension.h

@@ -34,6 +34,8 @@
 #include "../../util.h"
 #include "../../util.h"
 #include "../openxr_extension_wrapper.h"
 #include "../openxr_extension_wrapper.h"
 
 
+#include <jni.h>
+
 class OpenXRAndroidExtension : public OpenXRExtensionWrapper {
 class OpenXRAndroidExtension : public OpenXRExtensionWrapper {
 public:
 public:
 	static OpenXRAndroidExtension *get_singleton();
 	static OpenXRAndroidExtension *get_singleton();
@@ -49,6 +51,8 @@ public:
 private:
 private:
 	static OpenXRAndroidExtension *singleton;
 	static OpenXRAndroidExtension *singleton;
 
 
+	JavaVM *vm;
+	jobject activity_object;
 	bool loader_init_extension_available = false;
 	bool loader_init_extension_available = false;
 	bool create_instance_extension_available = false;
 	bool create_instance_extension_available = false;
 
 

+ 0 - 3
platform/android/api/java_class_wrapper.h

@@ -209,8 +209,6 @@ class JavaClassWrapper : public Object {
 #ifdef ANDROID_ENABLED
 #ifdef ANDROID_ENABLED
 	RBMap<String, Ref<JavaClass>> class_cache;
 	RBMap<String, Ref<JavaClass>> class_cache;
 	friend class JavaClass;
 	friend class JavaClass;
-	jclass activityClass;
-	jmethodID findClass;
 	jmethodID getDeclaredMethods;
 	jmethodID getDeclaredMethods;
 	jmethodID getFields;
 	jmethodID getFields;
 	jmethodID getParameterTypes;
 	jmethodID getParameterTypes;
@@ -229,7 +227,6 @@ class JavaClassWrapper : public Object {
 	jmethodID Long_longValue;
 	jmethodID Long_longValue;
 	jmethodID Float_floatValue;
 	jmethodID Float_floatValue;
 	jmethodID Double_doubleValue;
 	jmethodID Double_doubleValue;
-	jobject classLoader;
 
 
 	bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig);
 	bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig);
 #endif
 #endif

+ 11 - 0
platform/android/api/jni_singleton.h

@@ -239,6 +239,17 @@ public:
 	JNISingleton() {
 	JNISingleton() {
 #ifdef ANDROID_ENABLED
 #ifdef ANDROID_ENABLED
 		instance = nullptr;
 		instance = nullptr;
+#endif
+	}
+
+	~JNISingleton() {
+#ifdef ANDROID_ENABLED
+		if (instance) {
+			JNIEnv *env = get_jni_env();
+			ERR_FAIL_NULL(env);
+
+			env->DeleteGlobalRef(instance);
+		}
 #endif
 #endif
 	}
 	}
 };
 };

+ 8 - 0
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");
 	_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() {
 DirAccessJAndroid::DirAccessJAndroid() {
 }
 }
 
 

+ 1 - 0
platform/android/dir_access_jandroid.h

@@ -89,6 +89,7 @@ public:
 	virtual uint64_t get_space_left() override;
 	virtual uint64_t get_space_left() override;
 
 
 	static void setup(jobject p_dir_access_handler);
 	static void setup(jobject p_dir_access_handler);
+	static void terminate();
 
 
 	DirAccessJAndroid();
 	DirAccessJAndroid();
 	~DirAccessJAndroid();
 	~DirAccessJAndroid();

+ 17 - 0
platform/android/file_access_android.cpp

@@ -31,8 +31,12 @@
 #include "file_access_android.h"
 #include "file_access_android.h"
 
 
 #include "core/string/print_string.h"
 #include "core/string/print_string.h"
+#include "thread_jandroid.h"
+
+#include <android/asset_manager_jni.h>
 
 
 AAssetManager *FileAccessAndroid::asset_manager = nullptr;
 AAssetManager *FileAccessAndroid::asset_manager = nullptr;
+jobject FileAccessAndroid::j_asset_manager = nullptr;
 
 
 String FileAccessAndroid::get_path() const {
 String FileAccessAndroid::get_path() const {
 	return path_src;
 	return path_src;
@@ -257,3 +261,16 @@ void FileAccessAndroid::close() {
 FileAccessAndroid::~FileAccessAndroid() {
 FileAccessAndroid::~FileAccessAndroid() {
 	_close();
 	_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);
+}

+ 8 - 2
platform/android/file_access_android.h

@@ -35,9 +35,13 @@
 
 
 #include <android/asset_manager.h>
 #include <android/asset_manager.h>
 #include <android/log.h>
 #include <android/log.h>
+#include <jni.h>
 #include <stdio.h>
 #include <stdio.h>
 
 
 class FileAccessAndroid : public FileAccess {
 class FileAccessAndroid : public FileAccess {
+	static AAssetManager *asset_manager;
+	static jobject j_asset_manager;
+
 	mutable AAsset *asset = nullptr;
 	mutable AAsset *asset = nullptr;
 	mutable uint64_t len = 0;
 	mutable uint64_t len = 0;
 	mutable uint64_t pos = 0;
 	mutable uint64_t pos = 0;
@@ -48,8 +52,6 @@ class FileAccessAndroid : public FileAccess {
 	void _close();
 	void _close();
 
 
 public:
 public:
-	static AAssetManager *asset_manager;
-
 	virtual Error open_internal(const String &p_path, int p_mode_flags) override; // open a file
 	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
 	virtual bool is_open() const override; // true when file is open
 
 
@@ -92,6 +94,10 @@ public:
 
 
 	virtual void close() override;
 	virtual void close() override;
 
 
+	static void setup(jobject p_asset_manager);
+
+	static void terminate();
+
 	~FileAccessAndroid();
 	~FileAccessAndroid();
 };
 };
 
 

+ 8 - 0
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");
 	_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() {
 void FileAccessFilesystemJAndroid::close() {
 	if (is_open()) {
 	if (is_open()) {
 		_close();
 		_close();

+ 1 - 0
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
 	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 setup(jobject p_file_access_handler);
+	static void terminate();
 
 
 	virtual uint64_t _get_modified_time(const String &p_file) override;
 	virtual uint64_t _get_modified_time(const String &p_file) override;
 	virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
 	virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }

+ 11 - 7
platform/android/java_class_wrapper.cpp

@@ -1157,50 +1157,54 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
 	JNIEnv *env = get_jni_env();
 	JNIEnv *env = get_jni_env();
 	ERR_FAIL_NULL(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");
 	jclass bclass = env->FindClass("java/lang/Class");
 	getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
 	getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
 	getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
 	getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
 	Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
 	Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/reflect/Method");
 	bclass = env->FindClass("java/lang/reflect/Method");
 	getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
 	getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
 	getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
 	getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
 	getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
 	getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
 	getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
 	getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/reflect/Field");
 	bclass = env->FindClass("java/lang/reflect/Field");
 	Field_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
 	Field_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
 	Field_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
 	Field_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
 	Field_get = env->GetMethodID(bclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
 	Field_get = env->GetMethodID(bclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Boolean");
 	bclass = env->FindClass("java/lang/Boolean");
 	Boolean_booleanValue = env->GetMethodID(bclass, "booleanValue", "()Z");
 	Boolean_booleanValue = env->GetMethodID(bclass, "booleanValue", "()Z");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Byte");
 	bclass = env->FindClass("java/lang/Byte");
 	Byte_byteValue = env->GetMethodID(bclass, "byteValue", "()B");
 	Byte_byteValue = env->GetMethodID(bclass, "byteValue", "()B");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Character");
 	bclass = env->FindClass("java/lang/Character");
 	Character_characterValue = env->GetMethodID(bclass, "charValue", "()C");
 	Character_characterValue = env->GetMethodID(bclass, "charValue", "()C");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Short");
 	bclass = env->FindClass("java/lang/Short");
 	Short_shortValue = env->GetMethodID(bclass, "shortValue", "()S");
 	Short_shortValue = env->GetMethodID(bclass, "shortValue", "()S");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Integer");
 	bclass = env->FindClass("java/lang/Integer");
 	Integer_integerValue = env->GetMethodID(bclass, "intValue", "()I");
 	Integer_integerValue = env->GetMethodID(bclass, "intValue", "()I");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Long");
 	bclass = env->FindClass("java/lang/Long");
 	Long_longValue = env->GetMethodID(bclass, "longValue", "()J");
 	Long_longValue = env->GetMethodID(bclass, "longValue", "()J");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Float");
 	bclass = env->FindClass("java/lang/Float");
 	Float_floatValue = env->GetMethodID(bclass, "floatValue", "()F");
 	Float_floatValue = env->GetMethodID(bclass, "floatValue", "()F");
+	env->DeleteLocalRef(bclass);
 
 
 	bclass = env->FindClass("java/lang/Double");
 	bclass = env->FindClass("java/lang/Double");
 	Double_doubleValue = env->GetMethodID(bclass, "doubleValue", "()D");
 	Double_doubleValue = env->GetMethodID(bclass, "doubleValue", "()D");
+	env->DeleteLocalRef(bclass);
 }
 }

+ 9 - 2
platform/android/java_godot_io_wrapper.cpp

@@ -70,7 +70,11 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
 }
 }
 
 
 GodotIOJavaWrapper::~GodotIOJavaWrapper() {
 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() {
 jobject GodotIOJavaWrapper::get_instance() {
@@ -82,7 +86,9 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
 		JNIEnv *env = get_jni_env();
 		JNIEnv *env = get_jni_env();
 		ERR_FAIL_NULL_V(env, ERR_UNAVAILABLE);
 		ERR_FAIL_NULL_V(env, ERR_UNAVAILABLE);
 		jstring jStr = env->NewStringUTF(p_uri.utf8().get_data());
 		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 {
 	} else {
 		return ERR_UNAVAILABLE;
 		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);
 		ERR_FAIL_NULL(env);
 		jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
 		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->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_type, p_max_input_length, p_cursor_start, p_cursor_end);
+		env->DeleteLocalRef(jStr);
 	}
 	}
 }
 }
 
 

+ 8 - 4
platform/android/java_godot_lib_jni.cpp

@@ -95,6 +95,13 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
 	if (godot_io_java) {
 	if (godot_io_java) {
 		delete godot_io_java;
 		delete godot_io_java;
 	}
 	}
+
+	TTS_Android::terminate();
+	FileAccessAndroid::terminate();
+	DirAccessJAndroid::terminate();
+	FileAccessFilesystemJAndroid::terminate();
+	NetSocketAndroid::terminate();
+
 	if (godot_java) {
 	if (godot_java) {
 		if (!restart_on_cleanup) {
 		if (!restart_on_cleanup) {
 			if (p_restart) {
 			if (p_restart) {
@@ -125,10 +132,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv
 
 
 	init_thread_jandroid(jvm, env);
 	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);
 	DirAccessJAndroid::setup(p_directory_access_handler);
 	FileAccessFilesystemJAndroid::setup(p_file_access_handler);
 	FileAccessFilesystemJAndroid::setup(p_file_access_handler);
 	NetSocketAndroid::setup(p_net_utils);
 	NetSocketAndroid::setup(p_net_utils);

+ 1 - 0
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());
 		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->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y);
+		env->DeleteLocalRef(jImagePath);
 	}
 	}
 }
 }
 
 

+ 17 - 3
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 jStrMessage = env->NewStringUTF(p_message.utf8().get_data());
 		jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data());
 		jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data());
 		env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle);
 		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);
 		ERR_FAIL_NULL(env);
 		jstring jStr = env->NewStringUTF(p_text.utf8().get_data());
 		jstring jStr = env->NewStringUTF(p_text.utf8().get_data());
 		env->CallVoidMethod(godot_instance, _set_clipboard, jStr);
 		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();
 		JNIEnv *env = get_jni_env();
 		ERR_FAIL_NULL_V(env, false);
 		ERR_FAIL_NULL_V(env, false);
 		jstring jStrName = env->NewStringUTF(p_name.utf8().get_data());
 		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 {
 	} else {
 		return false;
 		return false;
 	}
 	}
@@ -340,7 +345,9 @@ int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) {
 		ERR_FAIL_NULL_V(env, 0);
 		ERR_FAIL_NULL_V(env, 0);
 		jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
 		jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
 		for (int i = 0; i < args.size(); i++) {
 		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);
 		return env->CallIntMethod(godot_instance, _create_new_godot_instance, jargs);
 	} else {
 	} 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_context = env->NewStringUTF(p_context.utf8().get_data());
 		jstring j_label = env->NewStringUTF(p_label.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->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_context = env->NewStringUTF(p_context.utf8().get_data());
 		jstring j_label = env->NewStringUTF(p_label.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->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);
 		ERR_FAIL_NULL(env);
 		jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data());
 		jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data());
 		env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file);
 		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);
 		ERR_FAIL_NULL_V(env, false);
 
 
 		jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data());
 		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 {
 	} else {
 		return false;
 		return false;
 	}
 	}

+ 8 - 0
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");
 	_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() {
 void NetSocketAndroid::multicast_lock_acquire() {
 	if (_multicast_lock_acquire) {
 	if (_multicast_lock_acquire) {
 		JNIEnv *env = get_jni_env();
 		JNIEnv *env = get_jni_env();

+ 1 - 0
platform/android/net_socket_android.h

@@ -63,6 +63,7 @@ protected:
 public:
 public:
 	static void make_default();
 	static void make_default();
 	static void setup(jobject p_net_utils);
 	static void setup(jobject p_net_utils);
+	static void terminate();
 
 
 	virtual void close();
 	virtual void close();
 
 

+ 4 - 0
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) {
 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") {
 	if (p_feature == "system_fonts") {
 		return true;
 		return true;
 	}
 	}

+ 10 - 0
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) {
 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.");
 	ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
 	if (ids.has(p_id)) {
 	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 jStrT = env->NewStringUTF(p_text.utf8().get_data());
 		jstring jStrV = env->NewStringUTF(p_voice.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->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);
 	}
 	}
 }
 }
 
 

+ 1 - 0
platform/android/tts_android.h

@@ -57,6 +57,7 @@ class TTS_Android {
 
 
 public:
 public:
 	static void setup(jobject p_tts);
 	static void setup(jobject p_tts);
+	static void terminate();
 	static void _java_utterance_callback(int p_event, int p_id, int p_pos);
 	static void _java_utterance_callback(int p_event, int p_id, int p_pos);
 
 
 	static bool is_speaking();
 	static bool is_speaking();