【问题标题】:Decode image and add to list of widgets [flutter]解码图像并添加到小部件列表 [flutter]
【发布时间】:2020-04-29 11:40:18
【问题描述】:

我想从 Windows 中的一个文件夹加载和显示多个图像,并根据它们的大小(例如,大、中、图标等...)对它们进行分组。

为了在显示它们之前知道它们的大小,我使用'package:image/image.dart' 中的decodeImage 函数为每个图像循环一个列表。

我想要实现的是图像在每次迭代后出现在屏幕上,在所有加载过程完成之前不会阻塞 UI。

我有一个图像模型列表,一旦我将每个图像添加到列表中,我就会调用setState,以便更新视图,但所有图像都出现在循环的末尾。

我该如何解决这个问题?

这是调试控制台的输出(所有图像出现大约需要 10 秒):

C:\flutter\flutter-desktop-embedding\example>flutter run
Launching lib\main.dart on Windows in debug mode...
Building Windows application...
flutter: 2020-01-12 16:00:12.604871
flutter: 2020-01-12 16:00:13.160340
flutter: 2020-01-12 16:00:13.656014
Syncing files to device Windows...
flutter: 2020-01-12 16:00:14.050997
flutter: 2020-01-12 16:00:14.561593
flutter: 2020-01-12 16:00:15.040311
flutter: 2020-01-12 16:00:15.502076
flutter: 2020-01-12 16:00:15.936912
flutter: 2020-01-12 16:00:16.404174
flutter: 2020-01-12 16:00:16.919285
flutter: 2020-01-12 16:00:17.481826
flutter: 2020-01-12 16:00:17.941551
flutter: 2020-01-12 16:00:18.400322
flutter: 2020-01-12 16:00:18.864382
flutter: 2020-01-12 16:00:19.297922
flutter: 2020-01-12 16:00:19.745731
flutter: 2020-01-12 16:00:20.218457
flutter: 2020-01-12 16:00:20.654293
flutter: 2020-01-12 16:00:21.120047
flutter: 2020-01-12 16:00:21.629715
Syncing files to device Windows...                              13.892ms (!)

这是代码(这里我使用了一个 1920x1080 jpg 图像):

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as dart_image;

void main() {
  // See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override
  debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  runApp(new MyApp());
}

class ImageModel {
  final File file;
  final double height;
  final double width;

  ImageModel(this.file, {this.width, this.height});
}

class MyApp extends StatefulWidget {
  MyApp();

  @override
  State<StatefulWidget> createState() {
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  List<ImageModel> imageModelList = [];

  @override
  void initState() {
    super.initState();
    imageModelList = [];
    _loadImages();
  }

  void _loadImages() {
    final imagePaths = List.filled(20, 'C:/flutter/test/elephant.jpg');
    for (final imagePath in imagePaths) {
      final file = File(imagePath);
      file.readAsBytes().then((bytes) {
        final image = dart_image.decodeImage(bytes);
        // Here i should read the size to group the images based on their size.
        // For now i just add images with 1920x1080 size.
        if (image != null && image.width == 1920 && image.height == 1080) {
          final width = image.width.toDouble();
          final height = image.height.toDouble();
          final imageModel = ImageModel(file, width: width, height: height);
          print(DateTime.now());
          setState(() => imageModelList.add(imageModel));
        }
      }).catchError((err) {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: Text('title'),
        ),
        body: SingleChildScrollView(
          child: Wrap(
            children: imageModelList.map((imageModel) {
              return new Container(
                child: Image.file(
                  imageModel.file,
                  width: 160,
                  filterQuality: FilterQuality.medium,
                ),
              );
            }).toList(),
          ),
        ),
      ),
      title: 'title',
    );
  }
}

【问题讨论】:

    标签: flutter flutter-desktop


    【解决方案1】:

    发生这种情况是因为您在主隔离上一次调用 dart_image.decodeImage(bytes) 20 次,这将使分配给主隔离的 CPU 内核 100% 忙于解码图像,直到它们全部完成。因此,尽管正确调用了 setState,但 CPU 太忙而无法在将布局传递给 GPU 进行绘制之前计算布局,因此您的应用完全冻结。

    为了防止这种情况,您需要将繁重的工作委托给其他隔离区。 Flutter 提供了compute 函数,它将产生一个新的隔离,使用提供的参数在其中运行提供的函数,并返回一个带有结果的 Future。对于您的用例,它看起来像这样:

    dart_image.Image image = await compute<List<int>, dart_image.Image>(dart_image.decodeImage, byteArray);
    

    这是一篇非常相似的文章:https://www.gladimdim.org/flutter-unblocking-ui-thread-with-isolates-compute-function-ck03qrwnj000peks1zvhvko1x

    如果您的应用会经常执行此操作,您可能希望在应用启动时创建一个持久隔离池 - 理想情况下,使用与 逻辑 CPU 内核(-1 到占主要隔离) - 然后使用这些隔离来解码图像,而不是 compute 函数。这将显着提高您的应用程序的性能,因为在创建新隔离时存在开销(在数百毫秒的范围内,具体取决于设备性能),而在隔离之间传递数据的开销仅为数十毫秒(再次,取决于设备性能和传递的数据量)。

    请注意,如果您过度使用 compute,某些平台甚至可能会出现异常行为。自从我上次尝试以来,Flutter 可能已经改进了这一点,但是当同时执行多个 compute 函数时,iOS 应用程序通常会延迟几秒钟或根本没有收到来自 compute 的结果。当我用持久隔离替换 compute 时,这个问题完全消失了。

    【讨论】:

      猜你喜欢
      • 2020-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-06
      • 1970-01-01
      • 2021-05-01
      • 2016-04-22
      • 2021-03-01
      相关资源
      最近更新 更多