Fix the logic to restart the Godot application
This commit is contained in:
parent
241c267d9c
commit
b162e7ac39
9 changed files with 163 additions and 88 deletions
|
@ -86,6 +86,11 @@ Comment: The Android Open Source Project
|
||||||
Copyright: 2002, Google Inc.
|
Copyright: 2002, Google Inc.
|
||||||
License: Apache-2.0
|
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: ./platform/android/power_android.cpp
|
Files: ./platform/android/power_android.cpp
|
||||||
./platform/osx/power_osx.cpp
|
./platform/osx/power_osx.cpp
|
||||||
./platform/windows/power_windows.cpp
|
./platform/windows/power_windows.cpp
|
||||||
|
|
|
@ -35,6 +35,8 @@ while IFS= read -rd '' f; do
|
||||||
continue 2
|
continue 2
|
||||||
elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper"* ]]; then
|
elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper"* ]]; then
|
||||||
continue 2
|
continue 2
|
||||||
|
elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix"* ]]; then
|
||||||
|
continue 2
|
||||||
fi
|
fi
|
||||||
python misc/scripts/copyright_headers.py "$f"
|
python misc/scripts/copyright_headers.py "$f"
|
||||||
continue 2
|
continue 2
|
||||||
|
|
|
@ -791,7 +791,6 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest_text += _get_xr_features_tag(p_preset);
|
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 += _get_application_tag(p_preset, _has_storage_permission(perms));
|
||||||
manifest_text += "</manifest>\n";
|
manifest_text += "</manifest>\n";
|
||||||
String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
|
String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
|
||||||
|
@ -955,10 +954,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
||||||
encode_uint32(retain_data_on_uninstall, &p_manifest.write[iofs + 16]);
|
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") {
|
if (tname == "activity" && attrname == "screenOrientation") {
|
||||||
encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]);
|
encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,19 +233,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
|
||||||
return manifest_xr_features;
|
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) {
|
String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
|
||||||
int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
|
int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
|
||||||
bool uses_xr = xr_mode_index == XR_MODE_OVR || xr_mode_index == XR_MODE_OPENXR;
|
bool uses_xr = xr_mode_index == XR_MODE_OVR || xr_mode_index == XR_MODE_OPENXR;
|
||||||
|
|
|
@ -103,8 +103,6 @@ String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset);
|
||||||
|
|
||||||
String _get_xr_features_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_activity_tag(const Ref<EditorExportPreset> &p_preset);
|
||||||
|
|
||||||
String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);
|
String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);
|
||||||
|
|
|
@ -16,12 +16,13 @@
|
||||||
|
|
||||||
<service android:name=".GodotDownloaderService" />
|
<service android:name=".GodotDownloaderService" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".utils.ProcessPhoenix"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
android:process=":phoenix"
|
||||||
|
android:exported="false"
|
||||||
|
/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<instrumentation
|
|
||||||
android:name=".GodotInstrumentation"
|
|
||||||
android:icon="@mipmap/icon"
|
|
||||||
android:label="@string/godot_project_name_string"
|
|
||||||
android:targetPackage="org.godotengine.godot" />
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
|
|
||||||
package org.godotengine.godot;
|
package org.godotengine.godot;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import org.godotengine.godot.utils.ProcessPhoenix;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -71,6 +72,7 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
Log.v(TAG, "Destroying Godot app...");
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
onGodotForceQuit(godotFragment);
|
onGodotForceQuit(godotFragment);
|
||||||
}
|
}
|
||||||
|
@ -78,27 +80,21 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
|
||||||
@Override
|
@Override
|
||||||
public final void onGodotForceQuit(Godot instance) {
|
public final void onGodotForceQuit(Godot instance) {
|
||||||
if (instance == godotFragment) {
|
if (instance == godotFragment) {
|
||||||
System.exit(0);
|
Log.v(TAG, "Force quitting Godot instance");
|
||||||
|
ProcessPhoenix.forceQuit(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onGodotRestartRequested(Godot instance) {
|
public final void onGodotRestartRequested(Godot instance) {
|
||||||
if (instance == godotFragment) {
|
if (instance == godotFragment) {
|
||||||
// HACK:
|
// It's very hard to properly de-initialize Godot on Android to restart the game
|
||||||
//
|
|
||||||
// Currently it's very hard to properly deinitialize Godot on Android to restart the game
|
|
||||||
// from scratch. Therefore, we need to kill the whole app process and relaunch it.
|
// 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
|
// 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).
|
// releasing and reloading native libs or resetting their state somehow and clearing statics).
|
||||||
//
|
Log.v(TAG, "Restarting Godot instance...");
|
||||||
// Using instrumentation is a way of making the whole app process restart, because Android
|
ProcessPhoenix.triggerRebirth(this);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue