Skip to content

[Android] GIF image loading causes file descriptor leak (FD count grows indefinitely) only when images are visible on screen #186899

Closed

Description

@m7yun

Steps to reproduce

When loading and displaying GIF images on Android, the number of open file descriptors (FD) increases indefinitely and never gets released, eventually causing the app to become unresponsive or crash due to FD exhaustion.

Important finding: The FD leak only occurs when GIF images are actually visible on screen. When GIFs are scrolled off-screen or the widget is unmounted, the FD count does NOT increase. This suggests the leak is related to active decoding/rendering of GIF frames, not just caching or loading.

This issue started occurring in Flutter 3.41.x and persists in 3.44.0. The same code works perfectly in Flutter 3.38.10.

1.Create a simple Flutter app that loads GIF images (e.g., using Image.network or Image.asset)

2.Keep the GIFs visible on screen (e.g., in a ListView without scrolling, or a PageView)

3.Monitor FD count using /proc/self/fd

4.Observe FD count continuously increasing while GIFs are visible

5.Scroll the GIFs off-screen or navigate away — FD growth stops

Expected results

FD count should remain stable regardless of whether GIFs are visible on screen.

Actual results

GIFs visible on screen → FD count grows indefinitely

GIFs not visible (scrolled off-screen / disposed) → FD count remains stable

Eventually FD limit is reached (typically 1024-4096), causing app to slow down and crash

Code sample

Code sample
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('GIF FD Leak Test')),
        body: ListView.builder(
          itemCount: 20,
          itemBuilder: (context, index) => Image.network(
            'https://example.com/animated.gif', // Any GIF URL
            height: 300,
          ),
        ),
      ),
    );
  }
}

FD Monitoring Code (Kotlin)
Add this to MyApplication.kt:

Code sample
Handler(Looper.getMainLooper()).post(object : Runnable {
    override fun run() {
        val fdCount = File("/proc/self/fd").listFiles()?.size ?: 0
        Log.d("FD_Monitor", "Current FD count: $fdCount")
        Handler(Looper.getMainLooper()).postDelayed(this, 2000)
    }
})

Screenshots or Video

Screenshots / Video demonstration

[Upload media here]

Logs

// With GIFs VISIBLE on screen:

Logs
D/FD_Monitor: Current FD count: 247
D/FD_Monitor: Current FD count: 312
D/FD_Monitor: Current FD count: 389
D/FD_Monitor: Current FD count: 451
D/FD_Monitor: Current FD count: 523
D/FD_Monitor: Current FD count: 598
... (continues growing until crash)

// After scrolling GIFs OFF screen:

Logs
D/FD_Monitor: Current FD count: 598
D/FD_Monitor: Current FD count: 599
D/FD_Monitor: Current FD count: 598
D/FD_Monitor: Current FD count: 597
... (stable, no further growth)

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.41.6)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode (Version 15.4)
[✓] VS Code (version 1.89.1)
[✓] Connected device (1 available)

