[Android] Loading of natives in static intializer makes it impossible to gracefully handle missing native libs
See original GitHub issueIssue 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
Issue Analytics
- State:
- Created 4 years ago
- Reactions:3
- Comments:18 (18 by maintainers)
Top GitHub Comments
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.