Skip to content

Android: decouple JNI setup from unused subsystems#15895

Draft
HTRamsey wants to merge 1 commit into
libsdl-org:mainfrom
HTRamsey:android-subsystem-decoupling
Draft

Android: decouple JNI setup from unused subsystems#15895
HTRamsey wants to merge 1 commit into
libsdl-org:mainfrom
HTRamsey:android-subsystem-decoupling

Conversation

@HTRamsey

Copy link
Copy Markdown
Contributor

Summary

On Android, JNI setup previously assumed every subsystem was compiled in: JNI_OnLoad/checkJNIReady waited on the audio and controller manager classes unconditionally, and SDLActivity always created a surface, clipboard handler, and controller listeners. As a result a build with subsystems disabled (e.g. -DSDL_VIDEO=OFF, -DSDL_AUDIO=OFF, joystick-only) would either fail to link, stall in checkJNIReady() because SDL_SetMainReady() never fired, or crash dereferencing a null surface.

This decouples the Android JNI layer from individual subsystems so SDL builds and runs with any subsystem disabled, and supports a headless SDLActivity (no rendering surface) when SDL_VIDEO_DISABLED is set.

Changes

C (src/core/android/)

  • Group the JNI native-method tables and method-ID lookups by subsystem and guard them with the matching SDL_*_DISABLED macros. The SDLControllerManager table is gated on joystick (haptic requires joystick, enforced in CMake), audio on SDL_AUDIO_DISABLED, video/input and dialog rows likewise.
  • JNI_OnLoad only registers the manager classes that are compiled in; checkJNIReady() only waits on the classes that exist, so SDL_SetMainReady() fires in reduced builds.
  • New JNI method nativeGetCompiledSubsystems() exports the compiled-in subsystem mask to Java so the Java layer can match the native build.
  • Header declarations reorganized to mirror the same guards.

Java (android-project/.../org/libsdl/app/)

  • SDL.setupJNI() intersects the requested subsystems with nativeGetCompiledSubsystems() and only sets up the audio / controller managers whose bits survive — this is what prevents the checkJNIReady() stall.
  • SDLActivity gates surface, layout, clipboard, drop-file, orientation, and controller-listener setup on the active subsystems, and is null-safe on the lifecycle paths when there is no surface (headless).
  • SDLControllerManager only builds a haptic handler when haptic is active and null-guards the relative-mouse paths for the headless case.

Testing

Built arm64-v8a, Release, NDK r27, android-21, -DSDL_SHARED=ON, with each subsystem disabled individually plus an all-off "minimal" config. All 13 configurations configure, compile, and link cleanly. Stripped libSDL3.so sizes (llvm-strip):

Config libSDL3.so (stripped) Δ vs baseline
baseline (everything on) 2,452,472
SDL_POWER=OFF 2,450,488 −1.9 KB
SDL_HAPTIC=OFF 2,449,504 −2.9 KB
SDL_DIALOG=OFF 2,448,808 −3.6 KB
SDL_SENSOR=OFF 2,447,072 −5.3 KB
SDL_CAMERA=OFF 2,436,288 −15.8 KB
SDL_AUDIO=OFF 2,430,512 −21.4 KB
SDL_GPU=OFF 2,232,096 −215.2 KB
SDL_JOYSTICK=OFF + SDL_HAPTIC=OFF 2,173,768 −272.2 KB
SDL_RENDER=OFF 2,124,824 −320.0 KB
SDL_HIDAPI=OFF 1,971,880 −469.3 KB
SDL_VIDEO=OFF (headless) 1,855,144 −583.3 KB
all of the above (minimal) 1,316,184 −1,109.7 KB

A minimal Android build is roughly 46% smaller than the full library.

Static/build verification is complete across all configs. On-device runtime validation of a headless activity and a controller-only build is still outstanding.

Query the natively compiled-in subsystems at runtime so the Java side
only registers and initializes the managers that exist, fixing
UnsatisfiedLinkError when SDL is built with a subsystem disabled
(e.g. -DSDL_AUDIO_DISABLED).

- SDL.setupJNI() calls nativeGetCompiledSubsystems() and skips
  SDLAudioManager/SDLControllerManager setup for absent subsystems.
- Gate SDLControllerManager.initializeDeviceListener() and the
  joystick key-event path on the controller subsystem; gate the
  clipboard handler on video, mirroring the native guards.
- Guard the SDLControllerManager JNI bindings with
  SDL_ANDROID_NEED_CONTROLLER_MANAGER (joystick or haptic enabled).
- Reorganize SDL_android.h/.c into guarded per-subsystem groups with
  matching ordering.

if ((subsystems & SDL_INIT_AUDIO) != 0) {
int compiled = SDLActivity.nativeGetCompiledSubsystems();
mInitializedSubsystems = subsystems & compiled;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mInitializedSubsystems = subsystems & compiled;
mInitializedSubsystems = (subsystems & compiled);

// C functions we call
public static native String nativeGetVersion();
public static native void nativeSetupJNI();
public static native int nativeGetCompiledSubsystems();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HTRamsey, @AntTheAlchemist, do we need to do something with proguard here?

SDLActivity.getContentView().requestPointerCapture();
} else {
SDLActivity.getContentView().releasePointerCapture();
if (SDLActivity.getContentView() != null) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put this into a temporary variable.

}

if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
if (mRelativeModeEnabled && !SDLActivity.isDeXMode() && SDLActivity.getContentView() != null) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please put this into a temporary variable.

return SDL_Unsupported();
}

#ifndef SDL_HAPTIC_DISABLED

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to the top of the function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants