Fix missing-assets detection race, 16 KB page alignment, and Android audio backend
build / build (push) Successful in 2m11s

- QuestShockActivity now actually blocks the native engine from starting
  when game data is missing, closing three gaps found via on-device
  testing: super.onCreate() must run unconditionally first (Android
  throws SuperNotCalledException otherwise); SDLActivity.mBrokenLibraries
  is now set provisionally before the storage-permission check, since
  onWindowFocusChanged() closing the permission dialog could otherwise
  start the engine before the async onRequestPermissionsResult() callback
  ran; and a new GameSurface (SDLSurface subclass) closes the actual gap
  that let the init_popups NULL-deref crash through even with
  mBrokenLibraries set - SDLSurface.surfaceChanged() starts the native
  thread directly without ever checking that flag.
- Force Android to use SDL2's openslES audio backend instead of AAudio
  (android/engine-patches/05-android-audio-driver.patch): AAudio only
  allows one open playback device at a time, but the engine opens two
  (cutscene audio via SDL_OpenAudioDevice, SFX/MIDI via Mix_OpenAudio),
  hitting an assertion failure on real hardware.
- Add 16 KB ELF page-size alignment (-Wl,-z,max-page-size=16384) to every
  Android shared library - the four prebuilts (SDL2, SDL2_mixer,
  fluidsynth-lite, gl4es, in build-image/Dockerfile) and the engine's own
  libmain.so (build.gradle) - matching Google's Play Store requirement
  for Android 15+ and clearing Android Studio's compatibility warning.
- Add a stageEngine Gradle task that automatically re-stages the patched
  engine/ copy and prebuilt libraries before any Android Studio build
  (hooked into preBuild, with proper up-to-date checking), so source/
  patch changes can't silently go stale in the build/android-engine
  scratch copy - previously a manual, easy-to-forget step. Skips
  automatically inside the build-image container so make apk/CI are
  unaffected.
This commit is contained in:
ml
2026-07-23 19:20:22 +02:00
parent ac640762d7
commit 4e47e0a989
8 changed files with 157 additions and 15 deletions
+49
View File
@@ -27,6 +27,49 @@ if (prebuiltDir == null) {
def questshockVersionName = project.hasProperty('questshockVersionName') ? project.property('questshockVersionName') : '0.0.0-dev'
def questshockVersionCode = project.hasProperty('questshockVersionCode') ? project.property('questshockVersionCode').toInteger() : 1
// Auto-refreshes the staged, patched engine/ copy and prebuilt libraries
// (see build-image/prepare-android-project.sh --host-paths, and the
// engineDir/prebuiltDir properties read above) so a plain Android Studio
// build/run can never silently compile against a stale scratch copy after
// engine/ or android/engine-patches/ change - previously a manual, easy to
// forget re-run. inputs/outputs are declared so Gradle skips the (Docker-
// invoking, not free) step entirely when nothing relevant actually changed,
// keeping pure-Java edit/run cycles fast.
def repoRoot = file("${projectDir}/../..")
def stageEngineTask = tasks.register("stageEngine", Exec) {
group = "build setup"
description = "Refreshes the patched engine/ scratch copy and prebuilt libraries for a native Android Studio build (build-image/prepare-android-project.sh --host-paths)."
workingDir repoRoot
commandLine "./run-image.sh", "bash", "build-image/prepare-android-project.sh", "--host-paths"
// /opt/prebuilt/android only ever exists inside the build-image
// container itself (baked in at image-build time, never on the host -
// see build-image/Dockerfile). Its presence means this build is
// build-apk.sh's own gradlew call, running INSIDE that container, which
// already staged everything itself in container mode before invoking
// gradlew - re-running this task there would try to `docker run` from
// inside a container with no docker socket, breaking make apk/CI. Only
// a genuine host-side Android Studio build (where this path is absent)
// needs this task.
onlyIf { !file('/opt/prebuilt/android').isDirectory() }
inputs.dir("${repoRoot}/engine")
inputs.dir("${projectDir}/../engine-patches")
inputs.file("${repoRoot}/build-image/Dockerfile")
outputs.dir("${repoRoot}/build/android-engine")
outputs.dir("${repoRoot}/build/android-prebuilt")
outputs.file("${projectDir}/../engine.properties")
}
// preBuild is what every variant's compile/native-build tasks already
// transitively depend on, regardless of AGP version's exact CMake task
// naming - the simplest reliable hook to run before any of them.
afterEvaluate {
tasks.named("preBuild").configure {
dependsOn stageEngineTask
}
}
android {
namespace "de.ladkau.questshock"
// compileSdk/buildToolsVersion/ndkVersion must all match what
@@ -67,6 +110,11 @@ android {
// which would otherwise miss our custom-installed SDL2 (see
// build-image/Dockerfile's SDL2_mixer build, which hit the
// exact same thing).
// -Wl,-z,max-page-size=16384: NDK 26 doesn't 16 KB-align ELF
// LOAD segments by default (only automatic in NDK 28+) - see
// build-image/Dockerfile's ANDROID_16KB_LDFLAGS, which does
// the same for the prebuilt SDL2/SDL2_mixer/fluidsynth-lite/
// gl4es .so's this links against.
arguments "-DENABLE_SDL2=ON", "-DENABLE_SOUND=BUNDLED", "-DENABLE_FLUIDSYNTH=BUNDLED", \
"-DANDROID_PREBUILT_DIR=${prebuiltDir}", \
"-DCMAKE_PREFIX_PATH=${prebuiltDir}/sdl2", \
@@ -74,6 +122,7 @@ android {
"-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH", \
"-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH", \
"-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH", \
"-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384", \
"-DANDROID_EXTRA_SOURCES=${projectDir}/src/main/cpp/questshock_native.c"
abiFilters 'arm64-v8a'
}