ListView 显示图片时出现内存不足问题

5

好的,我想要从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开始列出项目,就会在控制台中不断输出“New image created”。
2个回答

5
我将用一个非答案来回答这个问题。使用Slivers和CustomScrollView或者可能使用自定义RenderObjects理论上可以确定项目是否被绘制,但我认为这不是你实际想要的。
除非你对图像进行了奇怪的处理,使用ListView.builder旨在防止您描述的确切情况。
ListView.builder文档的第一行开始(强调我的):

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

使用ListView.builder而不是new ListView([items])的想法是它应该自动处理你正在谈论的事情-仅创建当前可见的元素。
如果您已经实现了这个并且在真实设备上实际上用完了内存,那么这将是提高Flutter人员的错误的好例子=D。
另外,将cacheExtent设置为1.0实际上会影响性能。从cacheExtent文档中得知:

视口在可见区域之前和之后有一个区域来缓存即将成为可见的项。

落入此缓存区域的项目会被布局,即使它们(尚未)未在屏幕上可见。cacheExtent描述了缓存区域在视口前沿和后沿之前延伸多少像素。

通过将其设置为1.0,您正在使得一旦图片超出视口范围,它就被释放。并且下一张图片直到几乎在视野中才加载。所以下次你滚动(向前或向后),(下一个或最后一个)图像将在进入视野时加载。
如果您将其设置得更高(或保持默认值,目前为250.0),则可以使下一个图像提前加载,正如您所希望的那样。 编辑: 因此,这里还有一个额外的因素。感谢您发布您的ImageView代码。
问题是您使用了带有widthFactor但没有HeightFactor的FractionallySizedBox。您将宽度设置为1.0,但没有高度(无论如何都不起作用),因此它占用零空间,因此一直尝试加载图像。
对于您的图像,最好使用SizedBox,ConstrainedBox或AspectRatio指定高度,然后使用Image.network的containcoverfit选项。
这里有一个有趣的例子-无限加载狗=)
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,
      ),
    );
  }
}

1
非常感谢您的回答:D 我知道有一种说法是按需创建视图,但是一旦我开始加载ListView,Flutter Inspector中的内存使用量就开始上升...它在启动时加载约228个图像。我将发布ImageView类的代码。非常感谢您的快速响应:D - MG lolenstine
谢谢你的帮助,我非常感激 :D - MG lolenstine
好的,我想还有一个bug... 刚刚下载了前228张图片,很奇怪,但是只有51MB... https://ibb.co/n3tOET - MG lolenstine
渲染它们所需要的空间比仅仅图像本身多得多 - JPG通常是高度压缩的,需要展开到图片在内存中实际使用的像素数量(而且根据你所做的情况,可能根本没有限制尺寸)。这仍然不应该导致崩溃,但很可能安卓在达到3GB之前就会切断您的应用程序的内存使用。 - rmtmckenzie
我已经为您制作了一个可工作的示例。请注意,如果您真的需要的话,您可能可以使所有图像具有不同的高度,并且只在需要时指定初始高度 - 但这意味着您的列表将不得不在其运行过程中不断重新计算高度。您可能需要使用Slivers来实现这一点,这会变得更加复杂。 - rmtmckenzie
显示剩余4条评论

1

我知道这是一个老话题,但对我仍然很相关,我没有找到任何一个答案真正起作用,而且提到的插件/小部件并没有真正允许太多的灵活性。最终我找到了photo_gallery小部件。

它足够灵活,可以加载特定的相册而不是所有相册,并且ThumbnailProvider生成的图像质量非常好,而且不会占用太多内存。我曾经在同一页中使用GridView加载了50个1MB以上的文件。

我不会在这里添加任何代码,因为示例库中的示例已经足够了。我唯一的建议是设置ThumbnailProvider的宽度和高度。默认值为72x72,这在今天已经相当低了。我在旧的Nexus 6上使用256x256而没有任何内存问题。抱歉,我不能代表iOS设备发言。


本地图库??那互联网上的图片呢? - Marcelo Juan Cabrera Gutierrez
问题主要出现在iOS设备上,Android则没有问题。 :) - Muhammad Adil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接