#9664·lynx

[Bug][Android][4.0.1] Flattened <image> lifecycle: destroyImage/clearNodeIndexImageMap remove managers without calling LynxImageManager.destroy()

Author: 1428062090Created Sep 18, 2026Updated Sep 18, 2026

System Info

System:

  • Platform: Android
  • Device used for observation: Samsung SM-A9080

Binaries / packages:

  • org.lynxsdk.lynx:lynx:4.0.1 (release)
  • org.lynxsdk.lynx:lynx:4.0.1-dev (debug)
  • org.lynxsdk.lynx:lynx-base:4.0.1 / 4.0.1-dev
  • org.lynxsdk.lynx:lynx-jssdk:4.0.1
  • org.lynxsdk.lynx:lynx-service-image:4.0.1

Integration notes:

  • Native Android integration using LynxView.
  • We inspected the resolved 4.0.1 AAR bytecode and added local diagnostic/lifecycle instrumentation for investigation.

Details

Summary

While investigating Native Heap growth in a long-running Android Lynx course page, we found two image-manager lifecycle paths in the resolved Lynx 4.0.1 AAR that remove image managers without explicitly calling LynxImageManager.destroy().

We would like to confirm whether this is expected behavior or an Android-side lifecycle bug, and whether it has already been fixed in Lynx 4.1.x.

This report is specifically about deterministic release of image-manager resources. It does not claim that this is the only source of our overall Native Heap growth; a separate root-layout loop is still under investigation.

Observed paths in 4.0.1

1. PlatformRendererContext.destroyImage(int)

The method obtains/removes a LynxImageManager from the root UIBodyView through:

rootView.obtainImageAccordingToNodeIndex(imageKey)

but the original 4.0.1 implementation does not subsequently invoke:

imageManager.destroy()

Our local investigation indicates that LynxImageManager.destroy() is the explicit image-resource release path, including the image/Fresco reference cleanup owned by the manager.

2. UIBody.UIBodyView.clearNodeIndexImageMap()

The method clears mImageMap directly, without first iterating over the retained LynxImageManager values and calling destroy() on each one.

Local diagnostic patch used for verification

For investigation only, we applied the following narrow lifecycle patch to the resolved 4.0.1 AAR:

PlatformRendererContext.destroyImage(int):

java
LynxImageManager manager = rootView.obtainImageAccordingToNodeIndex(imageKey);
if (manager != null) {
  manager.destroy();
}

UIBody.UIBodyView.clearNodeIndexImageMap():

java
for (LynxImageManager manager : mImageMap.values()) {
  if (manager != null) {
    manager.destroy();
  }
}
mImageMap.clear();

The patch preserves the original map-removal/clear behavior; it only adds the manager lifecycle release before the existing removal is completed.

Why we believe this deserves confirmation

In our course-page scenario, Native Heap grows during repeated page/image transitions and a large portion is released only when the full LynxView / Activity is destroyed. The bitmap cache itself remained bounded, so we were looking for image objects whose explicit native resource release might be skipped while their Java-side bookkeeping is removed.

The two paths above are candidates for delayed resource release because the manager is removed/cleared but its explicit destroy() lifecycle method is not reached.

Questions

  1. Is it intentional that PlatformRendererContext.destroyImage(int) does not call LynxImageManager.destroy() after obtainImageAccordingToNodeIndex(...)?
  2. Is it intentional that UIBody.UIBodyView.clearNodeIndexImageMap() clears image managers without calling destroy() first?
  3. If another native or Java lifecycle path is expected to release these managers, could you point us to it? We would like to verify the ownership contract.
  4. Has either path changed in Lynx 4.1.x? If so, which version contains the fix?
  5. If this is a valid bug, would a fix equivalent to the two snippets above be acceptable upstream?

We can provide sanitized bytecode diffs, logs, and a minimized reproducer if needed.

Reproduce link

No response

Reproduce Steps

  1. Use Lynx Android 4.0.1 with a page containing flattened <image> elements.
  2. Render images, then repeatedly remove image nodes / switch course pages while keeping the same LynxView alive.
  3. Exercise the Android image-destruction and root-image-map cleanup paths.
  4. Inspect the resolved 4.0.1 AAR implementation of PlatformRendererContext.destroyImage(int) and UIBody.UIBodyView.clearNodeIndexImageMap().
  5. Observe that image-manager bookkeeping is removed or cleared without an explicit LynxImageManager.destroy() invocation.

Our production bundle is private. We are preparing a sanitized minimal reproduction based on the Lynx repro template if maintainers need one.