Skip to content

fix(android): share OkHttpClient and ImageLoader across image loads (fatal TooManyRequestsException) - #443

Merged
duguyihou merged 2 commits into
duguyihou:mainfrom
tss-ashishmishra:fix/shared-image-loader
Jul 23, 2026
Merged

fix(android): share OkHttpClient and ImageLoader across image loads (fatal TooManyRequestsException)#443
duguyihou merged 2 commits into
duguyihou:mainfrom
tss-ashishmishra:fix/shared-image-loader

Conversation

@tss-ashishmishra

@tss-ashishmishra tss-ashishmishra commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #440reloadImage() built a new OkHttpClient and a new Coil ImageLoader on every image load (the per-view ProgressInterceptor was the reason). Consequences in image-heavy apps:

  • Each ImageLoader registers a ConnectivityManager network callback. Android caps an app at 100 callbacks, so rendering ~100 images crashes with a fatal android.net.ConnectivityManager$TooManyRequestsException (frequently surfacing on an unrelated thread, because the cap is app-wide).
  • Each OkHttpClient owns its own dispatcher, thread, and connection pool — memory bloat and GC pressure under fast scrolling, up to OutOfMemoryError.

Approach

One shared OkHttpClient and at most two shared ImageLoaders (one per respectCacheHeaders value, driven by cachePolicy), built lazily over the Coil singleton with applicationContext.

onProgress keeps working:

  • Each request is tagged with the view tag via an internal header (X-TurboImage-View-Tag) added in the Coil request builder.
  • The shared ProgressInterceptor reads and strips the header before the request reaches the network, then resolves the listener from a new ProgressListeners registry (ConcurrentHashMap<Int, ProgressListener>).
  • Listeners are registered per load and unregistered in onDropViewInstance, so no view leaks.

Behavior is otherwise unchanged: newBuilder() over the Coil singleton preserves the same defaults the per-load code inherited, and headers don't participate in Coil's default cache keys, so cache behavior is unaffected.

Testing

  • Compiles clean against React Native 0.86 (New Architecture) with the library's Coil version.
  • The shared-loader approach has been running as a production patch-package patch in a large e-commerce app (~80–90% image content): the exact browsing flow that previously crashed with TooManyRequestsException every session survived 20+ consecutive sessions crash-free, and the OOM/GC-pressure symptoms disappeared.

Every reloadImage() built a new OkHttpClient and a new Coil ImageLoader
to attach a view-specific ProgressInterceptor. Each ImageLoader registers
a ConnectivityManager network callback; Android caps an app at 100, so
rendering ~100 images crashes with a fatal TooManyRequestsException.
Each OkHttpClient also owns its own thread + connection pool, causing
memory bloat under image-heavy scrolling.

Fix: one shared OkHttpClient and at most two shared ImageLoaders (one
per respectCacheHeaders value). onProgress is preserved: each request is
tagged with the view tag via an internal header that ProgressInterceptor
strips before the request hits the network, and the matching listener is
resolved from a ConcurrentHashMap registry (registered on load,
unregistered in onDropViewInstance).

Fixes duguyihou#440

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
duguyihou

This comment was marked as outdated.

Reconciles this branch's shared-OkHttpClient/ImageLoader fix (duguyihou#440) with
main's independently-merged duguyihou#442, which added networkObserverEnabled(false)
and load-signature skip-reload logic touching the same reloadImage()/
onDropViewInstance() code.

Keeps both fixes: main's networkObserverEnabled(false) is carried onto the
shared ImageLoader as defense-in-depth (it already disables the
ConnectivityManager callback directly), while this branch's loader/client
sharing still fixes the connection-pooling and memory-bloat half of duguyihou#440
that networkObserverEnabled(false) alone didn't address. Skip-reload
signature tracking and progress-listener registration coexist unchanged.
@duguyihou
duguyihou merged commit 65d2c2b into duguyihou:main Jul 23, 2026
7 checks passed
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.

bug: [Android] Severe Memory Leak & Crash: TooManyRequestsException due to new Coil ImageLoader per image

2 participants