简体   繁体   English

在 ListView 中显示图像时内存不足

[英]Out of memory when displaying images in ListView

Ok, so I want to get items from LinearView and get their visibility, so I can free memory on the hidden items.好的,所以我想从 LinearView 获取项目并获得它们的可见性,这样我就可以释放隐藏项目的内存。

Is it possible to do that?有可能这样做吗? Do you have any other ideas?你还有其他建议吗?

So basically, I want to show a stream of images being dynamically downloaded from URLs, I want them to be in a ListView, so the person can scroll through them, but if we just load the ListView with a normal builder like this,所以基本上,我想显示从 URL 动态下载的图像流,我希望它们在 ListView 中,这样人们就可以滚动它们,但是如果我们只是使用像这样的普通构建器加载 ListView,

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

it'd download all items at once and the device would run out of memory.它会一次下载所有项目,并且设备会耗尽内存。 So I was thinking that I'd just free the memory taken up by image and then allow user to load more images without having to worry about memory.所以我想我只是释放图像占用的内存,然后允许用户加载更多图像而不必担心内存。

Also, I'd like to only have 5 images loaded at a time, so 1 would be displayed, 2 above and 2 below for faster and more fluid scrolling.此外,我希望一次只加载 5 张图像,因此将显示 1 张,上方 2 张和下方 2 张,以便更快、更流畅地滚动。

My questions:我的问题:

  • Is there a way to determine if item is drawn(in view) or not?有没有办法确定项目是否被绘制(在视图中)?
  • Can I limit how many ImageViews I want to have in LinearView, but keeping the scrolling ability.我可以限制我想要在 LinearView 中有多少 ImageViews,但保持滚动能力。

EDIT: ImageView class as promised编辑:承诺的 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;
  }
}

As soon as the ListView starts to list items, it starts spamming "New image created" into the console.一旦 ListView 开始列出项目,它就会开始向控制台发送垃圾邮件“已创建新图像”。

I'm going to answer this with a non-answer.我将用不回答来回答这个问题。 It is theoretically possible to determine whether the item is drawn or not using Slivers and a CustomScrollView or possibly by using custom RenderObjects, but I don't think that's what you actually want.理论上可以确定是否使用 Slivers 和 CustomScrollView 或可能使用自定义 RenderObjects 来绘制该项目,但我认为这不是您真正想要的。

Unless you're doing something weird with your images EDIT: SEE END , using the ListView.builder is designed to protect against the exact scenario you're describing.除非您对图像做一些奇怪的事情EDIT: SEE END ,否则使用 ListView.builder 旨在防止您描述的确切场景。

From the first line of the documentation for ListView.builder (emphasis mine):ListView.builder 文档的第一行(重点是我的):

Creates a scrollable, linear array of widgets that are created on demand .创建按需创建的可滚动的线性小部件数组。

The idea of using ListView.builder as opposed to new ListView([items]) is that it should automatically handle exactly what you're talking about - creating only the currently visible elements.使用 ListView.builder 而不是 new ListView([items]) 的想法是它应该自动处理你正在谈论的内容 - 只创建当前可见的元素。

If you've implemented this and you are actually running out of memory on a real device, then that would be a good case for raising a bug with the Flutter people =D.如果您已经实现了这一点并且您实际上在真实设备上耗尽了内存,那么这将是一个很好的案例,可以向 Flutter 人员 =D 提出错误。

Also, setting cacheExtent to 1.0 will actually be worse for performance.此外,将cacheExtent设置为 1.0 实际上会降低性能。 From the cacheExtent docs :cacheExtent 文档

The viewport has an area before and after the visible area to cache items that are about to become visible when the user scrolls.视口在可见区域之前和之后都有一个区域,用于缓存用户滚动时即将变为可见的项目。

Items that fall in this cache area are laid out even though they are not (yet) visible on screen.落入该缓存区域的项目即使在屏幕上(尚)不可见,也会进行布局。 The cacheExtent describes how many pixels the cache area extends before the leading edge and after the trailing edge of the viewport. cacheExtent 描述了缓存区域在视口的前缘之前和后缘之后延伸了多少像素。

By setting that to 1.0, you're making it so that as soon as a picture is out of the viewport, it's being deallocated.通过将其设置为 1.0,您可以在图片离开视口时立即将其释放。 And that the next images don't load until they're pretty much in view.并且下一个图像在它们几乎可见之前不会加载。 So next time you scroll (back or forward), the (next or last) image will have to load as it's coming into view.因此,下次您滚动(向后或向前)时,(下一个或最后一个)图像必须在进入视图时加载。

If you set this higher (or leave it to the default which is currently 250.0) you can make it so that the next images will load ahead of time, exactly as you want.如果您将其设置得更高(或将其保留为当前的默认值 250.0),您可以设置它以便下一个图像将按照您的需要提前加载。

EDIT:编辑:

So there's an additional factor at work here.所以这里还有一个额外的因素在起作用。 Thanks for posting your ImageView code.感谢您发布您的 ImageView 代码。

The issue is that you're using a FractionallySizedBox with a widthFactor but no HeightFactor.问题是您使用的是带有 widthFactor 但没有 HeightFactor 的 FractionallySizedBox。 You're setting width to 1.0, but no height (which wouldn't work anyways), so it's taking up zero space and therefore keeps trying to load images forever.您将宽度设置为 1.0,但没有高度(无论如何都行不通),因此它占用的空间为零,因此一直尝试永远加载图像。

Your best bet to specify a height using a SizedBox, ConstrainedBox, or AspectRatio for your images and then either contain or cover with the fit option for Image.network.最好的办法是使用 SizedBox、ConstrainedBox 或 AspectRatio 为图像指定高度,然后使用 Image.network 的fit选项containcover

Here's a fun example - infinitely loading dogs =)这是一个有趣的例子 - 无限加载狗 =)

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,
      ),
    );
  }
}

I know this is an old topic, but still relevant to me and I didn't find any of these answers really worked and the plugins/widgets mentioned didn't really allow much flexibility.我知道这是一个老话题,但仍然与我相关,我没有发现这些答案中的任何一个真正有效,并且提到的插件/小部件并没有真正提供很大的灵活性。 I eventually found the widget photo_gallery我最终找到了小部件photo_gallery

It is flexible enough to allow for specific albums to be loaded rather than all albums and the ThumbnailProvider produces excellent images without costing too much memory.它足够灵活,可以加载特定专辑而不是所有专辑,并且 ThumbnailProvider 可以生成出色的图像而不会消耗太多内存。 I've had as many as 50 1+MB files loaded on the same page in a GridView.我在 GridView 的同一页面上加载了多达 50 个 1+MB 的文件。

I'm not going to add any code here because the example in the repo is all you need.我不会在这里添加任何代码,因为 repo 中的示例就是您所需要的。 The only advise I'd give you is to set the width and height for the ThumbnailProvider.我给您的唯一建议是设置 ThumbnailProvider 的宽度和高度。 The default is 72x72 with is pretty low these days.默认为 72x72,这些天相当低。 I used 256x256 without any memory issues on an old Nexus 6. Sorry, I can't speak for iOS devices.我在旧的 Nexus 6 上使用了 256x256,没有任何内存问题。抱歉,我不能说 iOS 设备。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM