Skip to content

fix(android): free decoded images on the new backend - #427

Merged
mfazekas merged 6 commits into
mainfrom
fix/android-new-image-property
Oct 29, 2026
Merged

mfazekas merged 6 commits into
mainfrom
fix/android-new-image-property

Conversation

@mfazekas

@mfazekas mfazekas commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

On the new Android backend, every ViewModelImageProperty.set() decoded a new ImageAsset on the command server and never closed it, even when setting the same image again. Swapping images grew GPU memory from 304 MB to 4.8 GB over 211 swaps (2048×2048 PNGs, emulator). The handle is now closed right after setImage (the property keeps its own ref), and memory stays flat at ~311 MB.

set(undefined) now clears the image instead of being ignored. Sets run on a single-threaded scope so they apply in call order. Before, a clear could be overtaken by an earlier set that was still decoding.

Adds a harness test for set/clear. Memory isn't asserted automatically; use the AndroidMemoryLeak reproducer from #426 (image swap scenarios) to check.

Every image property set() decoded a new ImageAsset on the command server
and never closed it, so each swap leaked a decoded image. The handle is now
closed once the property holds its own ref. set(undefined) clears the image
instead of being ignored, and sets apply in call order.
@mfazekas
mfazekas force-pushed the fix/android-new-image-property branch from dd8c598 to c1d6aa6 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.

🟡 Changes recommended

An Android coroutine race can restore an image after clearing, and the regression test neither waits for mutation completion nor verifies the cleared state.

2 open issues
What changed in this PR

This PR addresses decoded-image handle leaks in the new Android backend, complementing the legacy-backend fixes in #426.

Changes:

  • Closes temporary decoded image handles after assignment.
  • Adds image clearing via set(undefined) and attempts to preserve setter call order.
  • Adds a set/clear harness test, skipping clears on experimental iOS.
File Description
example/​__tests__/​image-property-clear.harness.ts Adds repeated image set/clear calls and resource cleanup.
android/​src/​new/​java/​com/​margelo/​nitro/​rive/​HybridViewModelImageProperty.kt Releases decoded handles, supports clearing, and changes coroutine dispatching.

Review effort: Balanced

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

The Android setter still allows a suspended image decode to overwrite a later clear, while the test neither waits for native completion nor verifies the final cleared state.

1 open issue
1 resolved since last review

Review effort: Balanced

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

Independent sequencing state across image-property lookups allows an earlier decode to overwrite a later clear, while the harness can pass without verifying asynchronous set/clear results.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

High severity Reuse image property state across repeated lookups

android/​src/​new/​java/​com/​margelo/​nitro/​rive/​HybridViewModelImageProperty.kt:30

HybridViewModelInstance.imageProperty() creates a fresh wrapper on every lookup (HybridViewModelInstance.kt:79–81), so repeated lookups use independent generation counters and dispatchers. Calling instance.imageProperty(path).set(image) followed by instance.imageProperty(path).set(undefined) can therefore let the earlier decode finish after the clear and restore the image. Share the sequencing and generation state per underlying instance/path, or reuse the property wrapper across lookups.

1 resolved since last review

Review effort: Balanced

@mfazekas

Copy link
Copy Markdown
Collaborator Author

Re the previously missed issue in Copilot's last review (independent sequencing per imageProperty() lookup): fixed in 9a27f55. HybridViewModelInstance now keeps one sequencer per path (a serial scope plus a generation counter), and every wrapper it returns for that path shares it.

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

The Android image set/clear harness can pass despite failed native writes because it neither waits for asynchronous operations nor verifies their results before disposing resources.

No open issues 🎉

Review effort: Balanced

@HayesGordon HayesGordon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@mfazekas
mfazekas merged commit 0dd9b17 into main Oct 29, 2026
15 checks passed
@mfazekas
mfazekas deleted the fix/android-new-image-property branch October 29, 2026 13:46
mfazekas pushed a commit that referenced this pull request Oct 29, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.5.8](v0.5.7...v0.5.8)
(2026-10-29)


### Bug Fixes

* **android:** free decoded images on the new backend
([#427](#427))
([0dd9b17](0dd9b17))
* dispose a RiveFile that finishes loading after unmount
([#428](#428))
([b42ab4c](b42ab4c))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

3 participants