Optimizing Memory
Treat memory as ownership over time. A useful test repeatedly enters and leaves the same journey, then checks whether live views, pooled objects, images, tasks, and native resources reach a stable plateau.
Common Retention Sources
- intervals, timeouts, and animation callbacks that survive a view;
- notification, registry, lifecycle, network, or native-service subscriptions;
- async callbacks that retain a screen after its result is no longer relevant;
- navigation stacks and tab trees that intentionally retain logical views;
- unbounded image/data caches;
- pooled objects that are never released or are preloaded too aggressively;
- video, audio, cast, purchase, and native-sheet sessions whose owners never close them.
Release View-Owned Work
Use onUnmount() for work whose lifetime is the mounted view, and call super.onUnmount():
private refreshTimer?: ReturnType<typeof setInterval>;
private unsubscribe?: () => void;
override onUnmount(): void {
if (this.refreshTimer !== undefined) {
clearInterval(this.refreshTimer);
this.refreshTimer = undefined;
}
this.unsubscribe?.();
this.unsubscribe = undefined;
super.onUnmount();
}
Also cancel application-owned requests or guard their completion so a late result cannot update a reused or unmounted view. Do not rely on garbage collection to unregister native listeners.
Pooled classes have a second lifetime boundary. onWillRelease() should stop external work and drop references; onWillReuse() should restore the state required for the next owner. The framework restores its declared state defaults, but it cannot infer ownership of vendor objects, timers, or callbacks.
Understand Hibernation
hibernateView() releases a view’s renderer tree while retaining its logical view and state; wakeView() recreates renderers and invalidates state for rendering. This can reduce renderer memory in deep navigation, but it is not equivalent to unmounting:
- view objects and app state remain alive;
- view-owned subscriptions remain the owner’s responsibility;
- wake-up has a recreation cost;
- state that assumes a continuously live renderer must be tested.
Prefer the measured AppConfig aggregateViewHibernationPolicy for supported navigation transitions over ad hoc calls throughout application code.
Size Pools With Evidence
NodePool and InstancePool trade retained memory for fewer allocations. Check:
- whether free and total counts stabilize after repeated navigation;
- whether a class remains in use after its screen is gone;
- whether preloaded counts exceed the peak concurrent requirement;
- whether reused cells/views reset external and visual state correctly.
Hosanna DevTools’ Instance Pool diagnostics show pooled and in-use objects. Reduce preloads before disabling reuse; a lifecycle leak is usually made worse, not solved, by growing a pool.
Images and Data
- Match decoded image dimensions to the rendered surface.
- Bound caches by entries or bytes and define an eviction owner.
- Release obsolete detail payloads, search results, and temporary downloads when navigation no longer needs them.
- Avoid keeping duplicate normalized, filtered, and presentation copies of large catalogs.
- Stream or page large data sets where the application contract permits it.
Compressed file size is not decoded memory. Compare memory while the same images are visible, after they leave the viewport, and after the owning screen is released.
Diagnose a Suspected Leak
- Use a production-like build and fixed data.
- Record memory and pool counts before the journey.
- Repeat enter/use/exit at least several times.
- Allow normal asynchronous cleanup to settle.
- Compare live views, in-use pool rows, image/cache entries, timers, and native sessions.
- Remove one owner or cache at a time and repeat.
- Verify on real low-memory hardware.
Browser heap snapshots can identify JavaScript retainers, but they do not prove SceneGraph or native renderer memory. Use the platform profiler or device memory tools for the renderer under test.
Low-Memory Behavior
Roku runtime code subscribes to available low-memory device events and forwards device-info events into the application runtime. A release must still define its own safe degradation policy: evict recreatable caches, avoid starting optional work, preserve the current user action, and recover without a relaunch where possible.
Test low-memory handling together with navigation, images, and playback. Do not make a low-memory callback perform a large synchronous cleanup that creates a worse frame stall.