Fix crashes reported by the Google Play Console

This commit is contained in:
Fredia Huya-Kouadio 2024-07-03 22:32:47 -07:00
parent e6448ca0aa
commit c6a23a7a7d
7 changed files with 68 additions and 54 deletions

View File

@ -909,13 +909,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// Benchmark tracking must be done after `OS::get_singleton()->initialize()` as on some // Benchmark tracking must be done after `OS::get_singleton()->initialize()` as on some
// platforms, it's used to set up the time utilities. // platforms, it's used to set up the time utilities.
OS::get_singleton()->benchmark_begin_measure("Startup", "Total"); OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Setup");
OS::get_singleton()->benchmark_begin_measure("Startup", "Setup");
engine = memnew(Engine); engine = memnew(Engine);
MAIN_PRINT("Main: Initialize CORE"); MAIN_PRINT("Main: Initialize CORE");
OS::get_singleton()->benchmark_begin_measure("Startup", "Core");
register_core_types(); register_core_types();
register_core_driver_types(); register_core_driver_types();
@ -2453,8 +2451,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
Thread::release_main_thread(); // If setup2() is called from another thread, that one will become main thread, so preventively release this one. Thread::release_main_thread(); // If setup2() is called from another thread, that one will become main thread, so preventively release this one.
set_current_thread_safe_for_nodes(false); set_current_thread_safe_for_nodes(false);
OS::get_singleton()->benchmark_end_measure("Startup", "Core");
#if defined(STEAMAPI_ENABLED) #if defined(STEAMAPI_ENABLED)
if (editor || project_manager) { if (editor || project_manager) {
steam_tracker = memnew(SteamTracker); steam_tracker = memnew(SteamTracker);
@ -2465,7 +2461,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
return setup2(); return setup2();
} }
OS::get_singleton()->benchmark_end_measure("Startup", "Setup"); OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup");
return OK; return OK;
error: error:
@ -2519,7 +2515,7 @@ error:
} }
OS::get_singleton()->benchmark_end_measure("Startup", "Core"); OS::get_singleton()->benchmark_end_measure("Startup", "Core");
OS::get_singleton()->benchmark_end_measure("Startup", "Setup"); OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup");
#if defined(STEAMAPI_ENABLED) #if defined(STEAMAPI_ENABLED)
if (steam_tracker) { if (steam_tracker) {
@ -2553,6 +2549,8 @@ Error _parse_resource_dummy(void *p_data, VariantParser::Stream *p_stream, Ref<R
} }
Error Main::setup2(bool p_show_boot_logo) { Error Main::setup2(bool p_show_boot_logo) {
OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Setup2");
Thread::make_main_thread(); // Make whatever thread call this the main thread. Thread::make_main_thread(); // Make whatever thread call this the main thread.
set_current_thread_safe_for_nodes(true); set_current_thread_safe_for_nodes(true);
@ -3149,7 +3147,7 @@ Error Main::setup2(bool p_show_boot_logo) {
print_verbose("EDITOR API HASH: " + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR))); print_verbose("EDITOR API HASH: " + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR)));
MAIN_PRINT("Main: Done"); MAIN_PRINT("Main: Done");
OS::get_singleton()->benchmark_end_measure("Startup", "Setup"); OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup2");
return OK; return OK;
} }
@ -3230,6 +3228,8 @@ static MainTimerSync main_timer_sync;
// and should move on to `OS::run`, and EXIT_FAILURE otherwise for // and should move on to `OS::run`, and EXIT_FAILURE otherwise for
// an early exit with that error code. // an early exit with that error code.
int Main::start() { int Main::start() {
OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Start");
ERR_FAIL_COND_V(!_start_success, false); ERR_FAIL_COND_V(!_start_success, false);
bool has_icon = false; bool has_icon = false;
@ -3953,7 +3953,7 @@ int Main::start() {
} }
} }
OS::get_singleton()->benchmark_end_measure("Startup", "Total"); OS::get_singleton()->benchmark_end_measure("Startup", "Main::Start");
OS::get_singleton()->benchmark_dump(); OS::get_singleton()->benchmark_dump();
return EXIT_SUCCESS; return EXIT_SUCCESS;
@ -4221,7 +4221,7 @@ void Main::force_redraw() {
* The order matters as some of those steps are linked with each other. * The order matters as some of those steps are linked with each other.
*/ */
void Main::cleanup(bool p_force) { void Main::cleanup(bool p_force) {
OS::get_singleton()->benchmark_begin_measure("Shutdown", "Total"); OS::get_singleton()->benchmark_begin_measure("Shutdown", "Main::Cleanup");
if (!p_force) { if (!p_force) {
ERR_FAIL_COND(!_start_success); ERR_FAIL_COND(!_start_success);
} }
@ -4379,7 +4379,7 @@ void Main::cleanup(bool p_force) {
unregister_core_types(); unregister_core_types();
OS::get_singleton()->benchmark_end_measure("Shutdown", "Total"); OS::get_singleton()->benchmark_end_measure("Shutdown", "Main::Cleanup");
OS::get_singleton()->benchmark_dump(); OS::get_singleton()->benchmark_dump();
OS::get_singleton()->finalize_core(); OS::get_singleton()->finalize_core();

View File

@ -7,7 +7,7 @@ ext.versions = [
targetSdk : 34, targetSdk : 34,
buildTools : '34.0.0', buildTools : '34.0.0',
kotlinVersion : '1.9.20', kotlinVersion : '1.9.20',
fragmentVersion : '1.6.2', fragmentVersion : '1.7.1',
nexusPublishVersion: '1.3.0', nexusPublishVersion: '1.3.0',
javaVersion : JavaVersion.VERSION_17, javaVersion : JavaVersion.VERSION_17,
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.

View File

@ -9,7 +9,7 @@ dependencies {
implementation "androidx.fragment:fragment:$versions.fragmentVersion" implementation "androidx.fragment:fragment:$versions.fragmentVersion"
implementation project(":lib") implementation project(":lib")
implementation "androidx.window:window:1.2.0" implementation "androidx.window:window:1.3.0"
implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
} }

View File

@ -86,15 +86,14 @@ class Godot(private val context: Context) : SensorEventListener {
private val TAG = Godot::class.java.simpleName private val TAG = Godot::class.java.simpleName
} }
private val windowManager: WindowManager by lazy { private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
} private val mClipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
private val vibratorService: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
private val pluginRegistry: GodotPluginRegistry by lazy { private val pluginRegistry: GodotPluginRegistry by lazy {
GodotPluginRegistry.getPluginRegistry() GodotPluginRegistry.getPluginRegistry()
} }
private val mSensorManager: SensorManager by lazy {
requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
private val mAccelerometer: Sensor? by lazy { private val mAccelerometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
} }
@ -107,9 +106,6 @@ class Godot(private val context: Context) : SensorEventListener {
private val mGyroscope: Sensor? by lazy { private val mGyroscope: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
} }
private val mClipboard: ClipboardManager by lazy {
requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int -> private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int ->
if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
@ -323,13 +319,15 @@ class Godot(private val context: Context) : SensorEventListener {
return false return false
} }
if (expansionPackPath.isNotEmpty()) { beginBenchmarkMeasure("Startup", "Godot::onInitNativeLayer")
commandLine.add("--main-pack") try {
commandLine.add(expansionPackPath) if (expansionPackPath.isNotEmpty()) {
} commandLine.add("--main-pack")
val activity = requireActivity() commandLine.add(expansionPackPath)
if (!nativeLayerInitializeCompleted) { }
nativeLayerInitializeCompleted = GodotLib.initialize( val activity = requireActivity()
if (!nativeLayerInitializeCompleted) {
nativeLayerInitializeCompleted = GodotLib.initialize(
activity, activity,
this, this,
activity.assets, activity.assets,
@ -338,15 +336,17 @@ class Godot(private val context: Context) : SensorEventListener {
directoryAccessHandler, directoryAccessHandler,
fileAccessHandler, fileAccessHandler,
useApkExpansion, useApkExpansion,
) )
}
if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) {
nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts)
if (!nativeLayerSetupCompleted) {
Log.e(TAG, "Unable to setup the Godot engine! Aborting...")
alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit)
} }
if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) {
nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts)
if (!nativeLayerSetupCompleted) {
throw IllegalStateException("Unable to setup the Godot engine! Aborting...")
}
}
} finally {
endBenchmarkMeasure("Startup", "Godot::onInitNativeLayer")
} }
return isNativeInitialized() return isNativeInitialized()
} }
@ -370,6 +370,7 @@ class Godot(private val context: Context) : SensorEventListener {
throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view") throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view")
} }
beginBenchmarkMeasure("Startup", "Godot::onInitRenderView")
try { try {
val activity: Activity = host.activity val activity: Activity = host.activity
containerLayout = providedContainerLayout containerLayout = providedContainerLayout
@ -392,8 +393,7 @@ class Godot(private val context: Context) : SensorEventListener {
containerLayout?.addView(editText) containerLayout?.addView(editText)
renderView = if (usesVulkan()) { renderView = if (usesVulkan()) {
if (!meetsVulkanRequirements(activity.packageManager)) { if (!meetsVulkanRequirements(activity.packageManager)) {
alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit) throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message))
return null
} }
GodotVulkanRenderView(host, this) GodotVulkanRenderView(host, this)
} else { } else {
@ -482,6 +482,8 @@ class Godot(private val context: Context) : SensorEventListener {
containerLayout?.removeAllViews() containerLayout?.removeAllViews()
containerLayout = null containerLayout = null
} }
endBenchmarkMeasure("Startup", "Godot::onInitRenderView")
} }
return containerLayout return containerLayout
} }
@ -609,13 +611,17 @@ class Godot(private val context: Context) : SensorEventListener {
// These properties are defined after Godot setup completion, so we retrieve them here. // These properties are defined after Godot setup completion, so we retrieve them here.
val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))
val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")) val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")
runOnUiThread { runOnUiThread {
renderView?.inputHandler?.apply { renderView?.inputHandler?.apply {
enableLongPress(longPressEnabled) enableLongPress(longPressEnabled)
enablePanningAndScalingGestures(panScaleEnabled) enablePanningAndScalingGestures(panScaleEnabled)
setRotaryInputAxis(rotaryInputAxis) try {
setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue))
} catch (e: NumberFormatException) {
Log.w(TAG, e)
}
} }
} }
@ -646,12 +652,7 @@ class Godot(private val context: Context) : SensorEventListener {
decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener) decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener)
} }
@Keep fun alert(
private fun alert(message: String, title: String) {
alert(message, title, null)
}
private fun alert(
@StringRes messageResId: Int, @StringRes messageResId: Int,
@StringRes titleResId: Int, @StringRes titleResId: Int,
okCallback: Runnable? okCallback: Runnable?
@ -660,7 +661,9 @@ class Godot(private val context: Context) : SensorEventListener {
alert(res.getString(messageResId), res.getString(titleResId), okCallback) alert(res.getString(messageResId), res.getString(titleResId), okCallback)
} }
private fun alert(message: String, title: String, okCallback: Runnable?) { @JvmOverloads
@Keep
fun alert(message: String, title: String, okCallback: Runnable? = null) {
val activity: Activity = getActivity() ?: return val activity: Activity = getActivity() ?: return
runOnUiThread { runOnUiThread {
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
@ -770,7 +773,7 @@ class Godot(private val context: Context) : SensorEventListener {
mClipboard.setPrimaryClip(clip) mClipboard.setPrimaryClip(clip)
} }
private fun forceQuit() { fun forceQuit() {
forceQuit(0) forceQuit(0)
} }
@ -881,7 +884,6 @@ class Godot(private val context: Context) : SensorEventListener {
@Keep @Keep
private fun vibrate(durationMs: Int, amplitude: Int) { private fun vibrate(durationMs: Int, amplitude: Int) {
if (durationMs > 0 && requestPermission("VIBRATE")) { if (durationMs > 0 && requestPermission("VIBRATE")) {
val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (amplitude <= -1) { if (amplitude <= -1) {
vibratorService.vibrate( vibratorService.vibrate(

View File

@ -42,6 +42,7 @@ import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Messenger; import android.os.Messenger;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -203,6 +204,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
if (godotContainerLayout == null) { if (godotContainerLayout == null) {
throw new IllegalStateException("Unable to initialize engine render view"); throw new IllegalStateException("Unable to initialize engine render view");
} }
} catch (IllegalStateException e) {
Log.e(TAG, "Engine initialization failed", e);
final String errorMessage = TextUtils.isEmpty(e.getMessage())
? getString(R.string.error_engine_setup_message)
: e.getMessage();
godot.alert(errorMessage, getString(R.string.text_error_title), godot::forceQuit);
} catch (IllegalArgumentException ignored) { } catch (IllegalArgumentException ignored) {
final Activity activity = getActivity(); final Activity activity = getActivity();
Intent notifierIntent = new Intent(activity, activity.getClass()); Intent notifierIntent = new Intent(activity, activity.getClass());

View File

@ -121,7 +121,7 @@ public class GodotIO {
activity.startActivity(intent); activity.startActivity(intent);
return 0; return 0;
} catch (ActivityNotFoundException e) { } catch (Exception e) {
Log.e(TAG, "Unable to open uri " + uriString, e); Log.e(TAG, "Unable to open uri " + uriString, e);
return 1; return 1;
} }

View File

@ -81,7 +81,8 @@ fun beginBenchmarkMeasure(scope: String, label: String) {
* *
* * Note: Only enabled on 'editorDev' build variant. * * Note: Only enabled on 'editorDev' build variant.
*/ */
fun endBenchmarkMeasure(scope: String, label: String) { @JvmOverloads
fun endBenchmarkMeasure(scope: String, label: String, dumpBenchmark: Boolean = false) {
if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
return return
} }
@ -93,6 +94,10 @@ fun endBenchmarkMeasure(scope: String, label: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Trace.endAsyncSection("[$scope] $label", 0) Trace.endAsyncSection("[$scope] $label", 0)
} }
if (dumpBenchmark) {
dumpBenchmark()
}
} }
/** /**
@ -102,11 +107,11 @@ fun endBenchmarkMeasure(scope: String, label: String) {
* * Note: Only enabled on 'editorDev' build variant. * * Note: Only enabled on 'editorDev' build variant.
*/ */
@JvmOverloads @JvmOverloads
fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) { fun dumpBenchmark(fileAccessHandler: FileAccessHandler? = null, filepath: String? = benchmarkFile) {
if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
return return
} }
if (!useBenchmark) { if (!useBenchmark || benchmarkTracker.isEmpty()) {
return return
} }