libgdx: [Android] Loading of natives in static intializer makes it impossible to gracefully handle missing native libs
Issue details
Because GdxNativesLoader.load() is called in the static initializer of AndroidApplication, it’s not possible to run any code before it. This means that if native libs are not present the app immediately crashes, with no way to check for missing natives and handle the situation more gracefully (such as by displaying an error popup and closing the app).
This is an issue because of Google’s new app bundle functionality on Google Play. When app bundles are used, Google Play only distributes necessary resources to each device, including natives. This results in some nice space savings, but it also makes it very easy for someone to sideload the wrong APK and not get the natives they need.
I would think a better approach would be to load native libs in AndroidApplication.onCreate. This way code to check native libs could be run before calling super.onCreate. LibGDX could even perform the checking itself and display a standard error message, which a developer could then customize/override by manually checking earlier. This is much preferable to crashing as it won’t affect an application’s stability statistics on Google Play, and gives an opportunity to direct users to an APK which would work for them.
I’m not very familiar with the LibGDX codebase, so it’s possible I’m missing something here, but if there is agreement that moving native initialization to AndroidApplication.onCreate is a good idea I’d be happy to submit a pull request.
Reproduction steps/code
Can be reproduced in any libgdx android project by simply removing native libraries and commenting out native dependencies in build.gradle
Version of LibGDX and/or relevant dependencies
Tested on 1.9.10 but should affect all versions.
Stacktrace
(Example stack trace from a user crash on my game: Shattered Pixel Dungeon)
java.lang.ExceptionInInitializerError:
at java.lang.Class.newInstance (Class.java)
at android.app.AppComponentFactory.instantiateActivity (AppComponentFactory.java:69)
at androidx.core.app.CoreComponentFactory.instantiateActivity (CoreComponentFactory.java:43)
at android.app.Instrumentation.newActivity (Instrumentation.java:1215)
at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3008)
at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3257)
at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1948)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loop (Looper.java:214)
at android.app.ActivityThread.main (ActivityThread.java:7050)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:965)
Caused by: com.badlogic.gdx.utils.GdxRuntimeException:
at com.badlogic.gdx.utils.SharedLibraryLoader.load (SharedLibraryLoader.java:125)
at com.badlogic.gdx.utils.GdxNativesLoader.load (GdxNativesLoader.java:33)
at com.badlogic.gdx.backends.android.AndroidApplication.<clinit> (AndroidApplication.java:60)
at java.lang.Class.newInstance (Class.java)
at android.app.AppComponentFactory.instantiateActivity (AppComponentFactory.java:69)
at androidx.core.app.CoreComponentFactory.instantiateActivity (CoreComponentFactory.java:43)
at android.app.Instrumentation.newActivity (Instrumentation.java:1215)
at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3008)
at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3257)
at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1948)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loop (Looper.java:214)
at android.app.ActivityThread.main (ActivityThread.java:7050)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:965)
Caused by: java.lang.UnsatisfiedLinkError:
at java.lang.Runtime.loadLibrary0 (Runtime.java:1012)
at java.lang.System.loadLibrary (System.java:1669)
at com.badlogic.gdx.utils.SharedLibraryLoader.load (SharedLibraryLoader.java:119)
at com.badlogic.gdx.utils.GdxNativesLoader.load (GdxNativesLoader.java:33)
at com.badlogic.gdx.backends.android.AndroidApplication.<clinit> (AndroidApplication.java:60)
at java.lang.Class.newInstance (Class.java)
at android.app.AppComponentFactory.instantiateActivity (AppComponentFactory.java:69)
at androidx.core.app.CoreComponentFactory.instantiateActivity (CoreComponentFactory.java:43)
at android.app.Instrumentation.newActivity (Instrumentation.java:1215)
at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3008)
at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3257)
at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1948)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loop (Looper.java:214)
at android.app.ActivityThread.main (ActivityThread.java:7050)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:965)
Please select the affected platforms
- Android
- iOS (robovm)
- iOS (MOE)
- HTML/GWT
- Windows
- Linux
- MacOS
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 3
- Comments: 18 (18 by maintainers)
Links to this issue
Commits related to this issue
- Moved Android natives loading out of static init block (#5795) — committed to 00-Evan/libgdx by 00-Evan 3 years ago
- Moved Android natives loading out of static init block (#5795) (#6590) * Moved Android natives loading out of static init block (#5795) * GdxNativesLoader.nativesLoaded is now true after natives a... — committed to libgdx/libgdx by 00-Evan 3 years ago
- Squashed commit of the following: commit a6539945d6a53f39730da17d506c2bc827094c97 Author: MGSX <germain.aubert@gmail.com> Date: Fri Aug 6 11:49:51 2021 +0200 fix invalid GL calls in FrameBuffe... — committed to tommyettinger/libgdx by tommyettinger 3 years ago
Hence I think we should make a change and not switch to “never change a running system” mode just because changes might introduce new problems. If we know the static initializers are a problem, that should be changed. If the change introduces new problems, then it should be changed again.
You can use Android UI classes (e.g. textview or alertdialog) to display errors in the libGDX activity just the same as a separate one. It’s probably a bad idea to expect the libGDX classes to gracefully handle cases where initialization fails either way though. Will just move loading to init with no other changes and submit a pull request soon.
I’ve ended up working around this a bit with a separate activity in my project: https://github.com/00-Evan/shattered-pixel-dungeon/blob/master/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidLauncher.java
Basically AndroidLauncher is now a vanilla android Activity which loads natives, launches AndroidGame (which extends AndroidApplication), and finishes. If an exception occurs it displays a text view with an error message instead.