summaryrefslogtreecommitdiff
path: root/platform/android
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2023-01-26 15:58:12 +0100
committerRémi Verschelde <rverschelde@gmail.com>2023-01-26 15:58:12 +0100
commit3870861056ea8441707cab015d20e9f48337c61a (patch)
tree92d1c76e84ff4fbe05598a3005731772ebab0ef0 /platform/android
parentcc7aa72f01fdea5f93b0a618fcd12db55617beb0 (diff)
parentec4d720850091c7c2d3715031318bfcdbb391f62 (diff)
Merge pull request #72106 from m4gr3d/fix_godot_android_editor_4_crash_after_running_game
Fix the issue causing the Godot Android Editor to crash when returning from the launched and running game
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt50
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java30
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java38
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java22
-rw-r--r--platform/android/java_godot_wrapper.cpp19
-rw-r--r--platform/android/java_godot_wrapper.h4
-rw-r--r--platform/android/os_android.cpp12
-rw-r--r--platform/android/os_android.h1
8 files changed, 129 insertions, 47 deletions
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index f848089aa8..71385315ae 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -31,12 +31,11 @@
package org.godotengine.editor
import android.Manifest
+import android.app.ActivityManager
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.os.Build
-import android.os.Bundle
-import android.os.Debug
-import android.os.Environment
+import android.os.*
import android.util.Log
import android.widget.Toast
import androidx.window.layout.WindowMetricsCalculator
@@ -64,11 +63,18 @@ open class GodotEditor : FullScreenGodotApp() {
private const val COMMAND_LINE_PARAMS = "command_line_params"
+ private const val EDITOR_ID = 777
private const val EDITOR_ARG = "--editor"
private const val EDITOR_ARG_SHORT = "-e"
+ private const val EDITOR_PROCESS_NAME_SUFFIX = ":GodotEditor"
+ private const val GAME_ID = 667
+ private const val GAME_PROCESS_NAME_SUFFIX = ":GodotGame"
+
+ private const val PROJECT_MANAGER_ID = 555
private const val PROJECT_MANAGER_ARG = "--project-manager"
private const val PROJECT_MANAGER_ARG_SHORT = "-p"
+ private const val PROJECT_MANAGER_PROCESS_NAME_SUFFIX = ":GodotProjectManager"
}
private val commandLineParams = ArrayList<String>()
@@ -102,9 +108,10 @@ open class GodotEditor : FullScreenGodotApp() {
override fun getCommandLine() = commandLineParams
- override fun onNewGodotInstanceRequested(args: Array<String>) {
+ override fun onNewGodotInstanceRequested(args: Array<String>): Int {
// Parse the arguments to figure out which activity to start.
var targetClass: Class<*> = GodotGame::class.java
+ var instanceId = GAME_ID
// Whether we should launch the new godot instance in an adjacent window
// https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
@@ -115,12 +122,14 @@ open class GodotEditor : FullScreenGodotApp() {
if (EDITOR_ARG == arg || EDITOR_ARG_SHORT == arg) {
targetClass = GodotEditor::class.java
launchAdjacent = false
+ instanceId = EDITOR_ID
break
}
if (PROJECT_MANAGER_ARG == arg || PROJECT_MANAGER_ARG_SHORT == arg) {
targetClass = GodotProjectManager::class.java
launchAdjacent = false
+ instanceId = PROJECT_MANAGER_ID
break
}
}
@@ -139,6 +148,37 @@ open class GodotEditor : FullScreenGodotApp() {
Log.d(TAG, "Starting $targetClass")
startActivity(newInstance)
}
+ return instanceId
+ }
+
+ override fun onGodotForceQuit(godotInstanceId: Int): Boolean {
+ val processNameSuffix = when (godotInstanceId) {
+ GAME_ID -> {
+ GAME_PROCESS_NAME_SUFFIX
+ }
+ EDITOR_ID -> {
+ EDITOR_PROCESS_NAME_SUFFIX
+ }
+ PROJECT_MANAGER_ID -> {
+ PROJECT_MANAGER_PROCESS_NAME_SUFFIX
+ }
+ else -> ""
+ }
+ if (processNameSuffix.isBlank()) {
+ return false
+ }
+
+ val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ val runningProcesses = activityManager.runningAppProcesses
+ for (runningProcess in runningProcesses) {
+ if (runningProcess.processName.endsWith(processNameSuffix)) {
+ Log.v(TAG, "Killing Godot process ${runningProcess.processName}")
+ Process.killProcess(runningProcess.pid)
+ return true
+ }
+ }
+
+ return false
}
// Get the screen's density scale
diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
index 863e67f1e9..65032d6a68 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -74,28 +74,36 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
public void onDestroy() {
Log.v(TAG, "Destroying Godot app...");
super.onDestroy();
- onGodotForceQuit(godotFragment);
+ terminateGodotInstance(godotFragment);
}
@Override
public final void onGodotForceQuit(Godot instance) {
+ runOnUiThread(() -> {
+ terminateGodotInstance(instance);
+ });
+ }
+
+ private void terminateGodotInstance(Godot instance) {
if (instance == godotFragment) {
Log.v(TAG, "Force quitting Godot instance");
- ProcessPhoenix.forceQuit(this);
+ ProcessPhoenix.forceQuit(FullScreenGodotApp.this);
}
}
@Override
public final void onGodotRestartRequested(Godot instance) {
- if (instance == godotFragment) {
- // It's very hard to properly de-initialize Godot on Android to restart the game
- // from scratch. Therefore, we need to kill the whole app process and relaunch it.
- //
- // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
- // releasing and reloading native libs or resetting their state somehow and clearing statics).
- Log.v(TAG, "Restarting Godot instance...");
- ProcessPhoenix.triggerRebirth(this);
- }
+ runOnUiThread(() -> {
+ if (instance == godotFragment) {
+ // It's very hard to properly de-initialize Godot on Android to restart the game
+ // from scratch. Therefore, we need to kill the whole app process and relaunch it.
+ //
+ // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
+ // releasing and reloading native libs or resetting their state somehow and clearing statics).
+ Log.v(TAG, "Restarting Godot instance...");
+ ProcessPhoenix.triggerRebirth(FullScreenGodotApp.this);
+ }
+ });
}
@Override
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 905db13c85..50263bc392 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -348,11 +348,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
public void restart() {
- runOnUiThread(() -> {
- if (godotHost != null) {
- godotHost.onGodotRestartRequested(this);
- }
- });
+ if (godotHost != null) {
+ godotHost.onGodotRestartRequested(this);
+ }
}
public void alert(final String message, final String title) {
@@ -889,11 +887,20 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
private void forceQuit() {
// TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each
// native Godot components that is started in Godot#onVideoInit.
- runOnUiThread(() -> {
- if (godotHost != null) {
- godotHost.onGodotForceQuit(this);
- }
- });
+ forceQuit(0);
+ }
+
+ @Keep
+ private boolean forceQuit(int instanceId) {
+ if (godotHost == null) {
+ return false;
+ }
+ if (instanceId == 0) {
+ godotHost.onGodotForceQuit(this);
+ return true;
+ } else {
+ return godotHost.onGodotForceQuit(instanceId);
+ }
}
private boolean obbIsCorrupted(String f, String main_pack_md5) {
@@ -1052,11 +1059,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
@Keep
- private void createNewGodotInstance(String[] args) {
- runOnUiThread(() -> {
- if (godotHost != null) {
- godotHost.onNewGodotInstanceRequested(args);
- }
- });
+ private int createNewGodotInstance(String[] args) {
+ if (godotHost != null) {
+ return godotHost.onNewGodotInstanceRequested(args);
+ }
+ return 0;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
index 256d04e3a5..7700b9b628 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
@@ -55,21 +55,35 @@ public interface GodotHost {
default void onGodotMainLoopStarted() {}
/**
- * Invoked on the UI thread as the last step of the Godot instance clean up phase.
+ * Invoked on the render thread to terminate the given Godot instance.
*/
default void onGodotForceQuit(Godot instance) {}
/**
- * Invoked on the UI thread when the Godot instance wants to be restarted. It's up to the host
+ * Invoked on the render thread to terminate the Godot instance with the given id.
+ * @param godotInstanceId id of the Godot instance to terminate. See {@code onNewGodotInstanceRequested}
+ *
+ * @return true if successful, false otherwise.
+ */
+ default boolean onGodotForceQuit(int godotInstanceId) {
+ return false;
+ }
+
+ /**
+ * Invoked on the render thread when the Godot instance wants to be restarted. It's up to the host
* to perform the appropriate action(s).
*/
default void onGodotRestartRequested(Godot instance) {}
/**
- * Invoked on the UI thread when a new Godot instance is requested. It's up to the host to
+ * Invoked on the render thread when a new Godot instance is requested. It's up to the host to
* perform the appropriate action(s).
*
* @param args Arguments used to initialize the new instance.
+ *
+ * @return the id of the new instance. See {@code onGodotForceQuit}
*/
- default void onNewGodotInstanceRequested(String[] args) {}
+ default int onNewGodotInstanceRequested(String[] args) {
+ return 0;
+ }
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 03548d11f6..9d9d087896 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -60,7 +60,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
// get some Godot method pointers...
_on_video_init = p_env->GetMethodID(godot_class, "onVideoInit", "()Z");
_restart = p_env->GetMethodID(godot_class, "restart", "()V");
- _finish = p_env->GetMethodID(godot_class, "forceQuit", "()V");
+ _finish = p_env->GetMethodID(godot_class, "forceQuit", "(I)Z");
_set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V");
_alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V");
_get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I");
@@ -77,7 +77,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
_on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
- _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)V");
+ _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I");
_get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
// get some Activity method pointers...
@@ -179,14 +179,15 @@ void GodotJavaWrapper::restart(JNIEnv *p_env) {
}
}
-void GodotJavaWrapper::force_quit(JNIEnv *p_env) {
+bool GodotJavaWrapper::force_quit(JNIEnv *p_env, int p_instance_id) {
if (_finish) {
if (p_env == nullptr) {
p_env = get_jni_env();
}
- ERR_FAIL_NULL(p_env);
- p_env->CallVoidMethod(godot_instance, _finish);
+ ERR_FAIL_NULL_V(p_env, false);
+ return p_env->CallBooleanMethod(godot_instance, _finish, p_instance_id);
}
+ return false;
}
void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) {
@@ -345,14 +346,16 @@ void GodotJavaWrapper::vibrate(int p_duration_ms) {
}
}
-void GodotJavaWrapper::create_new_godot_instance(List<String> args) {
+int GodotJavaWrapper::create_new_godot_instance(List<String> args) {
if (_create_new_godot_instance) {
JNIEnv *env = get_jni_env();
- ERR_FAIL_NULL(env);
+ 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()));
}
- env->CallVoidMethod(godot_instance, _create_new_godot_instance, jargs);
+ return env->CallIntMethod(godot_instance, _create_new_godot_instance, jargs);
+ } else {
+ return 0;
}
}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 5dad2a3eb9..1bd79584d8 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -85,7 +85,7 @@ public:
void on_godot_setup_completed(JNIEnv *p_env = nullptr);
void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
void restart(JNIEnv *p_env = nullptr);
- void force_quit(JNIEnv *p_env = nullptr);
+ bool force_quit(JNIEnv *p_env = nullptr, int p_instance_id = 0);
void set_keep_screen_on(bool p_enabled);
void alert(const String &p_message, const String &p_title);
int get_gles_version_code();
@@ -103,7 +103,7 @@ public:
bool is_activity_resumed();
void vibrate(int p_duration_ms);
String get_input_fallback_mapping();
- void create_new_godot_instance(List<String> args);
+ int create_new_godot_instance(List<String> args);
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 29b91983bf..725fea8d54 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -739,9 +739,19 @@ Error OS_Android::create_process(const String &p_path, const List<String> &p_arg
}
Error OS_Android::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) {
- godot_java->create_new_godot_instance(p_arguments);
+ int instance_id = godot_java->create_new_godot_instance(p_arguments);
+ if (r_child_id) {
+ *r_child_id = instance_id;
+ }
return OK;
}
+Error OS_Android::kill(const ProcessID &p_pid) {
+ if (godot_java->force_quit(nullptr, p_pid)) {
+ return OK;
+ }
+ return OS_Unix::kill(p_pid);
+}
+
OS_Android::~OS_Android() {
}
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index 9b43797580..53910b1498 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -158,6 +158,7 @@ public:
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
+ virtual Error kill(const ProcessID &p_pid) override;
virtual bool _check_internal_feature_support(const String &p_feature) override;
OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);