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
This commit is contained in:
Rémi Verschelde
2023-01-26 15:58:12 +01:00
9 changed files with 132 additions and 48 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}