Android: Isolate game execution process

The launcher and GameActivity previously ran in the same process. Native
globals and library state therefore survived after returning to the
launcher, leaking static data from one game to the next game.

Run GameActivity in a private :game process and terminate it after SDL
shutdown completes, so every game starts with fresh native state. Keep
the launcher in the main process and refresh its game list from disk
when it resumes.
This commit is contained in:
kichikuou
2026-07-19 09:09:21 +09:00
parent 9f8c2af842
commit e3c168c5a6
4 changed files with 55 additions and 16 deletions
+1
View File
@@ -78,6 +78,7 @@
<activity android:name=".GameActivity"
android:label="@string/app_name"
android:process=":game"
android:alwaysRetainTaskState="true"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:theme="@style/AppTheme"
@@ -20,6 +20,7 @@ package io.github.kichikuou.xsystem35
import android.app.AlertDialog
import android.media.MediaPlayer
import android.os.Bundle
import android.os.Process
import android.text.InputType
import android.util.Log
import android.widget.EditText
@@ -56,6 +57,18 @@ class GameActivity : SDLActivity() {
midi.onActivityResume()
}
override fun onDestroy() {
try {
super.onDestroy()
} finally {
try {
midi.release()
} finally {
Process.killProcess(Process.myPid())
}
}
}
override fun getLibraries(): Array<String> {
return arrayOf("SDL2", "xsystem35")
}
@@ -75,7 +88,6 @@ class GameActivity : SDLActivity() {
return
}
File(gameRoot, Launcher.TITLE_FILE).writeText(str)
Launcher.updateGameList()
}
private fun textInputDialog(msg: String, oldVal: String, maxLen: Int, result: Array<String?>) {
@@ -154,11 +166,21 @@ class GameActivity : SDLActivity() {
}
private class MidiPlayer {
private enum class State {
STOPPED,
PLAYING,
PAUSED,
RELEASED,
}
private val player = MediaPlayer()
private var playing = false
private var playerPaused = false
private var state = State.STOPPED
fun start(path: String, loop: Boolean) {
if (state == State.RELEASED) {
return
}
state = State.STOPPED
try {
player.apply {
reset()
@@ -167,7 +189,7 @@ private class MidiPlayer {
prepare()
start()
}
playing = true
state = State.PLAYING
} catch (e: IOException) {
Log.e("midiStart", "Cannot play midi", e)
player.reset()
@@ -175,27 +197,41 @@ private class MidiPlayer {
}
fun stop() {
if (playing && player.isPlaying) {
player.stop()
playing = false
when (state) {
State.PLAYING, State.PAUSED -> {
player.stop()
state = State.STOPPED
}
State.STOPPED, State.RELEASED -> Unit
}
}
fun currentPosition(): Int {
return if (playing) player.currentPosition else 0
return when (state) {
State.PLAYING, State.PAUSED -> player.currentPosition
State.STOPPED, State.RELEASED -> 0
}
}
fun onActivityStop() {
if (playing && player.isPlaying) {
if (state == State.PLAYING && player.isPlaying) {
player.pause()
playerPaused = true
state = State.PAUSED
}
}
fun onActivityResume() {
if (playerPaused) {
if (state == State.PAUSED) {
player.start()
playerPaused = false
state = State.PLAYING
}
}
fun release() {
if (state == State.RELEASED) {
return
}
state = State.RELEASED
player.release()
}
}
@@ -60,9 +60,6 @@ class Launcher private constructor(rootDir: File) {
return gLauncher!!
}
fun updateGameList() {
gLauncher?.updateGameList()
}
}
private val store = GameStore(rootDir)
@@ -160,7 +157,7 @@ class Launcher private constructor(rootDir: File) {
observer?.onGameListChange()
}
private fun updateGameList() {
fun refreshGameList() {
store.updateGameList()
observer?.onGameListChange()
}
@@ -68,6 +68,11 @@ class LauncherActivity : Activity(), LauncherObserver {
super.onDestroy()
}
override fun onResume() {
super.onResume()
launcher.refreshGameList()
}
private fun onListItemClick(position: Int) {
startGame(launcher.games[position].path, null)
}