Flutter: избегайте зависания пользовательского интерфейса при выполнении массивной операции с базой данных - PullRequest
6 голосов
/ 29 мая 2020

ОБНОВЛЕНИЕ (15 июля 2020 г.)

Ответ mFeinstein на данный момент является единственным ответом, который дает мне первое приемлемое решение.

ВОПРОС

Я должен спросить вас, как лучше всего делать то, что я пытаюсь сделать:

  1. Вызов веб-службы в asyn c mode
  2. Ответ на синтаксический анализ
  3. Выполнение массовых операций с базой данных

Все это без остановки анимации прогресса, например, неопределенного индикатора выполнения.

Нет проблем по первому и второму пунктам. Проблема возникает на третьем этапе, когда выполняется массивная вставка в базу данных. И я еще не понимаю, как правильно реализовать это.

Какой-то псевдокод для уточнения

UI (отображается диалоговое окно и отображается индикатор выполнения .. .)

void callWS() async {
    MyProgressDialog _dialog = DialogHelper.showMyProgressDialog(_context, "Data", "Loading...");
    await getDataFromService();
    _dialog.close();
  }

ПОДКЛЮЧЕНИЕ (замораживание не происходит на индикаторе выполнения)

   static Future<void> getDataFromService() async {
    String uri = MY_URI;
    String wsMethod = MY_WS_METHOD;
    String wsContract = MY_WS_CONTRACT;

    SoapObject myRequest = SoapObject.fromSoapObject(namespace: my_namespace, name: wsMethod);

    MyConnectionResult response = await _openMyConnection(myRequest, uri, wsContract, wsMethod);
    if (response.result == MyResultEnum.OK) {
      await _parseResponse(response.data);
    }
  }

DATABASE (замораживание происходит в индикаторе выполнения )

  static Future<void> _parseResponse(xml.XmlElement elements) async {
    Database db = await MyDatabaseHelper.openConnection();
    db.transaction((tx) async {
      Batch batch = tx.batch();
      for (xml.XmlElement oi in elements.children) {
        int id = int.parse(oi.findElements("ID").first.text);
        String name = oi.findElements("NAME").first.text;

        DatabaseHelper.insertElement(
          tx,
          id: id,
          name: name,
        );
      }
      batch.commit(noResult: true);
    });
  }

НЕ РАБОТАЕТ АЛЬТЕРНАТИВА

Я тоже видел подход функции "вычислить", но похоже, что проблема в sqflite package, когда я называю операцию db. Например:

  static Future<void> performDelete() async {
    Database db = await openMyConnection();
    compute(_performDeleteCompute, db);
  }

  static void _performDeleteCompute(Database db) async {
    db.rawQuery("DELETE MYTABLE");
  }

Console error:'
-> Unhandled Exception: Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. 
-> If you are running an application and need to access the binary messenger before runApp() has been called (for example, during plugin initialization),
then you need to explicitly call the WidgetsFlutterBinding.ensureInitialized() first.
-> error defaultBinaryMessenger.<anonymous closure> (package:flutter/src/services/binary_messenger.dart:76:7)
    #1      defaultBinaryMessenger (package:flutter/src/services/binary_messenger.dart:89:4)
    #2      MethodChannel.binaryMessenger (package:flutter/src/services/platform_channel.dart:140:62)
    #3      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:146:35)
    #4      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
    #5      invokeMethod (package:sqflite/src/sqflite_impl.dart:17:13)
    #6      SqfliteDatabaseFactoryImpl.invokeMethod (package:sqflite/src/factory_impl.dart:31:7)
    #7      SqfliteDatabaseMixin.invokeMethod (package:sqflite_common/src/database_mixin.dart:287:15)
    #8      SqfliteDatabaseMixin.safeInvokeMethod.<anonymous closure> (package:sqflite_common/src/database_mixin.dart:208:43)
    #9      wrapDatabaseException (package:sqflite/src/exception_impl.dart:7:32)
    #10     SqfliteDatabaseFactoryImpl.wrapDatabaseException (package:sqflite/src/factory_impl.dart:27:7)
    #11     SqfliteDatabaseMixin.safeInvokeMethod (package:sqflite_common/src/database_mixin.dart:208:15)
    #12     SqfliteDatabaseMixin.txnRawQuery.<anonymous closure> (package:sqflite_common/src/database_mixin.dart:394:36)
    #13     SqfliteDatabaseMixin.txnSynchronized.<anonymous closure> (package:sqflite_common/src/database_mixin.dart:327:22)
    #14     BasicLock.synchronized (package:synchronized/src/basic_lock.dart:32:26)
    #15     SqfliteDatabaseMixin.txnSynchronized (package:sqflite_common/src/database_mixin.dart:323:33)
    #16     SqfliteDatabaseMixin.txnRawQuery (package:sqflite_common/src/database_mixin.dart:393:12)
    #17     SqfliteDatabaseExecutorMixin._rawQuery (package:sqflite_common/src/database_mixin.dart:126:15)
    #18     SqfliteDatabaseExecutorMixin.rawQuery (package:sqflite_common/src/database_mixin.dart:120:12)
    #19     DatabaseHelper._performDeleteCompute(package:flutter_infocad/Database/DatabaseHelper.dart:368:8)'

А также явный вызов WidgetsFlutterBinding.ensureInitialized() первым в runApp (), как указано в журнале ошибок, ничего не происходит.

Ответы [ 2 ]

3 голосов
/ 13 июля 2020

Проблема в том, что Flutter является однопоточным, поэтому, как только вы запустите тяжелый процесс, ваш одиночный поток заблокирует что-либо еще.

Решение состоит в том, чтобы правильно использовать этот однопоточный поток.

Dart будет иметь очередь событий с кучей Futures, ожидающих обработки. Как только движок Dart видит await, он позволяет другому Future захватить Single Thread и позволить ему работать. Таким образом, один Future будет работать одновременно внутри Isolate.

Так что, если мы поумнели, мы позволяем каждому играть в свое время, другими словами, мы разбиваем наши задачи на более мелкие задачи, поэтому движок Dart не будет голодать для других Futures, и все процессы, ожидающие запуска, могут иметь свое время.

Эквивалент для вашего кода будет примерно таким (при условии, что for это то, что требует много времени для выполнения из-за большой коллекции, а не отдельных шагов):

static Future<void> _parseResponse(xml.XmlElement elements) async {
  Database db = await MyDatabaseHelper.openConnection();
  db.transaction((tx) async {
    Batch batch = tx.batch();
    for (xml.XmlElement oi in elements.children) {      
      await Future(() {
        int id = int.parse(oi.findElements("ID").first.text);
        String name = oi.findElements("NAME").first.text;

         DatabaseHelper.insertElement(
          tx,
          id: id,
          name: name,
         );
      );
    }

    batch.commit(noResult: true);
  });
}

Это фрагментирует каждый шаг вашего for l oop в Future, поэтому на каждом этапе ваш пользовательский интерфейс будет иметь возможность выполнять все, что ему нужно, чтобы ваши анимации оставались плавными. Имейте в виду, что это будет иметь побочный эффект в виде замедления _parseResponse, поскольку размещение каждого шага for в очереди событий Future будет иметь дополнительную стоимость, поэтому вы можете дополнительно оптимизировать это для вашего конкретного случая использования. .

0 голосов
/ 13 июля 2020

Isolate & compute иногда не работает со сторонней библиотекой, вам нужно использовать flutter_isolate

FlutterIsolate позволяет создавать Isolate во флаттере, который может использовать плагины флаттера

...