首页 > 解决方案 > 需要使用 Isolates 来加速我的应用程序中的文件处理

问题描述

该应用程序的作用:

问题是什么:

我试过的:

有些事情可能没有意义,因为这是更大代码的精简版本。但是一切都应该正常工作,在这个例子中,它可以创建 pdf 或 png 文件,我更喜欢 png 文件,这是最耗时的文件。

完整代码https://github.com/fenchai23/pdf_isolate_test.git

是孤立的 pdf 包的作者的孤立示例,但它非常简单,而不是我想要做的。

这是完整的 main.dart 文件:

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Pdf Isolate Test'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List _invoice = [
    {
      'qty': '1',
      'code': '27274-7J125 SET',
      'desc': 'A/A NIS PATHFINDER NEW 2015 - 2PC  ALMERA  27275-1N605',
      'invt': '615',
      'codealt': 'AC2503 SET',
      'descalt': '',
      'group': '10.FILTRO A/A',
      'price': '5.53',
      'disc1': '4.42',
      'globalPrice': '5.53',
      'total': '5.53'
    },
    {
      'qty': '1',
      'code': '27274-EA000 SET',
      'desc':
          'A/A NIS FRONTIER VQ40  YD25 PATHFIADER D40  VITARRA J20 27274-EL00A 27277-4JA0A',
      'invt': '1018',
      'codealt': 'AC2507SET',
      'descalt': '27277-4JA0A' 'GRAN VITARRA J20',
      'group': '10.FILTRO A/A',
      'price': '4.25',
      'disc1': '3.40',
      'globalPrice': '4.25',
      'total': '4.25'
    }
  ];

  static Future<List<File>> generateInvoice(Map args) async {
    print('creating pdf');

    final String _cartSubTotal = '1.00';
    final String _cartTotal = '1.01';
    final String _cartItbms = '7.00%';

    final DateTime _startTime = DateTime.now();

    final String dateTimeStamp = DateTime.now().toString();

    PdfImage _logo;

    List<Product> products = [];

    final List customRecordData = args['customRecordData'];
    final String customClientName = args['customClientName'];
    final String customClientDirection = args['customClientDirection'];
    final String customClientSeller = args['customClientSeller'];
    final bool isPdf = args['isPdf'];

    for (final k in customRecordData) {
      Product productsHolder = Product(
        k['qty'],
        k['code'],
        k['desc'],
        k['globalPrice'],
        k['total'],
      );
      products.add(productsHolder);
    }

    final pw.Document pdf = pw.Document();

    _logo = PdfImage.file(pdf.document,
        bytes: (await rootBundle.load('assets/icons/appIcon.png'))
            .buffer
            .asUint8List());

    pw.Widget _pdfHeader(pw.Context context) {
      return pw.Column(
        children: [
          pw.Row(
            crossAxisAlignment: pw.CrossAxisAlignment.start,
            children: [
              pw.Expanded(
                child: pw.Column(
                  children: [
                    pw.Container(height: 2),
                    pw.Container(
                      decoration: pw.BoxDecoration(
                          borderRadius: 2,
                          border:
                              pw.BoxBorder(color: PdfColors.blue, width: 2)),
                      padding: const pw.EdgeInsets.only(
                          left: 10, top: 5, bottom: 5, right: 10),
                      alignment: pw.Alignment.centerLeft,
                      height: 60,
                      child: pw.DefaultTextStyle(
                        style: pw.TextStyle(
                          color: PdfColors.black,
                          fontSize: 10,
                        ),
                        child: pw.Column(
                          mainAxisSize: pw.MainAxisSize.min,
                          crossAxisAlignment: pw.CrossAxisAlignment.start,
                          mainAxisAlignment: pw.MainAxisAlignment.spaceEvenly,
                          children: [
                            pw.Text('Cliente:  $customClientName', maxLines: 1),
                            pw.Text('Dir:  $customClientDirection',
                                maxLines: 1),
                            pw.Text('Vend:  $customClientSeller', maxLines: 1),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              pw.Expanded(
                child: pw.Column(
                  mainAxisSize: pw.MainAxisSize.min,
                  children: [
                    pw.Container(
                      alignment: pw.Alignment.topRight,
                      padding: const pw.EdgeInsets.only(bottom: 8, left: 30),
                      height: 72,
                      child: pw.Image(_logo),
                    ),
                  ],
                ),
              ),
            ],
          ),
          if (context.pageNumber > 1) pw.SizedBox(height: 20),
        ],
      );
    }

    pw.Widget _contentTable(pw.Context context) {
      const tableHeaders = ['CANT', 'CODIGO', 'DESCRIPCION', 'PRECIO', 'TOTAL'];

      return pw.Table.fromTextArray(
        context: context,
        border: null,
        cellAlignment: pw.Alignment.centerLeft,
        headerDecoration: pw.BoxDecoration(
          borderRadius: 2,
          color: PdfColors.blue,
        ),
        headerHeight: 25,
        cellHeight: 25,
        cellAlignments: {
          0: pw.Alignment.center,
          1: pw.Alignment.centerLeft,
          2: pw.Alignment.centerLeft,
          3: pw.Alignment.center,
          4: pw.Alignment.centerRight,
        },
        headerStyle: pw.TextStyle(
          color: PdfColors.white,
          fontSize: 10,
          fontWeight: pw.FontWeight.bold,
        ),
        cellStyle: const pw.TextStyle(
          color: PdfColors.black,
          fontSize: 10,
        ),
        rowDecoration: pw.BoxDecoration(
          border: pw.BoxBorder(
            bottom: true,
            color: PdfColors.grey,
            width: .5,
          ),
        ),
        headers: List<String>.generate(
          tableHeaders.length,
          (col) => tableHeaders[col],
        ),
        data: List<List<String>>.generate(
          products.length,
          (row) => List<String>.generate(
            tableHeaders.length,
            (col) => products[row].getIndex(col),
          ),
        ),
      );
    }

    pw.Widget _contentFooter(pw.Context context) {
      return pw.Row(
        crossAxisAlignment: pw.CrossAxisAlignment.start,
        children: [
          pw.Expanded(
            flex: 2,
            child: pw.Column(
              crossAxisAlignment: pw.CrossAxisAlignment.start,
              children: [
                pw.Text(
                  '',
                  style: pw.TextStyle(
                    color: PdfColors.black,
                    fontWeight: pw.FontWeight.bold,
                  ),
                ),
                pw.Container(
                  margin: const pw.EdgeInsets.only(top: 20, bottom: 8),
                  child: pw.Text(
                    '',
                    style: pw.TextStyle(
                      color: PdfColors.black,
                      fontWeight: pw.FontWeight.bold,
                    ),
                  ),
                ),
                pw.Text(
                  '',
                  style: const pw.TextStyle(
                    fontSize: 8,
                    lineSpacing: 5,
                    color: PdfColors.black,
                  ),
                ),
              ],
            ),
          ),
          pw.Expanded(
            flex: 1,
            child: pw.DefaultTextStyle(
              style: const pw.TextStyle(
                fontSize: 10,
                color: PdfColors.black,
              ),
              child: pw.Column(
                crossAxisAlignment: pw.CrossAxisAlignment.start,
                children: [
                  pw.Row(
                    mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                    children: [
                      pw.Text('Sub Total:'),
                      pw.Text(_cartSubTotal),
                    ],
                  ),
                  pw.SizedBox(height: 5),
                  pw.Row(
                    mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                    children: [
                      pw.Text('I.T.B.M.S:'),
                      pw.Text(_cartItbms),
                    ],
                  ),
                  pw.Divider(color: PdfColors.black),
                  pw.DefaultTextStyle(
                    style: pw.TextStyle(
                      color: PdfColors.black,
                      fontSize: 14,
                      fontWeight: pw.FontWeight.bold,
                    ),
                    child: pw.Row(
                      mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                      children: [
                        pw.Text('Total:'),
                        pw.Text(_cartTotal),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      );
    }

    pw.Widget _pdfFooter(pw.Context context) {
      return pw.DefaultTextStyle(
        style: pw.Theme.of(context).defaultTextStyle.copyWith(
              color: PdfColors.grey,
            ),
        child: pw.Row(
          mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
          children: [
            pw.Container(
              child: pw.Text(dateTimeStamp),
              margin: const pw.EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
            ),
            pw.Container(
              alignment: pw.Alignment.centerRight,
              margin: const pw.EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
              child: pw.Text(
                'Página ${context.pageNumber} de ${context.pagesCount}',
              ),
            ),
          ],
        ),
      );
    }

    // Here is where PDF (Invoice) is created

    pdf.addPage(
      pw.MultiPage(
        pageTheme: pw.PageTheme(
          buildBackground: (pw.Context context) => pw.FullPage(
            ignoreMargins: true,
            child: pw.Container(
              color: PdfColors.white,
            ),
          ),
          pageFormat: PdfPageFormat.letter.copyWith(
            marginBottom: 1.5 * PdfPageFormat.cm,
            marginTop: 1.5 * PdfPageFormat.cm,
            marginLeft: 1 * PdfPageFormat.cm,
            marginRight: 1 * PdfPageFormat.cm,
          ),
        ),
        header: _pdfHeader,
        footer: _pdfFooter,
        build: (pw.Context context) => <pw.Widget>[
          pw.Padding(
            padding: pw.EdgeInsets.only(bottom: 12.0),
          ),
          _contentTable(context),
          pw.Padding(
            padding: pw.EdgeInsets.only(bottom: 12.0),
          ),
          _contentFooter(context),
        ],
      ),
    );

    final output = await getTemporaryDirectory();

    final timef = dateTimeStamp;
    // get a safe name for filenames
    final safeClientName =
        customClientName.replaceAll(RegExp("[\\\\/:*?\"<>|]"), '');
    final String fileName = '$timef--$customClientSeller--$safeClientName';

    File file;

    var imageList = List<File>();

    var pagesToPrint;

    pagesToPrint = await Printing.raster(pdf.save()).length;

    if (!isPdf) {
      int pageIndex = 1;

      await for (var page in Printing.raster(pdf.save(), dpi: 300)) {
        final image = await page.toPng();

        final String pageQty =
            (pagesToPrint > 1) ? '$pageIndex-$pagesToPrint' : '1-1';

        file = File("${output.path}/$fileName--$pageQty.png");

        await file.writeAsBytes(image);

        imageList.add(file);

        pageIndex++;
      }
    } else {
      file = File("${output.path}/$fileName.pdf");

      await file.writeAsBytes(pdf.save());

      imageList.add(file);
    }

    print('done creating pdf');

    final int elapsedTime = DateTime.now().difference(_startTime).inSeconds;

    print('time elapsed: $elapsedTime seconds');

    return imageList;
  }

  @override
  void initState() {
    final tempInvoice = [];
    for (final Map k in _invoice) {
      print(k);
      tempInvoice.add(k);
    }

    for (int i = 1; i <= 100; i++) {
      _invoice.addAll(tempInvoice);
    }

    print(_invoice);
    print(_invoice.length);

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
          child: Center(
              child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          CircularProgressIndicator(),
          SizedBox(
            height: 20,
          ),
          Text('If animation lags then we know isolate failed.')
        ],
      ))),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await getTemporaryDirectory()
            ..delete(recursive: true);

          var result = await compute(generateInvoice, {
            'customRecordData': _invoice,
            'customClientName': 'Obama',
            'customClientDirection': 'USA',
            'customClientSeller': '1',
            'isPdf': false,
          });

          print(result);
        },
        child: Icon(Icons.picture_as_pdf),
      ),
    );
  }
}

class Product {
  const Product(
    this.qty,
    this.code,
    this.desc,
    this.price,
    this.total,
  );

  final String qty;
  final String code;
  final String desc;
  final String price;
  final String total;

  String getIndex(int index) {
    switch (index) {
      case 0:
        return qty;
      case 1:
        return truncateWithEllipsis(25, code.trim());
      case 2:
        return truncateWithEllipsis(50, desc.trim());
      case 3:
        return price;
      case 4:
        return total;
    }
    return '';
  }
}

String truncateWithEllipsis(int cutoff, String myString) {
  return (myString.length <= cutoff)
      ? myString
      : '${myString.substring(0, cutoff)}...';
}

虽然应用程序运行良好,但当我尝试运行计算代码时,它给了我这个错误:

[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.

我试过给 main() asyncWidgetsFlutterBinding.ensureInitialized(); 但它继续调用同样的错误。

顺便说一句,这是花费最多时间的部分。

if (!isPdf) {
  int pageIndex = 1;

  await for (var page in Printing.raster(pdf.save(), dpi: 300)) {
    final image = await page.toPng();

    final String pageQty =
        (pagesToPrint > 1) ? '$pageIndex-$pagesToPrint' : '1-1';

    file = File("${output.path}/$fileName--$pageQty.png");

    await file.writeAsBytes(image);

    imageList.add(file);

    pageIndex++;
  }
} else {
  file = File("${output.path}/$fileName.pdf");

  await file.writeAsBytes(pdf.save());

  imageList.add(file);
}

标签: flutterdart

解决方案


也许我还没有看到它,但你在哪里产生你的孤立?

在 Isolate 上生成你的内容可能有点棘手,但 Flutter 有一个很好的解决方法,它被称为计算函数。试试看!计算 Flutter API

或者试试这个页面,它帮助我更好地了解这个功能。使用 Flutter 计算

也许很高兴知道 Dart 是一种单线程语言,这意味着您可以在第二个线程的后台运行您的代码,并且在主线程中您可以显示一个加载指示器。解决方法是生成更多线程或对代码进行矢量化。飞镖矢量化

汤姆


推荐阅读