فهرست منبع

Fix the logic to restart the Godot application

Fredia Huya-Kouadio 3 سال پیش
والد
کامیت
d38ffda2c3

+ 5 - 0
COPYRIGHT.txt

@@ -73,6 +73,11 @@ Copyright: 2008-2016, The Android Open Source Project
  2002, Google, Inc.
 License: Apache-2.0
 
+Files: ./platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
+Comment: ProcessPhoenix
+Copyright: 2015, Jake Wharton
+License: Apache-2.0
+
 Files: ./scene/animation/easing_equations.h
 Comment: Robert Penner's Easing Functions
 Copyright: 2001, Robert Penner

+ 2 - 0
misc/scripts/clang_format.sh

@@ -21,6 +21,8 @@ while read -r f; do
         continue
     elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper"* ]]; then
         continue
+    elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix"* ]]; then
+        continue
     fi
 
     python misc/scripts/copyright_headers.py "$f"

+ 0 - 5
platform/android/export/export_plugin.cpp

@@ -807,7 +807,6 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
 	}
 
 	manifest_text += _get_xr_features_tag(p_preset);
-	manifest_text += _get_instrumentation_tag(p_preset);
 	manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms));
 	manifest_text += "</manifest>\n";
 	String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
@@ -969,10 +968,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
 						encode_uint32(retain_data_on_uninstall, &p_manifest.write[iofs + 16]);
 					}
 
-					if (tname == "instrumentation" && attrname == "targetPackage") {
-						string_table.write[attr_value] = get_package_name(package_name);
-					}
-
 					if (tname == "activity" && attrname == "screenOrientation") {
 						encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]);
 					}

+ 0 - 13
platform/android/export/gradle_export_util.cpp

@@ -232,19 +232,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
 	return manifest_xr_features;
 }
 
-String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
-	String package_name = p_preset->get("package/unique_name");
-	String manifest_instrumentation_text = vformat(
-			"    <instrumentation\n"
-			"        tools:node=\"replace\"\n"
-			"        android:name=\".GodotInstrumentation\"\n"
-			"        android:icon=\"@mipmap/icon\"\n"
-			"        android:label=\"@string/godot_project_name_string\"\n"
-			"        android:targetPackage=\"%s\" />\n",
-			package_name);
-	return manifest_instrumentation_text;
-}
-
 String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
 	int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
 	bool uses_xr = xr_mode_index == XR_MODE_OPENXR;

+ 0 - 2
platform/android/export/gradle_export_util.h

@@ -102,8 +102,6 @@ String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset);
 
 String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset);
 
-String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset);
-
 String _get_activity_tag(const Ref<EditorExportPreset> &p_preset);
 
 String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);

+ 7 - 6
platform/android/java/lib/AndroidManifest.xml

@@ -16,12 +16,13 @@
 
         <service android:name=".GodotDownloaderService" />
 
-    </application>
+        <activity
+            android:name=".utils.ProcessPhoenix"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar"
+            android:process=":phoenix"
+            android:exported="false"
+            />
 
-    <instrumentation
-        android:icon="@mipmap/icon"
-        android:label="@string/godot_project_name_string"
-        android:name="org.godotengine.godot.GodotInstrumentation"
-        android:targetPackage="org.godotengine.godot" />
+    </application>
 
 </manifest>

+ 8 - 12
platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java

@@ -30,7 +30,8 @@
 
 package org.godotengine.godot;
 
-import android.content.ComponentName;
+import org.godotengine.godot.utils.ProcessPhoenix;
+
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
@@ -71,6 +72,7 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
 
 	@Override
 	public void onDestroy() {
+		Log.v(TAG, "Destroying Godot app...");
 		super.onDestroy();
 		onGodotForceQuit(godotFragment);
 	}
@@ -78,27 +80,21 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
 	@Override
 	public final void onGodotForceQuit(Godot instance) {
 		if (instance == godotFragment) {
-			System.exit(0);
+			Log.v(TAG, "Force quitting Godot instance");
+			ProcessPhoenix.forceQuit(this);
 		}
 	}
 
 	@Override
 	public final void onGodotRestartRequested(Godot instance) {
 		if (instance == godotFragment) {
-			// HACK:
-			//
-			// Currently it's very hard to properly deinitialize Godot on Android to restart the game
+			// 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).
-			//
-			// Using instrumentation is a way of making the whole app process restart, because Android
-			// will kill any process of the same package which was already running.
-			//
-			Bundle args = new Bundle();
-			args.putParcelable("intent", getIntent());
-			startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args);
+			Log.v(TAG, "Restarting Godot instance...");
+			ProcessPhoenix.triggerRebirth(this);
 		}
 	}
 

