From 70f9379eefc84b7651e3aababcce33987e073ed0 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 4 Sep 2025 18:35:10 -0300 Subject: [PATCH] Fix android termination bug (#1354) * move hook additions to onCreateView * Updated license header * Formatting nitpick * Added prefix to log messages --------- Co-authored-by: OpenSauce04 --- .../citra_emu/fragments/EmulationFragment.kt | 39 ++++++++++--------- .../citra_emu/utils/EmulationLifecycleUtil.kt | 14 +++++-- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index fbf3e5525..fa2a34a2d 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -85,7 +85,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private val preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) - private lateinit var emulationState: EmulationState + private var emulationState: EmulationState? = null private var perfStatsUpdater: Runnable? = null private lateinit var emulationActivity: EmulationActivity @@ -106,6 +106,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (context is EmulationActivity) { emulationActivity = context NativeLibrary.setEmulationActivity(context) + EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) + if (emulationState != null) { + EmulationLifecycleUtil.addShutdownHook(hook = { emulationState!!.stop() }) + } } else { throw IllegalStateException("EmulationFragment must have EmulationActivity parent") } @@ -156,8 +160,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram emulationState = EmulationState(game.path) emulationActivity = requireActivity() as EmulationActivity screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings) - EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) - EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) + EmulationLifecycleUtil.addShutdownHook(hook = { emulationState!!.stop() }) } override fun onCreateView( @@ -256,8 +259,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram binding.inGameMenu.setNavigationItemSelectedListener { when (it.itemId) { R.id.menu_emulation_pause -> { - if (emulationState.isPaused) { - emulationState.unpause() + if (emulationState!!.isPaused) { + emulationState!!.unpause() it.title = resources.getString(R.string.pause_emulation) it.icon = ResourcesCompat.getDrawable( resources, @@ -265,7 +268,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram requireContext().theme ) } else { - emulationState.pause() + emulationState!!.pause() it.title = resources.getString(R.string.resume_emulation) it.icon = ResourcesCompat.getDrawable( resources, @@ -355,7 +358,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } R.id.menu_exit -> { - emulationState.pause() + emulationState!!.pause() MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.emulation_close_game) .setMessage(R.string.emulation_close_game_message) @@ -363,9 +366,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram EmulationLifecycleUtil.closeGame() } .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> - emulationState.unpause() + emulationState!!.unpause() } - .setOnCancelListener { emulationState.unpause() } + .setOnCancelListener { emulationState!!.unpause() } .show() true } @@ -459,10 +462,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } private fun togglePause() { - if (emulationState.isPaused) { - emulationState.unpause() + if (emulationState!!.isPaused) { + emulationState!!.unpause() } else { - emulationState.pause() + emulationState!!.pause() } } @@ -470,7 +473,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram super.onResume() Choreographer.getInstance().postFrameCallback(this) if (NativeLibrary.isRunning()) { - emulationState.pause() + emulationState!!.pause() // If the overlay is enabled, we need to update the position if changed val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int @@ -488,7 +491,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } if (DirectoryInitialization.areCitraDirectoriesReady()) { - emulationState.run(emulationActivity.isActivityRecreated) + emulationState!!.run(emulationActivity.isActivityRecreated) } else { setupCitraDirectoriesThenStartEmulation() } @@ -496,7 +499,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun onPause() { if (NativeLibrary.isRunning()) { - emulationState.pause() + emulationState!!.pause() } Choreographer.getInstance().removeFrameCallback(this) super.onPause() @@ -512,7 +515,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (directoryInitializationState === DirectoryInitializationState.CITRA_DIRECTORIES_INITIALIZED ) { - emulationState.run(emulationActivity.isActivityRecreated) + emulationState!!.run(emulationActivity.isActivityRecreated) } else if (directoryInitializationState === DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED ) { @@ -1348,11 +1351,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height) - emulationState.newSurface(holder.surface) + emulationState!!.newSurface(holder.surface) } override fun surfaceDestroyed(holder: SurfaceHolder) { - emulationState.clearSurface() + emulationState!!.clearSurface() } override fun doFrame(frameTimeNanos: Long) { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt index 8f3b5dc07..b729bbc08 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationLifecycleUtil.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -18,11 +18,19 @@ object EmulationLifecycleUtil { } fun addShutdownHook(hook: Runnable) { - shutdownHooks.add(hook) + if (shutdownHooks.contains(hook)) { + Log.warning("[EmulationLifecycleUtil] Tried to add shutdown hook that already existed. Skipping.") + } else { + shutdownHooks.add(hook) + } } fun addPauseResumeHook(hook: Runnable) { - pauseResumeHooks.add(hook) + if (pauseResumeHooks.contains(hook)) { + Log.warning("[EmulationLifecycleUtil] Tried to add pause resume hook that already existed. Skipping.") + } else { + pauseResumeHooks.add(hook) + } } fun clear() {