Skip to content

fix(android): drop the extra native ref on legacy view model instances - #429

Open
mfazekas wants to merge 1 commit into
mainfrom
fix/android-legacy-vmi-extra-ref
Open

mfazekas wants to merge 1 commit into
mainfrom
fix/android-legacy-vmi-extra-ref

Conversation

@mfazekas

@mfazekas mfazekas commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

rive-android's legacy ViewModel.create*Instance returns a native pointer that already owns one ref, and the ViewModelInstance constructor takes a second ref that release() never drops. Every instance we create therefore outlives its File, along with any image bound to it. In the AndroidMemoryLeak reproducer (#426), mounting a screen with a bound 2048×2048 image grew GPU memory from 342 MB to 6.7 GB over 294 mounts. With this change it stays flat at ~305 MB over 366 mounts.

The workaround drops the extra ref, via reflection on cppDerefInstance, right after each instance we create in HybridViewModel and in the view's Auto/ByName binding. Doing that on a rive-android release without the bug would free an instance still in use, so it's gated on the rive-android version this module is built against (now exposed as BuildConfig.RIVE_ANDROID_VERSION). It applies only to 10.4.5 through 11.14.3, the range where the double ref exists (every release up to the current latest). Other versions, or a failed reflection lookup, leave behavior unchanged. One residual risk: if an app forces a different rive-android through Gradle dependency resolution rather than RiveRuntimeAndroidVersion, the gate sees the wrong version. Instances that rive-android auto-binds internally (autoBind) aren't covered.

Based on a patch from a user. Verified on an emulator with the legacy backend: the full harness suite passes (including useViewModelInstance, view-recreate and instance lookup/reuse flows), and the mount/unmount and image swap scenarios ran for several hundred cycles without crashes. Independent of #426 in code; the measurement needs both.

rive-android's ViewModel.create*Instance hands Kotlin a pointer that already
owns a native ref and the ViewModelInstance constructor adds another that is
never dropped, so created instances and their bound images outlive their
File. Drop it for rive-android 10.4.5 through 11.14.3, gated on the version
the module is built against.
@mfazekas
mfazekas force-pushed the fix/android-legacy-vmi-extra-ref branch from 87bb26f to 4115e96 Compare October 28, 2026 08:41
@HayesGordon
HayesGordon requested a balanced review from Copilot October 28, 2026 09:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Private JNI refcount manipulation changes native lifetimes and needs maintainer review and runtime validation across supported SDK versions, especially when Gradle resolves a version different from the BuildConfig gate.

No open issues 🎉

What changed in this PR

This PR addresses the remaining legacy Android view-model instance leak described in #426 by dropping an extra native reference, allowing instances and their bound images to be released.

Changes:

  • Adds a reflection-based workaround gated to rive-android versions 10.4.5–11.14.3.
  • Applies it to instance creation in HybridViewModel and the view’s Auto/ByName bindings.
  • Exposes the configured rive-android version through BuildConfig for the version gate.
File Description
android/​src/​legacy/​java/​com/​rive/​RiveReactNativeView.kt Applies the workaround to Auto and ByName binding instances.
android/​src/​legacy/​java/​com/​margelo/​nitro/​rive/​ViewModelInstanceExtraRef.kt Adds the version-gated native-reference workaround.
android/​src/​legacy/​java/​com/​margelo/​nitro/​rive/​HybridViewModel.kt Applies the workaround to all four instance-creation methods.
android/​build.gradle Exposes RIVE_ANDROID_VERSION in BuildConfig.

Review effort: Balanced

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