+ 0 - 50
platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java

@@ -1,50 +0,0 @@
-/*************************************************************************/
-/*  GodotInstrumentation.java                                            */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-package org.godotengine.godot;
-
-import android.app.Instrumentation;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class GodotInstrumentation extends Instrumentation {
-	private Intent intent;
-
-	@Override
-	public void onCreate(Bundle arguments) {
-		intent = arguments.getParcelable("intent");
-		start();
-	}
-
-	@Override
-	public void onStart() {
-		startActivitySync(intent);
-	}
-}

+ 141 - 0
platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java

@@ -0,0 +1,141 @@
+// clang-format off
+
+/* Third-party library.
+ * Upstream: https://github.com/JakeWharton/ProcessPhoenix
+ * Commit: 12cb27c2cc9c3fc555e97f2db89e571667de82c4
+ */
+
+/*
+ * Copyright (C) 2014 Jake Wharton
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.godotengine.godot.utils;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Process;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+/**
+ * Process Phoenix facilitates restarting your application process. This should only be used for
+ * things like fundamental state changes in your debug builds (e.g., changing from staging to
+ * production).
+ * <p>
+ * Trigger process recreation by calling {@link #triggerRebirth} with a {@link Context} instance.
+ */
+public final class ProcessPhoenix extends Activity {
+  private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents";
+  private static final String KEY_MAIN_PROCESS_PID = "phoenix_main_process_pid";
+
+  /**
+   * Call to restart the application process using the {@linkplain Intent#CATEGORY_DEFAULT default}
+   * activity as an intent.
+   * <p>
+   * Behavior of the current process after invoking this method is undefined.
+   */
+  public static void triggerRebirth(Context context) {
+    triggerRebirth(context, getRestartIntent(context));
+  }
+
+  /**
+   * Call to restart the application process using the specified intents.
+   * <p>
+   * Behavior of the current process after invoking this method is undefined.
+   */
+  public static void triggerRebirth(Context context, Intent... nextIntents) {
+    if (nextIntents.length < 1) {
+      throw new IllegalArgumentException("intents cannot be empty");
+    }
+    // create a new task for the first activity.
+    nextIntents[0].addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+
+    Intent intent = new Intent(context, ProcessPhoenix.class);
+    intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context.
+    intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents)));
+    intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid());
+    context.startActivity(intent);
+  }
+
+  // -- GODOT start --
+  /**
+   * Finish the activity and kill its process
+   */
+  public static void forceQuit(Activity activity) {
+    forceQuit(activity, Process.myPid());
+  }
+
+  /**
+   * Finish the activity and kill its process
+   * @param activity
+   * @param pid
+   */
+  public static void forceQuit(Activity activity, int pid) {
+    Process.killProcess(pid); // Kill original main process
+    activity.finish();
+    Runtime.getRuntime().exit(0); // Kill kill kill!
+  }
+
+  // -- GODOT end --
+
+  private static Intent getRestartIntent(Context context) {
+    String packageName = context.getPackageName();
+    Intent defaultIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
+    if (defaultIntent != null) {
+      return defaultIntent;
+    }
+
+    throw new IllegalStateException("Unable to determine default activity for "
+        + packageName
+        + ". Does an activity specify the DEFAULT category in its intent filter?");
+  }
+
+  @Override protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    // -- GODOT start --
+    ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS);
+    startActivities(intents.toArray(new Intent[intents.size()]));
+    forceQuit(this, getIntent().getIntExtra(KEY_MAIN_PROCESS_PID, -1));
+    // -- GODOT end --
+  }
+
+  /**
+   * Checks if the current process is a temporary Phoenix Process.
+   * This can be used to avoid initialisation of unused resources or to prevent running code that
+   * is not multi-process ready.
+   *
+   * @return true if the current process is a temporary Phoenix Process
+   */
+  public static boolean isPhoenixProcess(Context context) {
+    int currentPid = Process.myPid();
+    ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+    List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();
+    if (runningProcesses != null) {
+      for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
+        if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+}