Activity

  1. m7yun commented on May 21, 2026

    @m7yun
    Author

    Additional Context
    Platform: Android only (iOS works fine)

    Android versions tested: 12, 13, 14

    Devices tested: Pixel, Xiaomi, Samsung

    Image format: GIF only (PNG, JPEG, WebP work fine)

    Leak condition: Only when GIF is actively being rendered on screen

    Not a caching issue: cached_network_image_ce does not solve it

    Possible Root Cause
    Based on the "visible only" behavior, the leak likely occurs in the on-screen rendering pipeline for animated GIFs, specifically:

    The frame decoder may be opening file descriptors per frame/animation cycle

    These FDs are not being closed when frames are rendered

    The issue was likely introduced by changes to ImageDecoder or SkCodec between Flutter 3.38.10 and 3.41.0

    Request
    Please investigate the GIF decoding and frame rendering implementation in the Android engine layer, with special attention to:

    File descriptor lifecycle during active GIF animation

    Differences in lib/ui/painting/image_decoder.cc between versions 3.38.10 and 3.41.0

    Whether SkCodec or ImageDecoder is properly closing resources after each frame

    Thank you!

  2. added
    in triagePresently being triaged by the triage team
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    needs repro infoAutomated crash report whose cause isn't yet known
    and removed
    in triagePresently being triaged by the triage team
    on May 21, 2026
  3. mbcorona commented on May 22, 2026

    @mbcorona
    Member

    Thanks for the report. I tried to reproduce this on Flutter 3.44.0 (the version you say still affects this) on a physical Android device, using a real GIF in the same ListView setup you described. I monitored the FD count via run-as for over 20 minutes with the GIFs visible.
    The FD count stayed stable between 150 and 202 the whole time — no growth, no approach to any limit. It actually went down over time as the initial network sockets closed.
    A few things that would help us investigate further:
    Could you share a runnable repro with a real GIF URL (not the example.com placeholder), ideally as a GitHub repo or gist?
    Your flutter doctor shows 3.41.6 but the report mentions 3.44.0 — did you actually test on 3.44.0? It didn't reproduce there for me.
    Could you also share full flutter doctor -v output, the device model, and whether your real app uses plugins that might open FDs on their own (networking, video, audio, camera, etc.)? Your starting FD count of 247 is already higher than what I see on a baseline Flutter app, so something in your setup may be contributing.

  4. removed
    in triagePresently being triaged by the triage team
    on May 22, 2026
  5. m7yun commented on May 22, 2026

    @m7yun
    Author

    Thanks for the report. I tried to reproduce this on Flutter 3.44.0 (the version you say still affects this) on a physical Android device, using a real GIF in the same ListView setup you described. I monitored the FD count via run-as for over 20 minutes with the GIFs visible. The FD count stayed stable between 150 and 202 the whole time — no growth, no approach to any limit. It actually went down over time as the initial network sockets closed. A few things that would help us investigate further: Could you share a runnable repro with a real GIF URL (not the example.com placeholder), ideally as a GitHub repo or gist? Your flutter doctor shows 3.41.6 but the report mentions 3.44.0 — did you actually test on 3.44.0? It didn't reproduce there for me. Could you also share full flutter doctor -v output, the device model, and whether your real app uses plugins that might open FDs on their own (networking, video, audio, camera, etc.)? Your starting FD count of 247 is already higher than what I see on a baseline Flutter app, so something in your setup may be contributing.

    Hello,

    After my testing, I found that this FD leak issue occurs when loading GIF images inside a ListView component. It happens with both the standard ListView and ListView.builder.

    Key observations:

    The FD count only increases when GIF images are visible on screen

    When I scroll up until all GIF images move off the visible area, the FD count stabilizes and stops growing

    Please note that my project only loads GIFs inside ListView. I have not tested other scrolling components such as GridView or SingleChildScrollView.

    You can reproduce this issue using the following sample code:

    Code sample
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      List<String> list = [
        'http://img-haodanku-com.cdn.fudaiapp.com/452ab4d5b7c95afba96735254fc9edb4.gif',
        'http://img.haodanku.com/d5e078f8c6e71841736f1df1c9cb12d2.gif',
        'http://img-haodanku-com.cdn.fudaiapp.com/5a859b97c53f631a1c50590defdc253c.gif',
        'http://video.haodanku.com/304f8426ac09c1bf7e2ee0d52df4dce0.gif',
        'http://img-haodanku-com.cdn.fudaiapp.com/954664d170dfed91cb4f0b9abf93eef8.gif',
        'http://img-haodanku-com.cdn.fudaiapp.com/717a79dbc29c2dd771facb47aef0c3ed.gif',
        'http://img.haodanku.com/61a08a81783bd253f6f59fc096e7fe3a.gif',
        'http://img-haodanku-com.cdn.fudaiapp.com/35eeadc246f3333de65398c0e5ea2164.gif',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
        'https://img.alicdn.com/bao/uploaded/O1CN01Xusw6y1T7TlxRXDiQ_!!6000000002335-0-yinhe.jpg',
      ];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: Text(widget.title),
          ),
          body: ListView(
            children: list
                .map(
                  (e) => Container(
                    alignment: Alignment.center,
                    child: Image.network(e, width: 100, height: 200),
                  ),
                )
                .toList(),
          ),
        );
      }
    }
    

    Log Output

    With GIFs VISIBLE on screen
    2026-05-23 02:28:25.665  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 131
    2026-05-23 02:28:27.668  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 190
    2026-05-23 02:28:29.672  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 206
    2026-05-23 02:28:31.675  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 224
    2026-05-23 02:28:33.696  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 241
    2026-05-23 02:28:35.700  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 259
    2026-05-23 02:28:37.704  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 276
    2026-05-23 02:28:39.715  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 292
    2026-05-23 02:28:41.725  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 304
    2026-05-23 02:28:43.738  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 322
    2026-05-23 02:28:45.747  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 340
    2026-05-23 02:28:47.756  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 357
    2026-05-23 02:28:49.766  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 373
    2026-05-23 02:28:51.775  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 390
    2026-05-23 02:28:53.782  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 407
    2026-05-23 02:28:55.793  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 424
    2026-05-23 02:28:57.800  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 442
    2026-05-23 02:28:59.810  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 459
    2026-05-23 02:29:01.818  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 477
    2026-05-23 02:29:03.836  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 493
    2026-05-23 02:29:05.849  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 509
    2026-05-23 02:29:07.861  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    After scrolling GIFs OFF screen
    2026-05-23 02:29:09.904  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    2026-05-23 02:29:11.929  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    2026-05-23 02:29:13.953  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    2026-05-23 02:29:15.973  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    2026-05-23 02:29:17.997  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    2026-05-23 02:29:20.019  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    2026-05-23 02:29:22.079  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 535
    2026-05-23 02:29:24.128  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 532
    2026-05-23 02:29:26.153  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 532
    2026-05-23 02:29:28.176  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 532
    2026-05-23 02:29:30.196  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 532
    2026-05-23 02:29:32.219  8865-8865  FD_Monitor              com.example.test                     D  Current FD count: 532

    Flutter Doctor output

    Doctor output
    Doctor summary (to see all details, run flutter doctor -v):
    [✓] Flutter (Channel stable, 3.44.0, on macOS 15.7.4 24G517 darwin-x64, locale
        zh-Hans-CN)
    [✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
    [✓] Xcode - develop for iOS and macOS (Xcode 26.2)
    [✓] Chrome - develop for the web
    [✓] Connected device (4 available)
    [✓] Network resources
    
    • No issues found!
  6. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on May 22, 2026
  7. jason-simmons commented on May 23, 2026

    @jason-simmons
    Member

    Reproduced this using the app from #186899 (comment) and the Impeller/GLES back end.

    The app is displaying an animated WebP image that loops infinitely, so the engine is continuously creating TextureGLES instances. The file descriptor leak happens because some of those textures are leaking their GL fence sync objects.

    Normally if a fence has been set on a TextureGLES, then it will be deleted when TextureGLES::Bind is called. But when the animated image creates frames nonstop, some of the resulting textures never get a Bind call. The TextureGLES destructor then does not clean up the fence.

  8. m7yun commented on May 24, 2026

    @m7yun
    Author

    Reproduced this using the app from #186899 (comment) and the Impeller/GLES back end.

    The app is displaying an animated WebP image that loops infinitely, so the engine is continuously creating TextureGLES instances. The file descriptor leak happens because some of those textures are leaking their GL fence sync objects.

    Normally if a fence has been set on a TextureGLES, then it will be deleted when TextureGLES::Bind is called. But when the animated image creates frames nonstop, some of the resulting textures never get a Bind call. The TextureGLES destructor then does not clean up the fence.

    Thank you for your attention and detailed response.

    I have verified that this issue only reproduces when Impeller is enabled. Running the app with flutter run --no-enable-impeller works fine without any FD leak.

    May I ask what the estimated timeline is for a fix and an upcoming stable release?

    Best regards.

  9. nay0958 commented on Jun 19, 2026

    @nay0958

    I tested in flutter to show animated GIFs in chat list screen many format from .WEBM and .mp4 and .gif but it is so FD is leaking in second.But if you want to show without leaking FD ,the safety way is using Lottie.json format it can prevent from leaking FD to show animated GIFs view in chat screen

  10. github-actions commented on Jul 14, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  11. locked as resolved and limited conversation to collaborators on Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

needs repro infoAutomated crash report whose cause isn't yet known

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions