【问题标题】:Out of memory when displaying images in ListView在 ListView 中显示图像时内存不足
【发布时间】:2018-07-24 19:15:29
【问题描述】:

好的,所以我想从 LinearView 获取项目并获得它们的可见性,这样我就可以释放隐藏项目的内存。

有可能做到吗? 您还有其他想法吗?

所以基本上,我想显示从 URL 动态下载的图像流,我希望它们位于 ListView 中,以便人们可以滚动浏览它们,但如果我们只是使用像这样的普通构建器加载 ListView ,

ListView lvb = new ListView.builder(
    cacheExtent: 1.0,
    itemBuilder: (BuildContext context, int index) {
      return new ImageView(MyApp.imageLinks.values.elementAt(index));
    },
);

它会一次下载所有项目,并且设备会耗尽内存。所以我想我只是释放图像占用的内存,然后让用户加载更多图像而不必担心内存。

另外,我希望一次只加载 5 张图片,因此将显示 1 张,上方 2 张和下方 2 张,以便更快、更流畅地滚动。

我的问题:

  • 有没有办法确定项目是否已绘制(在视图中)?
  • 我可以限制我想在 LinearView 中拥有多少个 ImageView,但保持滚动能力。

编辑:承诺的 ImageView 类

class ImageView extends StatelessWidget {
  String url;

  ImageView([String url]) {
    if (url != null) {
      this.url = url;
    }
    print("New image");
  }

  @override
  Widget build(BuildContext context) {
    FractionallySizedBox fsb;
    if (url != null) {
      Image el = Image.network(
        url,
        scale: 0.1,
      );
      fsb = new FractionallySizedBox(
        widthFactor: 1.0,
        child: el,
      );
    print("New image created");
    return fsb;
  }
}

一旦 ListView 开始列出项目,它就会开始向控制台发送“已创建新图像”的垃圾邮件。

【问题讨论】:

    标签: android dart flutter


    【解决方案1】:

    我将不回答这个问题。理论上可以使用 Slivers 和 CustomScrollView 或可能使用自定义 RenderObjects 来确定项目是否被绘制,但我认为这不是您真正想要的。

    除非您对图像做一些奇怪的事情编辑:见结束,否则使用 ListView.builder 旨在防止您描述的确切场景。

    documentation for ListView.builder 的第一行(强调我的):

    创建一个可滚动的线性小部件数组,按需创建。

    使用 ListView.builder 而不是 new ListView([items]) 的想法是它应该自动处理您正在谈论的内容 - 只创建当前可见的元素。

    如果您已经实现了这一点,并且您实际上在真实设备上耗尽了内存,那么这将是一个向 Flutter 人提出错误的好案例 =D。

    另外,将cacheExtent 设置为 1.0 实际上会降低性能。来自cacheExtent docs

    视口在可见区域之前和之后都有一个区域要缓存 当用户滚动时即将变得可见的项目。

    位于此缓存区域中的项目即使在 (尚未)在屏幕上可见。 cacheExtent 描述了多少像素 缓存区域在前沿之前和尾部之后延伸 视口的边缘。

    通过将其设置为 1.0,您可以在图片离开视口时立即将其释放。并且下一个图像在它们几乎在视图中之前不会加载。因此,下次您滚动(后退或前进)时,必须加载(下一个或最后一个)图像,因为它进入视野。

    如果您将其设置得更高(或将其保留为当前的默认值 250.0),您可以设置它以便提前加载下一个图像,完全按照您的需要。

    编辑:

    所以这里还有一个额外的因素在起作用。感谢您发布您的 ImageView 代码。

    问题是您使用的 FractionallySizedBox 具有 widthFactor 但没有 HeightFactor。您将宽度设置为 1.0,但没有高度(这无论如何都不起作用),因此它占用了零空间,因此一直尝试永远加载图像。

    最好使用 SizedBox、ConstrainedBox 或 AspectRatio 为您的图像指定高度,然后使用 fit 选项为 Image.network 指定 containcover

    这是一个有趣的例子 - 无限加载狗 =)

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: new Scaffold(
            appBar: new AppBar(
              title: Text("Title"),
            ),
            body: ListView.builder(
              cacheExtent: 1000.0,
              itemBuilder: (BuildContext context, int index) {
                return new ImageView(
                  num: index,
                  url: "https://loremflickr.com/640/480/dog?random=$index",
                );
              },
            ),
          ),
        );
      }
    }
    
    class ImageView extends StatelessWidget {
      final int num;
      final String url;
    
      ImageView({@required this.num, @required this.url});
    
      @override
      Widget build(BuildContext context) {
        print("Building image number $num");
        return new AspectRatio(
          aspectRatio: 3.0 / 2.0,
          child: Image.network(
            url,
            fit: BoxFit.cover,
          ),
        );
      }
    }
    

    【讨论】:

    • 感谢您的回答 :D 我知道它是按需创建视图的说法,但是当我开始加载 ListView 时,颤动检查器中的内存使用量开始上升...它一开始就会加载大约 228 张图像。我将发布 ImageView 类的代码。非常感谢您的快速回复:D
    • 嗯,这可能与它如何确定图像大小有关 - 如果他们最初报告大小为零,那么它可能会继续加载越来越多。我来测试一下。
    • 感谢您的帮助,非常感谢您的帮助:D
    • 好吧,我猜还有一个错误...刚下载了前 228 张图片,很奇怪,但它只有 51MB...ibb.co/n3tOET
    • 渲染它们会占用比图像本身更多的空间 - JPG 通常是高度压缩的,需要分散到图像在内存中实际使用的像素数量(以及你已经完成了,它可能根本没有限制大小)。它仍然不应该导致崩溃,但是在 3gb 之前,android 有可能并且很可能会减少应用程序的内存使用量。
    【解决方案2】:

    我知道这是一个老话题,但仍然与我相关,我没有发现这些答案中的任何一个确实有效,并且提到的插件/小部件并没有真正提供太大的灵活性。我最终找到了小部件photo_gallery

    它足够灵活,可以加载特定专辑而不是所有专辑,并且 ThumbnailProvider 可以生成出色的图像而不会占用太多内存。我在 GridView 的同一页面上加载了多达 50 个 1+MB 文件。

    我不会在这里添加任何代码,因为 repo 中的示例就是您所需要的。我给你的唯一建议是设置 ThumbnailProvider 的宽度和高度。默认值是 72x72,现在已经很低了。我在旧的 Nexus 6 上使用 256x256 分辨率,没有任何内存问题。抱歉,我不会在 iOS 设备上发言。

    【讨论】:

    • 本地图库??网络图片呢
    • 这个问题主要发生在 iOS 设备上,android 没问题。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多