Merge pull request #72110 from m4gr3d/implement_godot_editor_3_kill_process
[3.x] Provide a delegate implementation for the `killProcess` logic on Android
This commit is contained in:
commit
9b0f0a7136
8 changed files with 129 additions and 47 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -462,11 +462,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) {
|
||||
|
@ -1022,11 +1020,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) {
|
||||
|
@ -1180,11 +1187,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
|
|||
_destroy_offscreen_gl = p_env->GetMethodID(godot_class, "destroyOffscreenGL", "()V");
|
||||
_set_offscreen_gl_current = p_env->GetMethodID(godot_class, "setOffscreenGLCurrent", "(Z)V");
|
||||
_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");
|
||||
|
@ -80,7 +80,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/GodotView;");
|
||||
|
||||
// get some Activity method pointers...
|
||||
|
@ -206,14 +206,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) {
|
||||
|
@ -372,14 +373,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ public:
|
|||
void on_godot_setup_completed(JNIEnv *p_env = NULL);
|
||||
void on_godot_main_loop_started(JNIEnv *p_env = NULL);
|
||||
void restart(JNIEnv *p_env = NULL);
|
||||
void force_quit(JNIEnv *p_env = NULL);
|
||||
bool force_quit(JNIEnv *p_env = NULL, 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();
|
||||
|
@ -112,7 +112,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
|
||||
|
|
|
@ -716,9 +716,19 @@ Error OS_Android::execute(const String &p_path, const List<String> &p_arguments,
|
|||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
|
|
@ -215,6 +215,7 @@ public:
|
|||
virtual String get_config_path() const;
|
||||
|
||||
virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false);
|
||||
virtual Error kill(const ProcessID &p_pid);
|
||||
|
||||
virtual bool _check_internal_feature_support(const String &p_feature);
|
||||
OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);
|
||||
|
|
Loading…
Reference in a new issue