You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have confirmed the issue is present in the latest version of rive-android
I have searched the documentation and forums and could not find an answer
I have searched existing issues and this is not a duplicate
Description
The rive-android AAR ships its own libc++_shared.so, built with NDK r27c. fbjni 0.8.0 and later (released 2026-09-25) is built with NDK r28c. Its libfbjni.so imports __cxa_init_primary_exception, which NDK r27's libc++ doesn't define and r28's does. An APK can hold only one libc++_shared.so per ABI, so an app that depends on both libraries needs pickFirst, as the README's Troubleshooting section suggests. If the copy that pickFirst keeps is Rive's, the app crashes when fbjni loads:
java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__cxa_init_primary_exception" referenced by "/data/app/~~…/libfbjni.so"...
There's no native crash or signal. dlopen fails, and the UnsatisfiedLinkError is thrown from the System.loadLibrary (or SoLoader) call that loads fbjni. In our app, Flipper loads fbjni at startup in debug builds, so every debug launch crashed. Release builds, which don't include fbjni, work normally.
Expected: adding rive-android next to another library that ships libc++_shared.so shouldn't break that library, or the README should say which copy to keep.
This affects the latest release: 11.14.1's libc++_shared.so is byte-identical to 11.13.0's on all four ABIs.
Evidence. These come from the AARs on Maven Central. The results are the same on arm64-v8a, armeabi-v7a, x86 and x86_64.
# NDK that built each JNI library (decoded from .note.android.ident)
librive-android.so (rive-android 11.14.1) Android r27c 12479018
libfbjni.so (fbjni 0.8.1) Android r28c 13676358
# Compiler of each bundled libc++_shared.so (llvm-readelf -p .comment, last clang line;
# both files also carry a common "clang version 18.0.1" line)
rive-android 11.14.1 Android (12470979, ... based on r522817c) clang version 18.0.3 # r27c
fbjni 0.8.1 Android (13624864, ... based on r530567e) clang version 19.0.1 # r28c
On every ABI, this is the only symbol libfbjni.so needs that Rive's libc++ lacks. The reverse direction works. fbjni 0.8.1's libc++ exports everything Rive's copy exports, plus 4 more symbols, and every libc++ symbol that librive-android.so imports resolves against it.
Why pickFirst alone can't fix this safely.pickFirst keeps one copy and drops the others. When two AARs both ship a copy, the order of dependencies on the runtime classpath decides which one is kept. A newer libc++_shared.so runs code built with older NDKs (r11 and later), but code built with a newer NDK can need symbols an older copy doesn't have (an NDK maintainer explains this in android/ndk#1639). So the kept copy has to come from the newest NDK used by any library in the APK. Today, an app that combines Rive with another c++_shared library built with r28 or newer has to make sure Rive's copy isn't the one kept, usually by overriding it by hand, and keep that override current as dependencies change. The README's pickFirst advice doesn't mention this.
App-side workaround. Put the newer libc++_shared.so (here, fbjni 0.8.1's) in app/src/main/jniLibs/<abi>/. AGP prefers the app module's own jniLibs over AAR copies, so this copy always wins pickFirst. Rive renders normally on it.
Possible fixes. Any of these would help:
Build with NDK r28c or newer. This is the smallest code change. kotlin/build.gradle.kts already says the 16 KB page-size flag "Can remove when upgrading to r28+". The bump also means changing the r27c check in rive-runtime's build/rive_build_config.lua and the NDK downloads in the CI workflows. Shipping a newer copy should be safe for consumers, because older-built libraries keep working on a newer libc++. For example, every libc++ symbol librive-android.so imports today resolves against r28c's copy. One caveat: a bump keeps the "newest copy must win" rule in place. If an r28-built Rive imports newer symbols, an app that keeps an older r27 copy (for example one from a current React Native release, which is still on r27b) would break Rive instead.
Link libc++ statically (-DANDROID_STL=c++_static) so the AAR no longer ships libc++_shared.so. This removes the conflict for good. The NDK C++ support guide says "JNI libraries distributed with Java AARs must not use the shared runtime to avoid conflicting with other libraries and the app." The middleware vendors guide recommends a single JNI library with the STL linked statically and a version script. Rive already meets those conditions:
each ABI has a single librive-android.so
since 11.9.2, a version script exports only JNI_OnLoad and Java_app_rive_*
it builds with no-exceptions no-rtti
Rive linked libc++ statically once before (Use static libc++, not shared #69, fixed by moved from using the static lib to the shared #70, whose diff switches to libc++_static despite its title) and switched to c++_shared during the 2023 CMake migration. VerifyNativeReleaseTask and ExtractNativeSymbolsTask currently require libc++_shared.so, so they would need updating.
At minimum, update the README Troubleshooting section. It should say that the kept libc++_shared.so must come from the newest NDK among all dependencies, and name the NDK each release is built with.
Previous working version
None. This isn't a Rive regression. It shows up as soon as an app adds a native library built with r28 (fbjni 0.8.0 and later).
Reproduction steps / code
These steps are distilled from our app, where Flipper loads fbjni at startup.
Set up an app like this:
dependencies {
implementation("app.rive:rive-android:11.14.1")
implementation("com.facebook.fbjni:fbjni:0.8.1")
}
android {
// Without this, the build fails on the duplicate libc++_shared.so
packaging.jniLibs.pickFirsts +="lib/*/libc++_shared.so"
}
Load fbjni at startup, for example System.loadLibrary("fbjni") in Application.onCreate(). Flipper does the same through SoLoader. You don't need to initialize Rive.
Launch the app. If the APK kept Rive's libc++_shared.so, it crashes with the error above.
Dependency order decides which copy is kept. To force Rive's copy, put Rive's jni/<abi>/libc++_shared.so in app/src/main/jniLibs/<abi>/.
To see which copy the APK has, run llvm-nm -D --defined-only lib/<abi>/libc++_shared.so | grep __cxa_init_primary_exception. Rive's copy prints nothing and fbjni's prints a T line.
Rive Android runtime version
11.13.0 in our app. 11.14.1 (latest) ships the same libc++_shared.so, byte-identical on all ABIs, as does every release from 11.9.2 on.
Rive API
Compose
Device
Any. The failure is in dynamic-linker symbol resolution, not device-specific. We saw it on every device in our automated test runs.
Device OS
Any (see Device).
App minimum SDK level
28
App NDK level
AGP 9.4.1 default, r28c (28.2.13676358).
Dependencies with native libraries
com.facebook.fbjni:fbjni:0.8.1, built with NDK r28c and shipping its own libc++_shared.so. Flipper loads it at startup, in debug builds only.
Submission checklist
rive-androidDescription
The
rive-androidAAR ships its ownlibc++_shared.so, built with NDK r27c.fbjni0.8.0 and later (released 2026-09-25) is built with NDK r28c. Itslibfbjni.soimports__cxa_init_primary_exception, which NDK r27's libc++ doesn't define and r28's does. An APK can hold only onelibc++_shared.soper ABI, so an app that depends on both libraries needspickFirst, as the README's Troubleshooting section suggests. If the copy thatpickFirstkeeps is Rive's, the app crashes when fbjni loads:There's no native crash or signal.
dlopenfails, and theUnsatisfiedLinkErroris thrown from theSystem.loadLibrary(or SoLoader) call that loads fbjni. In our app, Flipper loads fbjni at startup in debug builds, so every debug launch crashed. Release builds, which don't include fbjni, work normally.Expected: adding
rive-androidnext to another library that shipslibc++_shared.soshouldn't break that library, or the README should say which copy to keep.This affects the latest release: 11.14.1's
libc++_shared.sois byte-identical to 11.13.0's on all four ABIs.Evidence. These come from the AARs on Maven Central. The results are the same on arm64-v8a, armeabi-v7a, x86 and x86_64.
On every ABI, this is the only symbol
libfbjni.soneeds that Rive's libc++ lacks. The reverse direction works. fbjni 0.8.1's libc++ exports everything Rive's copy exports, plus 4 more symbols, and every libc++ symbol thatlibrive-android.soimports resolves against it.Why
pickFirstalone can't fix this safely.pickFirstkeeps one copy and drops the others. When two AARs both ship a copy, the order of dependencies on the runtime classpath decides which one is kept. A newerlibc++_shared.soruns code built with older NDKs (r11 and later), but code built with a newer NDK can need symbols an older copy doesn't have (an NDK maintainer explains this in android/ndk#1639). So the kept copy has to come from the newest NDK used by any library in the APK. Today, an app that combines Rive with anotherc++_sharedlibrary built with r28 or newer has to make sure Rive's copy isn't the one kept, usually by overriding it by hand, and keep that override current as dependencies change. The README'spickFirstadvice doesn't mention this.App-side workaround. Put the newer
libc++_shared.so(here, fbjni 0.8.1's) inapp/src/main/jniLibs/<abi>/. AGP prefers the app module's own jniLibs over AAR copies, so this copy always winspickFirst. Rive renders normally on it.Possible fixes. Any of these would help:
Build with NDK r28c or newer. This is the smallest code change.
kotlin/build.gradle.ktsalready says the 16 KB page-size flag "Can remove when upgrading to r28+". The bump also means changing the r27c check in rive-runtime'sbuild/rive_build_config.luaand the NDK downloads in the CI workflows. Shipping a newer copy should be safe for consumers, because older-built libraries keep working on a newer libc++. For example, every libc++ symbollibrive-android.soimports today resolves against r28c's copy. One caveat: a bump keeps the "newest copy must win" rule in place. If an r28-built Rive imports newer symbols, an app that keeps an older r27 copy (for example one from a current React Native release, which is still on r27b) would break Rive instead.Link libc++ statically (
-DANDROID_STL=c++_static) so the AAR no longer shipslibc++_shared.so. This removes the conflict for good. The NDK C++ support guide says "JNI libraries distributed with Java AARs must not use the shared runtime to avoid conflicting with other libraries and the app." The middleware vendors guide recommends a single JNI library with the STL linked statically and a version script. Rive already meets those conditions:librive-android.soJNI_OnLoadandJava_app_rive_*no-exceptions no-rttiRive linked libc++ statically once before (Use static libc++, not shared #69, fixed by moved from using the static lib to the shared #70, whose diff switches to
libc++_staticdespite its title) and switched toc++_sharedduring the 2023 CMake migration.VerifyNativeReleaseTaskandExtractNativeSymbolsTaskcurrently requirelibc++_shared.so, so they would need updating.At minimum, update the README Troubleshooting section. It should say that the kept
libc++_shared.somust come from the newest NDK among all dependencies, and name the NDK each release is built with.Previous working version
None. This isn't a Rive regression. It shows up as soon as an app adds a native library built with r28 (fbjni 0.8.0 and later).
Reproduction steps / code
These steps are distilled from our app, where Flipper loads fbjni at startup.
dependencies { implementation("app.rive:rive-android:11.14.1") implementation("com.facebook.fbjni:fbjni:0.8.1") } android { // Without this, the build fails on the duplicate libc++_shared.so packaging.jniLibs.pickFirsts += "lib/*/libc++_shared.so" }System.loadLibrary("fbjni")inApplication.onCreate(). Flipper does the same through SoLoader. You don't need to initialize Rive.libc++_shared.so, it crashes with the error above.jni/<abi>/libc++_shared.soinapp/src/main/jniLibs/<abi>/.llvm-nm -D --defined-only lib/<abi>/libc++_shared.so | grep __cxa_init_primary_exception. Rive's copy prints nothing and fbjni's prints aTline.Rive Android runtime version
11.13.0 in our app. 11.14.1 (latest) ships the same
libc++_shared.so, byte-identical on all ABIs, as does every release from 11.9.2 on.Rive API
Compose
Device
Any. The failure is in dynamic-linker symbol resolution, not device-specific. We saw it on every device in our automated test runs.
Device OS
Any (see Device).
App minimum SDK level
28
App NDK level
AGP 9.4.1 default, r28c (28.2.13676358).
Dependencies with native libraries
com.facebook.fbjni:fbjni:0.8.1, built with NDK r28c and shipping its ownlibc++_shared.so. Flipper loads it at startup, in debug builds only.Additional context
cannot locate symbol "__emutls_get_address" referenced by librive-android.so) look like the same problem with the roles reversed: there, the keptlibc++_shared.sowas older than the NDK Rive was built with ([question] __emutls_get_address change in r23b causes compatibility issues for libc++_shared.so android/ndk#1639).