Comparative Analysis of Native Image Loading and External Texture Solutions in Flutter
This article examines Flutter's native image loading mechanisms, compares them with Android's traditional image frameworks, discusses the challenges of duplicated resources and memory usage, and details two external‑texture approaches—ShareContext and shared memory—providing code examples and practical recommendations for mobile projects.
Native Image Loading in Flutter
Flutter supports several native image sources through the Image.network() , Image.file() , Image.asset() and Image.memory() constructors. Each constructor delegates to a subclass of ImageProvider that overrides the load method.
The core loading logic is illustrated by the following snippet, which resolves an image key, checks the ImageCache , and invokes load when the image is not cached:
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
…………
final ImageStreamCompleter? completer = PaintingBinding.instance!.imageCache!.putIfAbsent(
key,
() => load(key, PaintingBinding.instance!.instantiateImageCodec),
onError: handleError,
);
if (completer != null) {
stream.setCompleter(completer);
}
}The ImageCache stores images in a Map<Object, _CachedImage> and exposes maximumSize and maximumSizeBytes properties for configuring the total number of cached images and their memory footprint.
Image Loading on Android
Android loads images into Bitmap or Drawable objects via network, file, or resource parsing. Popular third‑party libraries—Picasso, Glide, and Fresco—provide mature caching and processing capabilities.
Problems with Separate Resource Pools
When a project contains both Flutter and native modules, each platform allocates its own memory region for images, leading to duplicated resources, increased package size, higher memory consumption, and the inability to share native image‑processing features such as filters or watermarks.
Industry Solutions: External Textures
In 2018, Xianyu published an article about using external textures to share native texture data with Flutter for camera preview and video playback. The same concept can be applied to image rendering.
Two main implementations exist:
ShareContext / ShareGroup : creates an EGL (or iOS EAGL) shared context so that textures can be accessed across the Flutter and native OpenGL environments.
Shared Memory (EGLImageKHR) : leverages the EGLImageKHR extension to share 2D image data between EGL clients without modifying the Flutter engine.
ShareContext Implementation
The EGL shared‑context creation is performed with eglCreateContext using the share_context parameter:
/**
* share_context:
* Specifies another EGL rendering context with which to share data.
*/
EGLContext eglCreateContext(EGLDisplay display,
EGLConfig config,
EGLContext share_context,
const EGLint * attrib_list);By injecting the Flutter EGL context into the native context, the native platform can render textures that Flutter later consumes. This method requires engine modifications and is invasive.
Shared‑Memory Implementation
Flutter provides a built‑in TextureRegistry that creates a SurfaceTexture and registers it with a generated texture ID. The Android side draws a Bitmap onto the SurfaceTexture and returns the ID via a method channel:
new MethodChannel(getFlutterView(), "CHANNEL").setMethodCallHandler(new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
TextureRegistry textureRegistry = registrar.textures();
TextureRegistry.SurfaceTextureEntry entry = textureRegistry.createSurfaceTexture();
long textureId = entry.id();
SurfaceTexture surfaceTexture = entry.surfaceTexture();
Bitmap bitmap = createBitmapYouNeed();
Surface surface = new Surface(surfaceTexture);
Canvas canvas = surface.lockCanvas(rect);
canvas.drawBitmap(bitmap, null, rect, null);
bitmap.recycle();
surface.unlockCanvasAndPost(canvas);
result.success(textureId);
}
});Flutter retrieves the texture ID and displays it with the Texture widget:
static const platform = const MethodChannel('CHANNEL_NAME');
Future
getTextureView() async {
int textureId = await platform.invokeMethod('loadTexture');
return Container(child: Texture(textureId: textureId));
}Internally, the texture ID is registered with the native engine ( FlutterRenderer , FlutterJNI , platform_view_android.cc , texture.cc ) and later fetched by TextureLayer during scene construction.
Comparison and Recommendations
For pure Flutter projects, the built‑in ImageCache combined with list pre‑loading usually suffices. When complex texture processing or native image‑optimisation is required, external textures become valuable. In hybrid projects, the choice depends on how many native images need reuse and whether native processing capabilities must be leveraged.
Conclusion
Flutter’s native image loading is straightforward but limited to a single‑layer cache. Android/iOS provide richer caching frameworks. External‑texture techniques—ShareContext and shared memory—allow Flutter to render native textures, with shared memory being the preferred, non‑intrusive solution. Developers should weigh the maintenance cost of engine modifications against the performance benefits when selecting an image‑loading strategy.
IEG Growth Platform Technology Team
Official account of Tencent IEG Growth Platform Technology Team, showcasing cutting‑edge achievements across front‑end, back‑end, client, algorithm, testing and other domains.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.