rive-android icon indicating copy to clipboard operation
rive-android copied to clipboard

fix: native library loading

Open DatL4g opened this issue 10 months ago • 3 comments

Introduce a NativeLoader which takes care of loading native libraries. ReLinker is not needed on API Level > 23 and causes more trouble than it solves.

Refs: #341

DatL4g avatar Feb 15 '25 15:02 DatL4g

Does this fix #341, or is it just related to it? I am not entirely sure what's going on here. 😅

ernestoyaquello avatar Feb 16 '25 21:02 ernestoyaquello

@ernestoyaquello It's related to the mentioned issue since it's a known bug in ReLinker for years. It will probably fix the issue as well, at least I don't see any reason why it should not work after merging.
I also use this kind of method in production apps and works without crashes, no matter if real/emulator or architecture.

More Context

Rive uses ReLinker to load native code which is only needed on older Android API Levels (below 23, Android Marshmallow).
It uses it's own native code loading process instead of System.loadLibrary because older phones did not support this properly.

Starting with Android 23 and up the System.loadLibrary works without issues.

Code Explanation

The updated and introduced code checks if the phone is old, then it uses ReLinker, otherwise it uses the normal System.loadLibrary method.
Additionally it won't crash the application if Rive could not be initialized, the Rive.init method returns a Boolean whether it could be loaded. This way people can dynamically disable animations, only thing to mention is that it will always return false if it's called more than once.

Work Around while not merged

I use this code to initialize rive properly:
Btw I am using a more complex version of NativeLoader here which is not required for rive.

/**
 * Retrieves the rive native binary name using reflection, falls back to "rive-android"
 * Uses NativeLoader to load the native binary
 * Sets the default renderer using reflection
 */
fun Rive.initSafely(
    context: Context,
    defaultRenderer: RendererType = defaultRendererType
) {
    val riveClass = "app.rive.runtime.kotlin.core.Rive"
    val libName = runCatching {
        val clazz = Class.forName(riveClass)
        val field = clazz.getDeclaredField("RIVE_ANDROID")
        field.isAccessible = true
        field.get(null) as? String
    }.getOrNull()?.trim()?.ifBlank { null } ?: "rive-android"

    val libLoaded = NativeLoader.loadLibrary(context, libName)
    val rendererSet = this.defaultRendererType == defaultRenderer || runCatching {
        val clazz = Class.forName(riveClass)
        val field = clazz.getDeclaredField("defaultRendererType")
        field.isAccessible = true
        field.set(clazz, defaultRenderer)
    }.isSuccess

    if (libLoaded && rendererSet) {
        initializeCppEnvironment()
    } else {
        init(context, defaultRenderer)
    }
}

DatL4g avatar Feb 17 '25 18:02 DatL4g

HI @DatL4g, thanks for your contribution!

It looks very promising and we're keen to find a way to resolve #341 for all those who reported it. As I mentioned in the issue thread, before integrating this PR, we'd like to test this and make sure this is solving the crashes.

Could you give us more specific repro steps? Which specific devices/ABIs have you confirmed experience the crash with the current implementation?

Once we validate this, we're happy to merge it

umberto-sonnino avatar Feb 21 '25 12:02 umberto-sonnino