Flutter: Невозможно вызвать метод канала платформы из другого изолята

Созданный на 5 янв. 2018  ·  133Комментарии  ·  Источник: flutter/flutter

Когда я пытаюсь вызвать метод канала платформы из созданного пользователем изолята, приложение сильно аварийно завершает работу (как на iOS, так и на Android). Я пытаюсь понять, ожидаемо это или нет.

Если это не так, вероятно, стоит упомянуть об этом где-нибудь.

В любом случае, я думаю, что это потенциально может быть сильным ограничением. Есть ли способ вызывать плагины платформы из вторичного изолята?

P5 annoyance crowd gold engine framework passed first triage plugin crash new feature

Самый полезный комментарий

У меня есть различные варианты использования, например:

  • Загрузите и проанализируйте огромный кусок данных (методы dart:convert не являются асинхронными), затем сохраните их в базе данных sqlite с помощью плагина sqflite (который использует привязки к платформе);
  • Предварительная выборка и анализ данных перед их подачей в пользовательский интерфейс;
  • Шифрование/дешифрование и чтение/запись данных из/в файл (криптоматериалы выполняются через канал методов для использования библиотек безопасности платформы);

В общем, может случиться так, что вы используете зависимости, которые объявляют некоторые общедоступные методы, о которых вы действительно не знаете, будет ли в конечном итоге использоваться специфичный для платформы код для выполнения своих задач (например, я думаю о flutterfire); их использование imho должно быть скорее деталью реализации, которая может меняться со временем, а не чем-то высеченным на камне.

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

Однако во время реализации я немного застрял: можете ли вы привести пример простого способа вызова статического метода на основном изоляте из вторичного?

Спасибо

Все 133 Комментарий

cc @mravn-google для сортировки

Код механизма разыменовывает null и аварийно завершает работу при попытке отправить сообщение платформы из вторичного изолята. Я еще не точно определил, где именно; Я получаю надгробную плиту, но мне нужно научиться интерпретировать такую ​​вещь.

В качестве (неуклюжего) обходного пути вторичный изолят может попросить основной отправить сообщение.

@sroddy Могу я спросить, чего вы пытаетесь достичь с помощью вторичного изолята?

У меня есть различные варианты использования, например:

  • Загрузите и проанализируйте огромный кусок данных (методы dart:convert не являются асинхронными), затем сохраните их в базе данных sqlite с помощью плагина sqflite (который использует привязки к платформе);
  • Предварительная выборка и анализ данных перед их подачей в пользовательский интерфейс;
  • Шифрование/дешифрование и чтение/запись данных из/в файл (криптоматериалы выполняются через канал методов для использования библиотек безопасности платформы);

В общем, может случиться так, что вы используете зависимости, которые объявляют некоторые общедоступные методы, о которых вы действительно не знаете, будет ли в конечном итоге использоваться специфичный для платформы код для выполнения своих задач (например, я думаю о flutterfire); их использование imho должно быть скорее деталью реализации, которая может меняться со временем, а не чем-то высеченным на камне.

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

Однако во время реализации я немного застрял: можете ли вы привести пример простого способа вызова статического метода на основном изоляте из вторичного?

Спасибо

@sroddy Спасибо за предоставление справочной информации.

О вызове статических методов на основном изоляте: это напрямую не поддерживается и должно быть реализовано через порты. Этот пакет может предоставлять эту функциональность, например, здесь . Хотя сам не пробовал.

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

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

Решение набросано ниже.

Предположим, вы настроили порты M1 и R1 на своем основном изоляте, а порты M2, R2 — на вторичном. M для сообщения, R для ответа.

  • В вашем вторичном изоляте используйте BinaryMessages.setMockMessageHandler для каждого канала, чтобы пересылать сообщения для платформы на M1 (прозрачно для плагина, который использует BinaryMessage.send с этим каналом). Сохраните ответные обратные вызовы и настройте R2 с обработчиком, который вызывает правильный обработчик при получении ответа на сообщение. Настройте M2 с обработчиком, который перенаправляет на BinaryMessages.handlePlatformMessage . Реализуйте ответный обратный вызов, чтобы пересылать ответы на R1.
  • Симметрично в вашем основном изоляте. Настройте M1 с обработчиком, который перенаправляет сообщения для платформы на BinaryMessages.send . Установите обратный вызов ответа, который перенаправляет ответы с платформы на R2. Также вызовите BinaryMessages.setMessageHandler для каждого из каналов, чтобы настроить обработчик входящих сообщений с платформы, которые перенаправляются на M2. Сохраните ответные обратные вызовы и настройте R1 с обработчиком, который вызывает правильный обработчик при получении ответа от вторичного изолята.

@Hixie , я предлагаю перейти к следующему этапу. Обходной путь, кажется, существует. Хорошее решение потребует некоторой работы по проектированию и итерации API.

У меня такая же проблема, есть новости по этой проблеме?

@mravn-google У вас есть новости по этому поводу? Я столкнулся с той же проблемой (мне нужно сгенерировать, например, пару ключей RSA 2048 на стороне платформы, что занимает некоторое время на старых устройствах или шифровании/дешифровании данных). Я бы предпочел избегать создания потоков или сервисов, так как это удвоило бы мою работу на стороне платформы, поскольку она должна быть реализована как для платформ Android, так и для платформ iOS. Есть ли у нас какой-нибудь простой способ асинхронного запуска этих специфичных для платформы операций из Dart? Кажется, ваш обходной путь в порядке, однако кажется, что у меня есть некоторые проблемы с реализацией этого, поскольку я совершенно новичок во Flutter и Dart (я не уверен, как настроить обработчик, который перенаправляет сообщения для изоляции и т. д.), извините об этом.

У меня такая же проблема.

Мне нужно сгенерировать PDF-файл на стороне Android/iOS, что занимает некоторое время, и метод вычисления (...) приводит к сбою приложения, если я запускаю вызов оттуда.

Я получил эту проблему, и теперь я застрял.

Я хочу прочитать accessToken из общих настроек в фоновой изоляции, даже если приложение не находится на переднем плане (основная изоляция может быть не запущена, я не очень уверен).

Итак, может ли кто-нибудь сказать мне решение для чтения сохраненных данных из фоновой изоляции.
Я сейчас действительно в беде.

/cc @bkonyi Кажется, это связано с тем, над чем вы работаете.

У меня такая же проблема. наша продуктовая база связана с процессором. нам нужно использовать метод платформы в изоляции. Не могли бы вы рассказать нам другой способ решить эту проблему?

@Thomson-Tsui У меня была похожая проблема, и, основываясь на анализе работы @bkonyi и его образца FlutterGeofencing, мне удалось заставить что- то работать в изоляции, которую я использую с flutter_blue и другими плагинами. Это довольно непроверено, но, пожалуйста, попробуйте.

У меня такая же проблема, я думаю, это важно

Кто-нибудь решил эту проблему?

Мне нужно воспроизводить звук с разными интервалами в фоновом режиме. Самый простой способ сделать это — иметь отдельный изолированный модуль, управляющий звуком, но без этой функции потребуется очень странная работа с портами.

У меня есть предположение, что при запуске изолята от dart родные библиотеки не линкуются.

Например, при запуске кода флаттера из java вызывается этот метод:

   private native void nativeRunBundleAndSnapshotFromLibrary (
       long nativePlatformViewId,
       <strong i="7">@NonNull</strong> String [] prioritizedBundlePaths,
       <strong i="8">@Nullable</strong> String entrypointFunctionName,
       <strong i="9">@Nullable</strong> String pathToEntrypointFunction,
       <strong i="10">@NonNull</strong> AssetManager manager
   );

В котором происходит все волшебство)

Это подтверждается тем, что если создать несколько FlutterNativeView, и при каждом запуске:

  public void runFromBundle (FlutterRunArguments args)

тогда мы получаем несколько "изолятов дротика"

Мы также сталкиваемся с этой проблемой. Нам нужно собирать данные из фоновых изолятов (например, местоположение, шум и т. д.), которые зависят от каналов платформы. @ mravn-google -- есть новости об обновлении API, которое могло бы решить эту проблему?

Это действительно серьезная проблема в инструментальных или мультимедийных приложениях.
Поток платформы — не простой путь.

Я также столкнулся с подобной проблемой и застрял прямо сейчас.

+1 (мой проект тоже остро нуждается в изолированных каналах платформы)

Мне нужно отображать изображения в фоновом режиме. Это должно быть сделано в изоляции, потому что каждое изображение занимает несколько секунд и блокирует пользовательский интерфейс, если это делается в основном потоке. Я застрял на первом собственном вызове dart:ui.PictureRecorder и предполагаю, что каждый графический вызов (который использует собственные функции) также не будет работать. Поскольку я использую много функций в холсте, было бы сложно работать с портами обратного вызова.

Решение высоко ценится.

+1 Я понял эту проблему

Похоже, @mravn-google перестал работать на Google в Орхусе. Интересно, есть ли кто-нибудь еще из команды Google Flutter, кто занимается этим. ИМО, это главный показатель серьезного использования Flutter для создания устойчивых и надежных приложений Flutter....

Может быть , @sethladd может дать статус?

+1

какие-либо обновления по этому поводу?

+1

День, когда вы столкнулись с ошибкой, которую вы знаете, как решить самостоятельно, но только ждите, это последняя надежда, пока я не стану флаттер-инженером.

Нам нужно более подробное объяснение обходного пути для новых пользователей флаттера/дротика. Я начал использовать изоляты только два дня назад, а флаттер/дротик на прошлой неделе, и похоже, что мне придется использовать основной поток для некоторых серьезно трудоемких задач, что делает обучение флаттеру плохим решением. Без полезной многопоточности мне лучше изучить Kotlin и дважды создавать свои приложения. Я надеюсь, что вы все вместе сможете собрать что-то понятное для начинающих, мне бы очень хотелось иметь возможность оправдать изучение флаттера перед моими работодателями, чтобы я мог использовать его на работе.
Если не здесь, возможно, в StackOverflow, я разместил здесь вопрос: https://stackoverflow.com/q/57466952/6047611

+1 надеюсь скоро исправят

+1 Моему проекту нужны каналы платформы в изоляции.

Я столкнулся с той же проблемой и сделал пакет ( Isolate Handler ), используя обходной путь, аналогичный тому, который опубликован @mravn-google. Тем не менее, обработчик изолятов позволяет вам использовать каналы как из основного изолята, так и из других изолятов. Он поддерживает прямые вызовы MethodChannel из изолятов, но в настоящее время потоки EventChannel не поддерживаются. Я рассмотрю возможность добавления поддержки для них, если это окажется чем-то, что нужно людям.

Одним из недостатков обходного пути является необходимость предоставления обработчику изоляции имен каналов. В моем проекте это не проблема, так как я сам пишу свой собственный код, но в противном случае вам придется просмотреть исходный код любого плагина, который вы используете, чтобы найти их.

Другой вариант, который уже был опубликован выше, — это FlutterIsolate , который использует другой подход и также заслуживает изучения.

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

Редактировать:

В конце концов я решил свою проблему более аккуратно, запустив изоляцию с нативной стороны, вместо того, чтобы использовать подход, аналогичный собственному плагину Flutter для Android Alarm Manager .

Единственное, что мне нужно было сделать, это преобразовать мой нативный код в плагины, чтобы их можно было зарегистрировать в нативном приложении, что было относительно безболезненной миграцией. Если вы используете плагины от pub.dev, это еще проще, так как они должны работать без проблем.

+1 есть какие-нибудь обновления здесь?

+1

Интересная вещь, которую я замечаю здесь:
Некоторые плагины, кажется, написаны с предположением, что вызов канала метода передает сообщения из Dart в фоновый поток; соответственно, есть несколько сильно сбитых с толку людей, которые последовали этому примеру геозоны bg и теперь задаются вопросом, как вернуться в основной поток...
Этот код плагина не вызовет "рывок", если они выполняют короткую задачу, но по умолчанию они работают в потоке платформы , который раньше назывался "поток пользовательского интерфейса" на Android; это может привести к тому, что жесты будут сброшены во время высокой рабочей нагрузки, и я понимаю, что это также заблокирует все другие каналы сообщений.
Тогда вы зависите от разработки автора плагина; только если они переместили свою тяжелую работу в другой поток в нативном коде, вы будете запускать код, ожидающий на каналах методов; эти каналы сообщений должны работать в потоке платформы.
Хотя я понимаю обоснование, которое приводит здесь хороший чинмайгард, соглашаясь с тем, что:

  • большинство вызовов API платформы выполняются быстро и часто должны выполняться в основном потоке;
  • имеет смысл учитывать потоки в нативном коде
  • поиск пути назад к «основному потоку» в переопределении метода фреймворка, очевидно, сбивает с толку людей, привыкших к Android Java.

    • Я могу только представить, как это умножится, когда они сделают свои первые вызовы API только для потоков платформы.

Я чувствую, что получившаяся архитектура открыла значительный анти-шаблон по умолчанию для авторов плагинов; на первый взгляд, я думаю, что даже плагин firebase ml использует поток платформы для своей обработки.
Так что я бы хотел, чтобы было какое-то сильное предупреждение, которое было бы дано людям, занимающимся тяжелой атлетикой; возможно, что-то в примере приложения по умолчанию, которое генерирует ошибки на экране, если вызовы канала сообщений занимают больше времени, чем ~ 20 мс, возможно, что-то более очевидное для всех; честно говоря, все, что убеждает тех людей, которые звонят в БД и т. д. пойти получить нить уже.

Ой, бесплатного обеда не бывает, это точно...

В итоге я реализовал большую рабочую нагрузку с потоками нативной платформы, используя Java и Swift. Обходной путь не найден.

@AndruByrne Это действительно очень интересная информация, и я не знал об этом, насколько это очевидно, как сейчас кажется в ретроспективе. У меня уже есть одна проблема, связанная с моим плагином, когда пользователь пытался использовать его как способ выполнить тяжелую задачу в фоновом режиме, и это блокировало пользовательский интерфейс.

Я согласен с тем, что должно быть предупреждение, и я добавлю его в свой плагин.

+1 нужно это

К сожалению, Flutter не готов к прайм-тайму, потому что ему не хватает чего-то очень простого, но очень важного. Не все приложения Flutter могут обойтись простым async/await. Для тяжелой работы изоляты необходимы, а плагины, которые не могут разрешить вызовы методов канала платформы из другого изолята, делают их бессмысленными и избыточными. В Xamarin это было встроено в инфраструктуру с первого дня - безусловно, сейчас за это нужно проголосовать за решение.

+1 нужно это

+1

Мне также нужно вызвать метод канала платформы из другого изолята!

+1

+1

+1

+1 Это обязательно

+1
У меня есть собственный SDK, который я должен вызвать. Было бы неплохо иметь что-то вроде BackgroundFlutterMethodChannel .

+1

+1

Есть ли у кого-нибудь информация о том, что на самом деле описывает _краширует плохо_? Я работаю над проверкой концепции, которая реализует собственную библиотеку, и я сталкиваюсь с тайм-аутами сторожевого таймера (0x8badf00d) и другими странными ошибками при отправке сообщений туда и обратно через канал метода.

Я подозреваю, что это вызвано тем, что некоторые методы [и результаты] вызываются из других потоков, но мне не удалось добиться какого-либо прогресса в выявлении точной проблемы.

+1

+1

+1

+1

+1 нужно это

+1 это тоже нужно!

Я думаю, что этот вопрос должен быть приоритетным как минимум P2.
Использование каналов платформы, о котором я могу думать,

  • Для вызова специфичных для платформы API
  • Запускать тяжелые задачи для конкретной платформы в фоновом режиме и сообщать о завершении

И это главный блокатор.

Плагин flutter_downloader обрабатывает фоновый изолирующий код. Думаю, стоит посмотреть.

я надеялся, что это будет решено в следующей версии, но ошибка все еще остается в версии 1.12!

наш код на стороне клиента в значительной степени находится на другой стороне канала и написан на kotlin, и приложение почти зависает для каждого отдельного http-соединения.

Гыоооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо?

Этот плагин может быть полезен

+1 нам это нужно... давай

+1

Самая большая проблема для меня заключается в том, что связь между Dart и нативными платформами происходит в основном потоке — отправка значительных фрагментов данных невозможна без отставания пользовательского интерфейса или написания громоздкого кода для данных страницы.

+1 нужно это

написание громоздкого кода для данных страницы.

@lukaszciastko , что вы подразумеваете под написанием кода для данных страницы. Есть ли способ решить отстающий пользовательский интерфейс при отправке значительных кусков данных??

@YaredTaddese

В зависимости от сложности того, что делает ваш ISOLATE, и взаимодействия между вашим пользовательским интерфейсом и ISOLATE — можно обойти эту проблему в коде вашего пользовательского интерфейса, заранее структурировав, какие данные требуются ISOLATE для выполнения своей работы, до вызова ISOLATE. В моем текущем приложении Flutter есть богатая генерация PDF-файлов — пользователь может выбрать несколько PDF-файлов для создания.

Каждый может генерировать от 1 до 10 000 страниц. Мой пользовательский интерфейс реагирует, когда каждое задание завершается, создается TAB, показывающий отчет - пользователь получает уведомление, когда задание завершается с помощью TOAST, и пользовательский интерфейс не останавливается - пользователь не знает, что есть службы в фоновом режиме заняты создание богатых PDF-файлов.

Обратите внимание, мое поколение PDF является ИЗОЛЯЦИЕЙ. В ISOLATE - мне также нужно загрузить изображения из CLOUD FIRESTORE и вставить в PDF - все это происходит без проблем.

@MsXam, но я не могу вызвать функцию для конкретной платформы из другого изолята ... что заставляет меня вызывать функцию для конкретной платформы в основном изоляте ... что приводит к отставанию пользовательского интерфейса

У меня такая же проблема, я думаю, что это очень важно

@YaredTaddese

В зависимости от сложности того, что делает ваш ISOLATE, и взаимодействия между вашим пользовательским интерфейсом и ISOLATE — можно обойти эту проблему в коде вашего пользовательского интерфейса, заранее структурировав, какие данные требуются ISOLATE для выполнения своей работы, до вызова ISOLATE. В моем текущем приложении Flutter есть богатая генерация PDF-файлов — пользователь может выбрать несколько PDF-файлов для создания.

Каждый может генерировать от 1 до 10 000 страниц. Мой пользовательский интерфейс реагирует, когда каждое задание завершается, создается TAB, показывающий отчет - пользователь получает уведомление, когда задание завершается с помощью TOAST, и пользовательский интерфейс не останавливается - пользователь не знает, что есть службы в фоновом режиме заняты создание богатых PDF-файлов.

Обратите внимание, мое поколение PDF является ИЗОЛЯЦИЕЙ. В ISOLATE - мне также нужно загрузить изображения из CLOUD FIRESTORE и вставить в PDF - все это происходит без проблем.

Вы вызываете код для конкретной платформы?

У кого-нибудь есть обходной путь для вызова каналов платформы из изолята? Это противоречит идее сделать это из потока пользовательского интерфейса.

Я застрял на том же самом.

Я вызываю метод-канал с кодом kotlin и API getStream.io, но это чертовски медленно, и мой пользовательский интерфейс Flutter зависает, я хотел добавить к нему вычисления, но получил те же ошибки.

Как я могу это исправить?

Я тоже только что наткнулся на это. Я создаю прототип нового приложения, и на самом деле эта проблема может стать для меня препятствием и заставить меня искать другие решения, отличные от Flutter, что меня огорчит.

Как показывает количество ответов на эту проблему, это ОСНОВНАЯ проблема, которая в значительной степени противоречит основному варианту использования изолятов в контексте приложения Flutter.

Я тоже только что наткнулся на это. Я создаю прототип нового приложения, и на самом деле эта проблема может стать для меня препятствием и заставить меня искать другие решения, отличные от Flutter, что меня огорчит.

Как показывает количество ответов на эту проблему, это ОСНОВНАЯ проблема, которая в значительной степени противоречит основному варианту использования изолятов в контексте приложения Flutter.

да, это большая проблема, нужно срочно исправлять

Я изучаю флаттер уже 10 дней, и пока он мне нравится, но поэкспериментировав некоторое время, начинаешь натыкаться на проблемы, и видишь минусы, и ЭТОТ выпуск, открытый с 5 января 2018 года, уф...

Пожалуйста, исправьте эту ошибку.

Надеюсь на поддержку в ближайшее время.

@jpsarda , @klaszlo8207 Не задерживайте дыхание. И да, *было решение проблемы до сегодняшнего дня: https://pub.dev/packages/isolate_handler. Программисту пришлось отключить его сегодня, потому что очень недавнее изменение во Flutter (всего несколько дней назад) сделало невозможным дальнейшее использование этого маршрута.

Однако вы должны знать, что на самом деле это не означало параллелизма. Возможно, так казалось, и внешний вид важен в мобильных приложениях, но в любом случае код платформы выполняется в основном потоке, поэтому делегирование длинной задачи обратно из изолята в основной поток на самом деле ничего не дало.

Было бы неплохо иметь дротик, обрабатывающий многопоточность по умолчанию каждый раз, когда вы используете асинхронную функцию.

@spiderion Async никогда не предназначался для многопоточности, как и на многих других платформах. Не поймите меня неправильно, я не скажу, что ограничение канала платформы не является большой проблемой, потому что это так (мое приложение работало с плагином, о котором я упоминал, до вчерашнего дня, и теперь мне нужно искать обходные пути), но async/ С самого первого дня ожидание никогда не касалось многопоточности.

@zoechi Мы хотели бы знать, есть ли какие-либо обновления по этой проблеме или есть ли какие-либо планы по ее устранению?
Спасибо.

@spiderion Они не могут это исправить, это следует из того, как работают изоляторы. За ними не стоит движок пользовательского интерфейса, следовательно, нет связи с каналом платформы. Тем не менее, есть два плагина, которые помогут вам:

  • https://pub.dev/packages/flutter_isolate обеспечивает замену изолята, который может взаимодействовать с плагинами, потому что он создает свою собственную поддержку пользовательского интерфейса (ничего, что вы видите или с чем вам приходится иметь дело, просто технически),

  • https://pub.dev/packages/isolate_handler , который мы только что модифицировали, чтобы полагаться на указанный выше пакет, потому что предыдущий способ, которым он использовался, стал невозможным из-за недавнего изменения Flutter. Преимущество использования этого пакета по сравнению с самим flutter_isolate состоит в том, что он добавляет возможности обработки, вы можете запускать несколько изолятов, отслеживать их и вам не нужно настраивать собственную связь между изолятом и основным пакетом. thread (что-то, что вам нужно сделать вручную как с исходным запасом Isolate , так и с FlutterIsolate ), потому что он абстрагирован и легко доступен.

Я использую второй с полным успехом. На самом деле, я использую его уже довольно давно, и когда пришло критическое изменение Flutter, я помог его программисту перейти на новое решение только потому, что мое приложение тоже сломалось. :-)

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

Привет @deakjahn , спасибо за быстрый ответ.

Предложенные вами решения выглядят очень полезными. Однако я не знаю, может ли это решить мою проблему. В настоящее время у меня есть ситуация в приложении, когда пользователь создает историю, скажем, похожую на Instagram. В этом случае приложению необходимо загрузить в хранилище firebase список видео и файлов, загрузка которых может занять очень много времени (около 15 минут, если соединение медленное), а затем после загрузки файлов приложение должно создать документ в облаке Firebase, Firestore. Проблема, с которой я сталкиваюсь, заключается в том, что если пользователь убивает приложение во время загрузки файлов, то полная задача не выполняется полностью.

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

Я пытаюсь запустить пример getBatteryLevel:
https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab
и он вылетает каждый раз, когда я пытаюсь запустить его на устройстве Android. Может кто-нибудь помочь? Я даже не уверен, как проверить ошибку, так как журналов нет, и она просто продолжает работать в бесконечном цикле, и я использую тот же самый код.

Обозначение этой проблемы для P5 ??? 🥵

Пожалуйста, ознакомьтесь с предыдущими комментариями. Кажется очень маловероятным , что это будет изменено в фреймворке, потому что для этого требуются механизмы, которые не нужны большинству изолятов, поэтому нет смысла добавлять эти накладные расходы. Для случаев, когда вам это нужно, есть пакеты для решения этой проблемы.

Пользователей не волнует механизм; насколько это сложно или нет. Им просто нужен результат. Это может быть невозможно сделать. Но если пользователям это нужно, Flutter должен это предоставить...

Я понимаю, что реализация имеет ограничения и может потребоваться много времени, чтобы добавить эту функцию в существующий изолят или создать новый API с этой функцией. Я просто думаю, что это должно иметь более высокий приоритет, чем P5.

Кроме того, я прочитал все ваши комментарии, и мне кажется, что вы просто продвигаете созданный вами пакет.

Нет, это не я создал его, но я принял его право собственности несколько недель назад, да. Я не думаю, что это держалось в секрете в этой ветке. В дополнение к этому я упомянул два пакета (в одном посте, на самом деле, только один раз), которые оба способны предоставить то, что вы ищете, и я не имею никакого отношения ко второму, кроме как полагаться на его отличную работу, проделанную до сих пор.

Если вы читали это, единственная причина, по которой я использовал их в первую очередь, заключалась в том, что мне нужна была та же самая функциональность, о которой вы просили. И мои приложения полагаются на него уже больше года, без проблем. Кроме того, знакомство с внутренней работой изолятов лучше, чем было бы случайным разработчиком, просто использующим их (плюс то, что я начал работать над их аналогом во Flutter Web), заставляет меня думать, что то, что я написал, было правдой: эта функциональность не будет включен в структуру, потому что есть довольно много аргументов против его включения. Не потому, что это займет кучу времени, вовсе нет. Это займет относительно немного времени. Просто потому, что это добавит сложности серверной части всем изолятам, чем лишь немногие из них действительно понадобятся и будут использоваться.

@deakjahn спасибо за эти советы, поможет ли использование isolate_handler вызвать метод и отправить данные на сторону дротика всякий раз, когда я получаю уведомление на нативной стороне? (Я использую собственные SDK twilio и получаю push-уведомления на собственной стороне)

Если это означает, что у вас есть плагин patform, который вы используете для связи через канал платформы, тогда да, любой из двух пакетов поможет. Если он работает из обычного кода, то эти пакеты заставят его работать и из изолированного кода.

@deakjahn Спасибо за быстрый ответ! Моя проблема в том, что я не мог сделать это с обычным каналом платформы, я не уверен, но я думаю, что twilio sdk обрабатывает push-уведомления в отдельном изоляте, а не в основном потоке, и я думаю, что это моя проблема. Я хотел знать, будет ли это работать, если я буду вызывать метод каждый раз, когда получаю уведомление, используя один из двух пакетов? Большое спасибо. (Я рассматриваю возможность перехода на собственные приложения, если это невозможно)

Я даже не знаю, что такое Twilio (хорошо, я погуглил :-)), поэтому я действительно не могу быть уверен. Но в любом случае, если вы хотите взаимодействовать с любым нативным кодом, вам все равно придется использовать канал платформы. Либо вы используете уже существующий плагин, который кто-то сделал для этого Twilio, либо вы его сами создаете (в этом случае вы либо создаете практически такой же плагин, просто не публикуете его и ссылаетесь на него локально), либо просто копируете соответствующий плагин код в свое собственное приложение (это не будет внешней зависимостью, но в остальном у него будет практически такой же код).

И, если у вас есть канал платформы, применяется предыдущий ответ: если вы уже можете использовать канал платформы и плагин из основного потока обычного приложения Flutter, то вы можете использовать то же самое из изоляции, используя любой из два пакета.

Таким образом, эта проблема существует с 5 января 2018 года, и это все еще невозможно с дополнительными усилиями. Предоставленные решения хороши, но на рабочем столе пока ничего не работает. Так что для тех, кто хочет попробовать, насколько хорошо флаттер работает на десктопе, и посмотреть, возможно ли это для будущего продукта, полностью застрянет здесь.
Почти любое приложение нуждается в изоляции, потому что всегда есть какие-то огромные вычисления, как возможно, что ничего не происходит с такими важными недостатками дизайна API?

Я добавил выход для этого в моем окне системного оповещения плагина

Решение упоминается здесь Изолировать связь .
Хотя приведенный пример относится к плагину окна системного оповещения, его можно легко воспроизвести для других плагинов. И для его работы не требуются никакие другие причудливые плагины!

Это все еще может быть довольно проблематично. Это означает, что нет возможности вызвать плагин напрямую из изолята, работающего в фоновом/переднем плане, потому что вам нужно будет отправить сообщение в область приложения, чтобы что-то сделать с плагином. Поэтому, если ваше приложение не работает, вы ничего не сможете сделать с плагином в фоновом режиме.

Так, например, если вы хотите сделать что-либо с общими настройками в backgroundMessageHandler плагина FCM, когда приложение будет закрыто, оно выдаст исключение MissingPluginException.

Или если вы не хотите открывать приложение с окном системного предупреждения. Окно оповещения запущено на другом изоляте. Таким образом, вам придется запускать код в рамках вашего приложения. Это проблематично, потому что приложение в настоящее время закрыто.

И, вероятно, есть много других сенариев, где это является огромной проблемой.

@ michael-ottink описанные вами сценарии - это именно те, в которых не должно быть проблем. Плагины, которые выполняют код в фоновом режиме, обычно создают экземпляр совершенно нового FlutterEngine со своим собственным реестром плагинов, и изолят, который работает в нем, сможет напрямую связываться по каналам платформы с этими плагинами.

Кстати, это означает, что если вам нужен изолят для использования плагинов, самый простой способ — просто обернуть этот изолят во FlutterEngine, и он волшебным образом унаследует эти возможности. Это то, что пакет flutter_isolate делает для вас на Android и iOS (хотя я согласен, что было бы лучше иметь правильное исправление, а не обходной путь. Доступные сегодня обходные пути имеют узкие места, накладные расходы или иным образом могут не поддерживаться или не поддерживаться во всех все платформы.)

@ryanheise , значит, flutter_isolate делает это за меня? Как я не могу сказать из документов. Что мне нужно сделать, чтобы он работал для моего FCM onBackgroundMessage? Итак, мне нужно создать новый изолят в моем onBackgroundMessageHandler? Но я не могу использовать там плагины, так как же мне тогда использовать flutter_isolate?

Ваша проблема в том, что firebase_messaging давно не обновлялся и сильно отстает от последних API-интерфейсов фонового выполнения. Если бы они обновили свой плагин, у вас больше не было бы этой проблемы. Просто чтобы еще раз уточнить, тип сценария, который вы описываете, - это именно тот сценарий, в котором не должно быть проблем, потому что фоновые изоляты уже должны иметь доступ к плагинам, если они реализованы правильно. плагин firebase_messaging не реализован в соответствии с последними API, поэтому он у вас не работает. Вы можете отправить отчет об ошибке с этим проектом.

Мне очень нужна помощь в этом. Есть ли способ, которым я могу это сделать. У меня завтра первый серьезный дилинг, и я просто не могу понять.

Сделайте все, чтобы плагины загружались. Я не знаю, следовали ли вы инструкциям в README о том, как включить фоновые сообщения, но вам нужно создать собственный класс приложения, который подключается к инициализации фонового плагина FlutterNativeView. Если вы этого не сделали, ни один из плагинов не будет доступен для использования. Если это все еще не работает, вы можете попробовать понизить версию своего проекта до старой архитектуры плагинов в стиле v1 (и рискнуть сломать другие плагины, которым требуется v2).

Я настраиваю свой Application.kt следующим образом

import `in`.jvapps.system_alert_window.SystemAlertWindowPlugin
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService

import android.os.Build
import android.app.NotificationManager
import android.app.NotificationChannel

public class Application: FlutterApplication(), PluginRegistrantCallback {

   override fun onCreate() {
     super.onCreate()
     FlutterFirebaseMessagingService.setPluginRegistrant(this)
     createNotificationChannels()
     SystemAlertWindowPlugin.setPluginRegistrant(this)
   }

   override fun registerWith(registry: PluginRegistry) {
     FirebaseCloudMessagingPluginRegistrant.registerWith(registry)
     SystemAlertWindowPlugin.registerWith(registry.registrarFor("in.jvapps.system_alert_window"))
   }

   fun createNotificationChannels() {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = "groupChannel"
        val descriptionText = "This is the group channel"
        val importance = NotificationManager.IMPORTANCE_HIGH
        val mChannel = NotificationChannel("59054", name, importance)
        mChannel.description = descriptionText
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(mChannel)
    }
  }
}

Лучше всего опубликовать это на странице проблем firebase_messaging, потому что это не связано с этой проблемой.

Я могу сделать PR, в котором будет новый метод spawnIsolate для SchedulerBinding.

Тогда в этом изоляте можно будет вызывать методы платформы.

Это поможет вам, только если вам нужно вызвать метод платформы и получить ответ.

import 'dart:async';
import 'dart:isolate';

import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart';

Future<void> _test(SendPort sendPort) async {
  final dir = await getTemporaryDirectory();
  sendPort.send(dir);
}

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

  final Completer<Object> completer = Completer<Object>();
  final RawReceivePort receivePort = RawReceivePort(completer.complete);

  final Isolate isolate = await ServicesBinding.instance.spawnIsolate(
    _test,
    receivePort.sendPort,
  );

  print(await completer.future);

  receivePort.close();
  isolate.kill();
}

Бревно

Performing hot restart...
Syncing files to device Pixel 4...
Restarted application in 722ms.
I/flutter (11705): Directory: '/data/user/0/com.example.bug/cache'

Я могу сделать PR, в котором будет новый метод spawnIsolate для SchedulerBinding.

Тогда в этом изоляте можно будет вызывать методы платформы.

Это поможет вам, только если вам нужно вызвать метод платформы и получить ответ.

SchedulerBinding.instance.spawnIsolate

Есть ли шанс проверить это? Если это соответствует моим потребностям и т.д..

@Найлик
Заменить этот файл (актуально для версии 1.17.5)


переплет.дротик

```dart// Copyright 2014 Авторы Flutter. Все права защищены.
// Использование этого исходного кода регулируется лицензией в стиле BSD, которую можно
// находится в файле LICENSE.

импортировать « дротик: асинхронный »;
импортировать ' дротик: изолировать ';
импортировать ' dart:typed_data ';
импортировать ' dart:ui ' как пользовательский интерфейс;

import ' package:flutter/foundation.dart ';

импортировать «asset_bundle.dart»;
импортировать 'binary_messenger.dart';
импортировать 'system_channels.dart';

/// Прослушивает сообщения платформы и направляет их в [defaultBinaryMessenger].
///
/// [ServicesBinding] также регистрирует [LicenseEntryCollector], который предоставляет
/// лицензии, найденные в файле LICENSE , хранящемся в корне актива
/// комплект и реализует сервисное расширение ext.flutter.evict (см.
/// [выселить]).
миксин ServicesBinding на BindingBase {
@переопределить
недействительным initInstances () {
супер.initInstances();
_экземпляр = это;
_defaultBinaryMessenger = createBinaryMessenger();
window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
initЛицензии();
SystemChannels.system.setMessageHandler(handleSystemMessage);
}

/// Текущий [ServicesBinding], если он был создан.
static ServicesBinding get instance => _instance;
статический ServicesBinding _instance;

/// Экземпляр по умолчанию [BinaryMessenger].
///
/// Это используется для отправки сообщений из приложения на платформу, и
/// отслеживает, какие обработчики были зарегистрированы на каждом канале, поэтому
/// он может отправлять входящие сообщения зарегистрированному обработчику.
BinaryMessenger получить defaultBinaryMessenger => _defaultBinaryMessenger;
BinaryMessenger _defaultBinaryMessenger;

/// Создает экземпляр [BinaryMessenger] по умолчанию, который можно использовать для отправки
/// сообщения платформы.
@защищено
BinaryMessenger createBinaryMessenger() {
вернуть константу _DefaultBinaryMessenger._();
}

/// Обработчик, вызываемый для сообщений, полученных на [SystemChannels.system]
/// канал сообщений.
///
/// Другие привязки могут переопределить это, чтобы отвечать на входящие системные сообщения.
@защищено
@mustCallSuper
БудущееhandleSystemMessage (Объект systemMessage) асинхронный { }

/// Добавляет соответствующие лицензии в [LicenseRegistry].
///
/// По умолчанию реализация [ServicesBinding] [initLicenses] добавляет
/// все лицензии, собранные инструментом flutter во время компиляции.
@защищено
@mustCallSuper
недействительными initLicenses () {
LicenseRegistry.addLicense(_addLicenses);
}

Ручей_addLicenses() асинхронно* {
// Здесь мы используем таймеры (а не scheduleTask из привязки планировщика)
// поскольку уровень служб не может использовать привязку планировщика (планировщик
// привязка использует уровень сервисов для управления событиями своего жизненного цикла). Таймеры
// это то, что ScheduleTask в любом случае использует под капотом. Единственная разница в том,
// что они просто будут выполняться следующими, вместо того, чтобы иметь приоритет относительно
// другие задачи, которые могут выполняться. Используя _something_ здесь, чтобы сломать
// важно разделить это на две части, потому что для копирования изолятов требуется некоторое время
// данные на данный момент, и если мы получаем данные в том же цикле событий
// итерация по мере того, как мы отправляем данные на следующий изолят, мы определенно
// будем пропускать кадры. Другим решением было бы
// происходит в одном изоляте, и мы можем туда попасть в конце концов, но сначала мы
// посмотрим, можно ли сделать изолированную связь дешевле.
// См.: https://github.com/dart-lang/sdk/issues/31959
// https://github.com/dart-lang/sdk/issues/31960
// TODO(ianh): Удалите эту сложность, как только эти ошибки будут исправлены.
окончательный ЗавершительrawLicenses = Завершитель();
Timer.run(() асинхронный {
rawLicenses.complete(rootBundle.loadString('ЛИЦЕНЗИЯ', кеш: ложь));
});
ждите rawLicenses.future;
окончательный Завершитель> parsedLicenses = Завершитель>();
Timer.run(() асинхронный {
parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
});
ждать parsedLicenses.future;
выход* поток.fromIterable(ожидание разбораLicenses.future);
}

// Это выполняется в другом изоляте, созданном _addLicenses выше.
статический список_parseLicenses (строка rawLicenses) {
final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
окончательный списокрезультат =[];
окончательный списоклицензии = rawLicenses.split(_licenseSeparator);
for (окончательная строка лицензии в лицензиях) {
окончательный int split = license.indexOf('\n\n');
если (разделить >= 0) {
результат. добавить (LicenseEntryWithLineBreaks (
license.substring(0, разделить).split('\n'),
лицензия.подстрока (разделить + 2),
));
} еще {
result.add (LicenseEntryWithLineBreaks (const[], лицензия));
}
}
вернуть результат;
}

@переопределить
недействительным initServiceExtensions () {
супер.initServiceExtensions();

assert(() {
  registerStringServiceExtension(
    // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
    // the rootBundle cache and cause the entire image cache to be cleared.
    // This is used by hot reload mode to clear out the cache of resources
    // that have changed.
    name: 'evict',
    getter: () async => '',
    setter: (String value) async {
      evict(value);
    },
  );
  return true;
}());

}

/// Вызывается в ответ на расширение службы ext.flutter.evict .
///
/// Это используется инструментом flutter во время горячей перезагрузки, чтобы любые изображения
/// изменившиеся на диске очищаются от кешей.
@защищено
@mustCallSuper
void evict (строковый актив) {
rootBundle.evict (актив);
}

БудущееspawnIsolate(
БудущееИлиточка входа (T-сообщение),
Т-сообщение, {
логическое значение приостановлено = ложь,
логические ошибкиAreFatal,
SendPort при выходе,
SendPort при ошибке,
Строка имя_отладки,
}) {
утверждать(
_isMainIsolate,
«Невозможно сделать несколько уровней изолятов»,
);

final RawReceivePort messageReceiver = RawReceivePort(
  (Object receivedMessage) async {
    if (receivedMessage is SendPort) {
      receivedMessage.send(
        _IsolateStarter<T>(
          ui.PluginUtilities.getCallbackHandle(entryPoint),
          message,
        ),
      );
    } else if (receivedMessage is _Message) {
      final ByteData result = await defaultBinaryMessenger.send(
        receivedMessage.channel,
        receivedMessage.message,
      );
      receivedMessage.sendPort.send(result);
    }
  },
);
RawReceivePort onExitReceiver;
onExitReceiver = RawReceivePort(
  (Object message) {
    onExit?.send(message);

    onExitReceiver.close();
    messageReceiver.close();
  },
);

return Isolate.spawn(
  _startIsolate,
  messageReceiver.sendPort,
  paused: paused,
  errorsAreFatal: true,
  onExit: onExitReceiver.sendPort,
  onError: onError,
  debugName: debugName,
);

}
}

Будущее_startIsolate(SendPort sendPort) асинхронный {
_sendPortToMainIsolate = sendPort;
_Изолятбиндинг();

окончательный Completer<_IsolateStarter> завершение =
Завершитель<_IsolateStarter>();

окончательный RawReceivePort ReceivePort = RawReceivePort(
(Объект изолироватьстартер) {
утверждать (isolateStarter is _IsolateStarter);
completer.complete (изолироватьстартер как _IsolateStarter);
},
);

sendPort.send(receivePort.sendPort);

окончательный _IsolateStarterisolateStarter = ожидать завершения.будущее;

получитьПорт.закрыть();

конечная функция функция =
ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

функция ожидания (isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

bool get _isMainIsolate => _sendPortToMainIsolate == null;

класс _IsolateStarter{
_IsolateStarter(this.callbackHandle, this.message);

окончательный ui.CallbackHandle callbackHandle;
финальное Т-сообщение;
}

класс _Message {
_Сообщение(
этот.канал,
это сообщение,
этот.sendPort,
);

конечный строковый канал;
финальное сообщение ByteData;
конечный SendPort sendPort;
}

класс _IsolateBinding расширяет BindingBase с помощью ServicesBinding {}

/// Реализация [BinaryMessenger] по умолчанию.
///
/// Этот мессенджер отправляет сообщения со стороны приложения на сторону платформы и
/// отправляет входящие сообщения со стороны платформы в соответствующий
/// обработчик.
класс _DefaultBinaryMessenger расширяет BinaryMessenger {
const _DefaultBinaryMessenger._();

// Обработчики входящих сообщений от плагинов платформы.
// Это статично, так что этот класс может иметь конструктор const.
статическая финальная карта_handlers =
{};

// Поддельные обработчики, которые перехватывают исходящие сообщения и отвечают на них.
// Это статично, так что этот класс может иметь конструктор const.
статическая финальная карта_mockHandlers =
{};

Будущее_sendPlatformMessage (строковый канал, сообщение ByteData) {
окончательный ЗавершительЗавершитель = Завершитель();

if (_isMainIsolate) {
  // ui.window is accessed directly instead of using ServicesBinding.instance.window
  // because this method might be invoked before any binding is initialized.
  // This issue was reported in #27541. It is not ideal to statically access
  // ui.window because the Window may be dependency injected elsewhere with
  // a different instance. However, static access at this location seems to be
  // the least bad option.
  ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
    try {
      completer.complete(reply);
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context:
            ErrorDescription('during a platform message response callback'),
      ));
    }
  });
} else {
  RawReceivePort receivePort;
  receivePort = RawReceivePort(
    (Object message) async {
      assert(message is ByteData);
      completer.complete(message as ByteData);
      receivePort.close();
    },
  );
  _sendPortToMainIsolate.send(
    _Message(channel, message, receivePort.sendPort),
  );
}

return completer.future;

}

@переопределить
Будущееобработать сообщение платформы (
Струнный канал,
байтовые данные,
обратный вызов ui.PlatformMessageResponseCallback,
) асинхронный {
ответ ByteData;
пытаться {
окончательный обработчик MessageHandler = _handlers[channel];
если (обработчик != ноль) {
ответ = обработчик ожидания (данные);
} еще {
ui.channelBuffers.push(канал, данные, обратный вызов);
обратный вызов = ноль;
}
} поймать (исключение, стек) {
FlutterError.reportError(FlutterErrorDetails(
исключение: исключение,
стек: стек,
библиотека: 'библиотека сервисов',
контекст: ErrorDescription('во время обратного вызова сообщения платформы'),
));
} наконец-то {
если (обратный вызов!= ноль) {
обратный вызов (ответ);
}
}
}

@переопределить
Будущееsend(строковый канал, сообщение ByteData) {
окончательный обработчик MessageHandler = _mockHandlers[channel];
если (обработчик != ноль)
обработчик возврата (сообщение);
вернуть _sendPlatformMessage (канал, сообщение);
}

@переопределить
void setMessageHandler (строковый канал, обработчик MessageHandler) {
если (обработчик == ноль)
_handlers.remove(канал);
еще
_handlers[канал] = обработчик;
ui.channelBuffers.drain (канал, (данные ByteData, обратный вызов ui.PlatformMessageResponseCallback) async {
await handlePlatformMessage (канал, данные, обратный вызов);
});
}

@переопределить
void setMockMessageHandler (строковый канал, обработчик MessageHandler) {
если (обработчик == ноль)
_mockHandlers.remove (канал);
еще
_mockHandlers[канал] = обработчик;
}
}
```

У меня флаттер 1.21, поэтому я попытался обновить весь код для компиляции.
Теперь осталась одна проблема, потому что в _IsolateBinding отсутствует множество реализаций методов BindingBase...

мне нравится

Error: The non-abstract class '_IsolateBinding' is missing implementations for these members:
 - BindingBase with ServicesBinding.SchedulerBinding.addPersistentFrameCallback

около 30 раз.

В конце консоль печатает

/C:/flutter/packages/flutter/lib/src/services/binding.dart:341:7: Error: 'BindingBase' doesn't implement 'SchedulerBinding' so it can't be used with 'ServicesBinding'.
 - 'BindingBase' is from 'package:flutter/src/foundation/binding.dart' ('/C:/flutter/packages/flutter/lib/src/foundation/binding.dart').
 - 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('/C:/flutter/packages/flutter/lib/src/scheduler/binding.dart').
 - 'ServicesBinding' is from 'package:flutter/src/services/binding.dart' ('/C:/flutter/packages/flutter/lib/src/services/binding.dart').
class _IsolateBinding extends BindingBase with ServicesBinding {}

Я не уверен на 100%, что делает вызов _IsolateBinding() в _startIsolate , но без него я получаю общую ошибку ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. .

Я пытался это исправить, но думаю, что мои знания миксинов еще не так хороши.
Есть идеи, как это исправить?


Новый файл выглядит так (пока)

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// <strong i="24">@dart</strong> = 2.8

import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';

import 'asset_bundle.dart';
import 'binary_messenger.dart';
import 'restoration.dart';
import 'system_channels.dart';

/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
///
/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
/// the licenses found in the `LICENSE` file stored at the root of the asset
/// bundle, and implements the `ext.flutter.evict` service extension (see
/// [evict]).
mixin ServicesBinding on BindingBase, SchedulerBinding {
  <strong i="25">@override</strong>
  void initInstances() {
    super.initInstances();
    _instance = this;
    _defaultBinaryMessenger = createBinaryMessenger();
    _restorationManager = createRestorationManager();
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    initLicenses();
    SystemChannels.system.setMessageHandler(handleSystemMessage);
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    readInitialLifecycleStateFromNativeWindow();
  }

  /// The current [ServicesBinding], if one has been created.
  static ServicesBinding get instance => _instance;
  static ServicesBinding _instance;

  /// The default instance of [BinaryMessenger].
  ///
  /// This is used to send messages from the application to the platform, and
  /// keeps track of which handlers have been registered on each channel so
  /// it may dispatch incoming messages to the registered handler.
  BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
  BinaryMessenger _defaultBinaryMessenger;

  /// Creates a default [BinaryMessenger] instance that can be used for sending
  /// platform messages.
  <strong i="26">@protected</strong>
  BinaryMessenger createBinaryMessenger() {
    return const _DefaultBinaryMessenger._();
  }

  /// Called when the operating system notifies the application of a memory
  /// pressure situation.
  ///
  /// This method exposes the `memoryPressure` notification from
  /// [SystemChannels.system].
  <strong i="27">@protected</strong>
  <strong i="28">@mustCallSuper</strong>
  void handleMemoryPressure() { }

  /// Handler called for messages received on the [SystemChannels.system]
  /// message channel.
  ///
  /// Other bindings may override this to respond to incoming system messages.
  <strong i="29">@protected</strong>
  <strong i="30">@mustCallSuper</strong>
  Future<void> handleSystemMessage(Object systemMessage) async {
    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
    final String type = message['type'] as String;
    switch (type) {
      case 'memoryPressure':
        handleMemoryPressure();
        break;
    }
    return;
  }

  /// Adds relevant licenses to the [LicenseRegistry].
  ///
  /// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
  /// all the licenses collected by the `flutter` tool during compilation.
  <strong i="31">@protected</strong>
  <strong i="32">@mustCallSuper</strong>
  void initLicenses() {
    LicenseRegistry.addLicense(_addLicenses);
  }

  Stream<LicenseEntry> _addLicenses() async* {
    // We use timers here (rather than scheduleTask from the scheduler binding)
    // because the services layer can't use the scheduler binding (the scheduler
    // binding uses the services layer to manage its lifecycle events). Timers
    // are what scheduleTask uses under the hood anyway. The only difference is
    // that these will just run next, instead of being prioritized relative to
    // the other tasks that might be running. Using _something_ here to break
    // this into two parts is important because isolates take a while to copy
    // data at the moment, and if we receive the data in the same event loop
    // iteration as we send the data to the next isolate, we are definitely
    // going to miss frames. Another solution would be to have the work all
    // happen in one isolate, and we may go there eventually, but first we are
    // going to see if isolate communication can be made cheaper.
    // See: https://github.com/dart-lang/sdk/issues/31959
    //      https://github.com/dart-lang/sdk/issues/31960
    // TODO(ianh): Remove this complexity once these bugs are fixed.
    final Completer<String> rawLicenses = Completer<String>();
    scheduleTask(() async {
      rawLicenses.complete(await rootBundle.loadString('NOTICES', cache: false));
    }, Priority.animation);
    await rawLicenses.future;
    final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
    scheduleTask(() async {
      parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
    }, Priority.animation);
    await parsedLicenses.future;
    yield* Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
  }

  // This is run in another isolate created by _addLicenses above.
  static List<LicenseEntry> _parseLicenses(String rawLicenses) {
    final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
    final List<LicenseEntry> result = <LicenseEntry>[];
    final List<String> licenses = rawLicenses.split(_licenseSeparator);
    for (final String license in licenses) {
      final int split = license.indexOf('\n\n');
      if (split >= 0) {
        result.add(LicenseEntryWithLineBreaks(
          license.substring(0, split).split('\n'),
          license.substring(split + 2),
        ));
      } else {
        result.add(LicenseEntryWithLineBreaks(const <String>[], license));
      }
    }
    return result;
  }

  <strong i="33">@override</strong>
  void initServiceExtensions() {
    super.initServiceExtensions();

    assert(() {
      registerStringServiceExtension(
        // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
        // the rootBundle cache and cause the entire image cache to be cleared.
        // This is used by hot reload mode to clear out the cache of resources
        // that have changed.
        name: 'evict',
        getter: () async => '',
        setter: (String value) async {
          evict(value);
        },
      );
      return true;
    }());
  }

  /// Called in response to the `ext.flutter.evict` service extension.
  ///
  /// This is used by the `flutter` tool during hot reload so that any images
  /// that have changed on disk get cleared from caches.
  <strong i="34">@protected</strong>
  <strong i="35">@mustCallSuper</strong>
  void evict(String asset) {
    rootBundle.evict(asset);
  }

  Future<Isolate> spawnIsolate<T>(
      FutureOr<void> entryPoint(T message),
      T message, {
        bool paused = false,
        bool errorsAreFatal,
        SendPort onExit,
        SendPort onError,
        String debugName,
      }) {
    assert(
    _isMainIsolate,
    'Can\'t make multiple levels of isolates',
    );

    final RawReceivePort messageReceiver = RawReceivePort(
          (Object receivedMessage) async {
        if (receivedMessage is SendPort) {
          receivedMessage.send(
            _IsolateStarter<T>(
              ui.PluginUtilities.getCallbackHandle(entryPoint),
              message,
            ),
          );
        } else if (receivedMessage is _Message) {
          final ByteData result = await defaultBinaryMessenger.send(
            receivedMessage.channel,
            receivedMessage.message,
          );
          receivedMessage.sendPort.send(result);
        }
      },
    );
    RawReceivePort onExitReceiver;
    onExitReceiver = RawReceivePort(
          (Object message) {
        onExit?.send(message);

        onExitReceiver.close();
        messageReceiver.close();
      },
    );

    return Isolate.spawn(
      _startIsolate,
      messageReceiver.sendPort,
      paused: paused,
      errorsAreFatal: true,
      onExit: onExitReceiver.sendPort,
      onError: onError,
      debugName: debugName,
    );
  }



  // App life cycle

  /// Initializes the [lifecycleState] with the [Window.initialLifecycleState]
  /// from the window.
  ///
  /// Once the [lifecycleState] is populated through any means (including this
  /// method), this method will do nothing. This is because the
  /// [Window.initialLifecycleState] may already be stale and it no longer makes
  /// sense to use the initial state at dart vm startup as the current state
  /// anymore.
  ///
  /// The latest state should be obtained by subscribing to
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
  <strong i="36">@protected</strong>
  void readInitialLifecycleStateFromNativeWindow() {
    if (lifecycleState != null) {
      return;
    }
    final AppLifecycleState state = _parseAppLifecycleMessage(window.initialLifecycleState);
    if (state != null) {
      handleAppLifecycleStateChanged(state);
    }
  }

  Future<String> _handleLifecycleMessage(String message) async {
    handleAppLifecycleStateChanged(_parseAppLifecycleMessage(message));
    return null;
  }

  static AppLifecycleState _parseAppLifecycleMessage(String message) {
    switch (message) {
      case 'AppLifecycleState.paused':
        return AppLifecycleState.paused;
      case 'AppLifecycleState.resumed':
        return AppLifecycleState.resumed;
      case 'AppLifecycleState.inactive':
        return AppLifecycleState.inactive;
      case 'AppLifecycleState.detached':
        return AppLifecycleState.detached;
    }
    return null;
  }

  /// The [RestorationManager] synchronizes the restoration data between
  /// engine and framework.
  ///
  /// See the docs for [RestorationManager] for a discussion of restoration
  /// state and how it is organized in Flutter.
  ///
  /// To use a different [RestorationManager] subclasses can override
  /// [createRestorationManager], which is called to create the instance
  /// returned by this getter.
  RestorationManager get restorationManager => _restorationManager;
  RestorationManager _restorationManager;

  /// Creates the [RestorationManager] instance available via
  /// [restorationManager].
  ///
  /// Can be overriden in subclasses to create a different [RestorationManager].
  <strong i="37">@protected</strong>
  RestorationManager createRestorationManager() {
    return RestorationManager();
  }
}

Future<void> _startIsolate<T>(SendPort sendPort) async {
  _sendPortToMainIsolate = sendPort;
  _IsolateBinding();

  final Completer<_IsolateStarter<T>> completer =
  Completer<_IsolateStarter<T>>();

  final RawReceivePort receivePort = RawReceivePort(
        (Object isolateStarter) {
      assert(isolateStarter is _IsolateStarter<T>);
      completer.complete(isolateStarter as _IsolateStarter<T>);
    },
  );

  sendPort.send(receivePort.sendPort);

  final _IsolateStarter<T> isolateStarter = await completer.future;

  receivePort.close();

  final Function function =
  ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

  await function(isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

bool get _isMainIsolate => _sendPortToMainIsolate == null;

class _IsolateStarter<T> {
  _IsolateStarter(this.callbackHandle, this.message);

  final ui.CallbackHandle callbackHandle;
  final T message;
}

class _Message {
  _Message(
      this.channel,
      this.message,
      this.sendPort,
      );

  final String channel;
  final ByteData message;
  final SendPort sendPort;
}

//TODO not working
class _IsolateBinding extends BindingBase with ServicesBinding {}

/// The default implementation of [BinaryMessenger].
///
/// This messenger sends messages from the app-side to the platform-side and
/// dispatches incoming messages from the platform-side to the appropriate
/// handler.
class _DefaultBinaryMessenger extends BinaryMessenger {
  const _DefaultBinaryMessenger._();

  // Handlers for incoming messages from platform plugins.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _handlers =
  <String, MessageHandler>{};

  // Mock handlers that intercept and respond to outgoing messages.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _mockHandlers =
  <String, MessageHandler>{};

  Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
    final Completer<ByteData> completer = Completer<ByteData>();

    if (_isMainIsolate) {
      // ui.window is accessed directly instead of using ServicesBinding.instance.window
      // because this method might be invoked before any binding is initialized.
      // This issue was reported in #27541. It is not ideal to statically access
      // ui.window because the Window may be dependency injected elsewhere with
      // a different instance. However, static access at this location seems to be
      // the least bad option.
      ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
        try {
          completer.complete(reply);
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'services library',
            context:
            ErrorDescription('during a platform message response callback'),
          ));
        }
      });
    } else {
      RawReceivePort receivePort;
      receivePort = RawReceivePort(
            (Object message) async {
          assert(message is ByteData);
          completer.complete(message as ByteData);
          receivePort.close();
        },
      );
      _sendPortToMainIsolate.send(
        _Message(channel, message, receivePort.sendPort),
      );
    }

    return completer.future;
  }

  <strong i="38">@override</strong>
  Future<void> handlePlatformMessage(
      String channel,
      ByteData data,
      ui.PlatformMessageResponseCallback callback,
      ) async {
    ByteData response;
    try {
      final MessageHandler handler = _handlers[channel];
      if (handler != null) {
        response = await handler(data);
      } else {
        ui.channelBuffers.push(channel, data, callback);
        callback = null;
      }
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context: ErrorDescription('during a platform message callback'),
      ));
    } finally {
      if (callback != null) {
        callback(response);
      }
    }
  }

  <strong i="39">@override</strong>
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }

  <strong i="40">@override</strong>
  void setMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _handlers.remove(channel);
    else
      _handlers[channel] = handler;
    ui.channelBuffers.drain(channel, (ByteData data, ui.PlatformMessageResponseCallback callback) async {
      await handlePlatformMessage(channel, data, callback);
    });
  }

  <strong i="41">@override</strong>
  void setMockMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _mockHandlers.remove(channel);
    else
      _mockHandlers[channel] = handler;
  }

  <strong i="42">@override</strong>
  bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;

  <strong i="43">@override</strong>
  bool checkMockMessageHandler(String channel, MessageHandler handler) => _mockHandlers[channel] == handler;
}

@Найлик
На данный момент я не могу обновить флаттер, чтобы помочь вам

Мне интересно, почему другие инструменты флаттера, реализованные командой флаттера (например, devtools), никогда не попадали в этот блокировщик.
Кажется, они исправляют ошибки, только если они блокируются ими.

Это не серьезная новая функция, это блокировщик фреймворка.
Итак, согласно этому https://github.com/flutter/flutter/issues/18761#issuecomment -639248761, это должен быть P3

Мне интересно, почему другие инструменты флаттера, реализованные командой флаттера (например, devtools), никогда не попадали в этот блокировщик.
Кажется, они исправляют ошибки, только если они блокируются ими.

Честно говоря, потому что я думаю, что влияние этой проблемы на приложения реального мира переоценено. Я просто слежу за проблемой, потому что это облегчило бы задачу, но есть «обходные пути». И многие приложения и плагины в производстве используют фоновую функциональность.

Я также не уверен, что некоторые из +1 просто не совсем понимают, в чем проблема. (Например https://github.com/flutter/flutter/issues/13937#issuecomment-635683123). Имо, официальной документации для фоновых изолятов и безголовой среды выполнения (и как ею управлять) все еще немного не хватает, а обмен сообщениями в firebase — это кошмар, но это не имеет ничего общего с фреймворком, движком 🤷‍♂️

Может быть, просто перестаньте ругать флаттер-команду, прогресс есть, и это не то, что нельзя обойти... И если есть пиар, почему бы просто не отправить его на рассмотрение флаттер-команде 🤔

Существует проблема с реализацией этого поведения для изолятов.

Пример:

Есть главный изолят и двигатель.
Если изоляту что-то нужно, он обращается к движку.
Если движку что-то нужно, он просит изолировать.

Сейчас наша ситуация:
Есть 2 изолята и двигатель.
Если изоляту что-то нужно, он обращается к движку.
Если двигателю что-то нужно, что он должен делать?
Спросить обоих? Тогда чей ответ взять?
Или задать только один изолят? Тогда какой?

@hpoul Я должен согласиться не согласиться, так как многие люди здесь новички в программировании для Android / iOS, и они выбрали флаттер для своей первой среды разработки приложений.

Поэтому важно, чтобы кто-то понял это и исправил или предложил официальный обходной путь с примером для таких проблем, когда вы заблокированы и вынуждены выполнять сложные «обходные пути», поскольку очевидно, что новички не будут знать, чему учиться или даже с чего начать, потому что в комментариях здесь или где-либо еще в stackoverflow или других блогах нет хорошо объясненных обходных путей. Есть только вещи, указывающие на некоторые пакеты, которые могут или не могут реализовать это правильно.

Люди, которые имеют огромный опыт работы с сервисами, очередями фоновых задач и т. д., как в Android, так и в iOS, которые могут легко обойти это, не являются целевой аудиторией для флаттера.

И что касается подобных комментариев не по теме, я уверен, что в каждом другом выпуске есть ложные +1, что не сильно меняет порядок приоритетов выпуска, и этот выпуск по-прежнему остается в топе.

Привет @phanirithvij

Ссылка на flutter_isolate была указана выше и существует с февраля 2019 года. Это также не сложно. Я скопирую README, который кажется простым:

FlutterIsolate позволяет создавать изолят во флаттере, который может использовать плагины флаттера. Он создает необходимые специфичные для платформы биты (FlutterBackgroundView для Android и FlutterEngine для iOS), чтобы каналы платформы могли работать внутри изоляции.

| | Андроид | iOS | Описание |
| :--------------- | :----------------: | :-----------------: | :-------------------------------- |
| FlutterIsolate.spawn (точка входа, сообщение) | :white_check_mark: | :white_check_mark: | порождает новый FlutterIsolate |
| флаттеризолат.пауза () | :white_check_mark: | :white_check_mark: | приостанавливает выполнение изолята |
| flutterIsolate.resume() | :white_check_mark: | :white_check_mark: | возобновил приостановленный isoalte |
| флаттеризолат.убить () | :white_check_mark: | :white_check_mark: | убивает изолят |

Я говорю просто, так как эти методы имеют те же имена, что и в «оригинальном» классе Isolate , поэтому, если вы уже знаете, как использовать изоляты из официальной документации, вам не составит труда понять, как их использовать. это как замена, по крайней мере, для тех методов, которые перечислены выше.

(Примечание: я тоже хотел бы, чтобы эта проблема была официально устранена по причинам, которые я уже изложил в более раннем комментарии, некоторыми из которых я делюсь с вами, но я также не назвал бы этот обходной путь «сложным».)

В ответ на комментарий @nikitadol flutter_isolate создает отдельный движок для каждого изолята, чтобы не сталкиваться с одной и той же проблемой. Конечно, не идеально создавать новый движок для каждого изолята, и это одна из причин, по которой я уверен, что команда Flutter могла бы справиться с задачей лучше. Точно так же альтернативное решение, заключающееся в передаче всех вызовов методов плагина через основной изолят, создаст узкое место, которое в первую очередь противоречит целям изолятов, поэтому я уверен, что команда Flutter могла бы справиться и с этим лучше.

Я согласен с теми, кто сказал, что у команды Flutter могут быть проблемы с более высоким приоритетом, которые нужно решить в первую очередь, но, основываясь на том, что мы видели ранее с архитектурой плагина, также может быть, что для исправления этого потребуется серьезное изменение архитектуры и несколько других открытых проблем. также требуют серьезного архитектурного изменения, и взвешивание всех этих различных требований может занять некоторое время, прежде чем они смогут даже решить эту конкретную проблему.

@ryanheise

flutter_isolate создает отдельный движок для каждого изолята

поэтому я уверен, что команда Flutter могла бы справиться и с этим лучше.

Вы говорите, что команда Flutter может это сделать, но как?
Вопросы, которые я задавал выше, остаются в силе.
Если решение состоит в том, чтобы создать новый движок, вы можете просто использовать свой плагин

(Обратите внимание, flutter_isolate — это не мой плагин, но, поскольку мне тоже нужна была эта функция, я решил внести свой вклад в проект.)

Что я имел в виду под «лучшей работой», так это то, что, вероятно, нет необходимости раскручивать ВСЕ механизмы FlutterEngine только для связи с плагинами, но на данный момент вся необходимая инфраструктура привязана к движку, поэтому в настоящее время Flutter_isolate должен раскрутить весь движок только для того, чтобы получить доступ к плагинам.

(Обратите внимание, flutter_isolate — это не мой плагин, но, поскольку мне тоже нужна была эта функция, я решил внести свой вклад в проект.)

Извините, я не заметил, что это вилка

Flutter_isolate должен раскрутить весь движок только для того, чтобы получить доступ к плагинам.

Если указать что 'is_background_view=true', то движок не запускает рендеринг - значит движок не запускается полностью

Точный API для работы без головы отличается для iOS и Android v1 и Android v2, но, по сути, это то, что уже делает flutter_isolate. Тем не менее, существует разница в накладных расходах между обычным изолятом и FlutterEngine, поэтому дальнейшее снижение накладных расходов — это только то, что сможет сделать команда Flutter. Это не только накладные расходы во время запуска, но и накладные расходы на использование памяти.

То есть вы просто предлагаете оптимизировать создание нового движка для работы в фоновом режиме?

Тогда это, вероятно, не относится к этому вопросу

Я не предлагаю этого, я просто указываю, что обходной путь flutter_isolate имеет это ограничение из-за накладных расходов, которых нельзя избежать. Я не предлагаю команде Flutter попытаться сделать обходной путь более эффективным, я думаю, что в идеале было бы архитектурное изменение способа загрузки плагинов, чтобы не было необходимости создавать экземпляр FlutterEngine.

Если двигатель равен 1, а изоляты больше 1, то возвращаемся к этому вопросу:

https://github.com/flutter/flutter/issues/13937#issuecomment -667314232

На самом деле я не пытаюсь придумать решение для этой новой облегченной архитектуры, но если вы спросите меня, я могу придумать два варианта упрощенного решения:

  1. Сделайте каналы методов немного похожими на серверные сокеты, заставив их принимать соединения от нескольких клиентов. Таким образом, канал метода на стороне платформы плагина может принимать подключения от нескольких изолятов, а затем, после подключения, отправлять сообщения нужному изоляту.
  2. Создайте новый реестр плагинов и т. д. для каждого изолята, НО в том же FlutterEngine,

если ты спрашиваешь меня

Да, я спрашиваю

может отправлять сообщения нужному изоляту.

Я думаю, что это плохое решение, так как «правильно изолировать» — относительное понятие.

Создайте новый реестр плагинов и т. д. для каждого изолята, НО в том же FlutterEngine,

Это может быть хорошим вариантом, но только если движок всегда сам регистрирует плагины.

Но это происходит только в том случае, если все плагины в GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

Создание дополнительных обратных вызовов не является очевидным поведением.

Я думаю, что это плохое решение, так как «правильно изолировать» — относительное понятие.

Это может быть плохим решением, но не может быть плохим по этой причине. «Правильная изоляция» однозначна, точно так же, как сокет сервера однозначно обрабатывает клиентские соединения.

Это может быть хорошим вариантом, но только если движок всегда сам регистрирует плагины.

Что было бы хорошо, чтобы убедиться, что это всегда происходит. На данный момент механизм немного глючит.

Это может быть плохим решением, но не может быть плохим по этой причине. «Правильная изоляция» однозначна, точно так же, как сокет сервера однозначно обрабатывает клиентские соединения.

Тогда каждый плагин должен учитывать, что к нему можно получить доступ из разных изолятов - плохо

Ага.

Позвольте мне добавить свои два цента к этому обсуждению... Работая с Flutter на коммерческой основе около двух лет, самое важное, что я узнал, это то, что Flutter — это инструментарий пользовательского интерфейса . В этом утверждении не было бы ничего удивительного — это даже написано на основном сайте Flutter — если бы об этом не забывали.

Flutter — это набор инструментов пользовательского интерфейса, и то, что он делает хорошо, — это пользовательский интерфейс. Если вы пытаетесь создать приложение с большой нагрузкой на ЦП, которое полагается на дорогостоящую обработку данных или использует сложные алгоритмы и потребляет большие объемы данных, вам НЕ следует использовать для этой цели набор инструментов пользовательского интерфейса. Бизнес-логику не следует смешивать с пользовательским интерфейсом.

Конечно, с Dart мы можем сделать гораздо больше, чем просто пользовательский интерфейс, но давайте не будем забывать об основной цели этого инструментария.

Я пробовал несколько подходов, включая использование безголового Flutter, что оказалось полной катастрофой. Не потому, что это не работает, а потому, что нам трудно объяснить другим разработчикам, как это работает. Расходы на техническое обслуживание, которые он добавляет, просто того не стоят. Не говоря уже о тестировании такого решения. В прошлый раз, когда я пытался, Flutter Driver вообще не справился.

Если вашему приложению действительно нужна вычислительная мощность, ИМХО, у вас есть три варианта:

  • перенести свою логику на сервер;
  • переместите свою логику на нативную платформу — для этого вы можете использовать Kotlin Native — и используйте каналы Event/Method только для предоставления моделей просмотра вашему пользовательскому интерфейсу (я думаю, что это то, что сделал OLX: https://tech.olx.com/fast -прототипы-с-флаттер-котлин-нативный-d7ce5cfeb5f1);
  • не используйте Flutter и ищите другой фреймворк (для этого может подойти Xamarin/Blazor, поскольку C# предлагает более сложную многопоточную среду, чем Dart).

У всего вышеперечисленного есть свои недостатки, но попытка растянуть библиотеку/фреймворк, чтобы сделать то, для чего он на самом деле не был разработан, также не идеальна.

Если бы isolate_handler не решил эту проблему?

@hasonguo Он использует flutter_isolate в фоновом режиме, он просто добавляет дополнительный уровень, чтобы упростить обработку изолятов (на самом деле, любых изолятов), добавляя шаблон для связи, чтобы вам не нужно было настраивать его вручную каждый раз. время. Ранее он использовал другой способ решения проблемы, но это стало невозможным из-за изменений в самом Flutter, и вместо этого он был изменен, чтобы полагаться на flutter_isolate .

Мы идем по кругу. Да, есть решения, оба плагина на самом деле. Как указал Райан, существуют накладные расходы, связанные с обеспечением возможности связи по каналам. Скорее всего, именно эти накладные расходы заставили команду Flutter не добавлять их автоматически к исходному Isolate , так как вам не нужна поддержка во многих (большинстве?) случаях.

Тем не менее, это не такое чистое решение, как могло бы быть. Если бы команда Flutter смогла обеспечить канальную связь без накладных расходов, напрямую и автоматически, прямо из коробки с помощью любого базового Isolate , вся дискуссия была бы спорной. Однако верно и обратное: на самом деле они не стремятся к изменениям ради самих изменений. Если есть жизнеспособное решение в пакете или плагине, зачем тратить время и добавлять вес ядру?

Создайте новый реестр плагинов и т. д. для каждого изолята, НО в том же FlutterEngine,

Это может быть хорошим вариантом, но только если движок всегда сам регистрирует плагины.

Но это происходит только в том случае, если все плагины в GeneratedPluginRegistrant.
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

Создание дополнительных обратных вызовов не является очевидным поведением.

Еще одно улучшение этой второй идеи заключается в том, чтобы плагины загружались лениво в новом изоляте. Обычно, если проект зависит от многих плагинов, GeneratedPluginRegistrant будет довольно большим, и в зависимости от того, насколько эффективна процедура инициализации плагина, это может привести к некоторым накладным расходам. Как именно это реализовать — отдельная история. Каналы платформы не принадлежат подключаемым модулям, поэтому в настоящее время отправка сообщения по каналу не может использоваться для запуска создания экземпляра подключаемого модуля на стороне платформы. Таким образом, либо каналы методов должны быть связаны с плагинами, либо должен быть дополнительный механизм, который Dart-сторона плагина может вызвать для ленивой инициализации перед созданием первого канала метода, и этот механизм может запускать создание экземпляра. стороны платформы этого плагина в изоляте.

@ТахаТессер

В вашем примере ошибка не связана с этой проблемой

E/flutter ( 7814): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Invalid argument(s): Isolate.spawn expects to be passed a static or top-level function

Привет @nikitadol
Спасибо, обновленный пример, он не выдает исключение, это правильно? (Мне никогда не нужно было использовать раньше)
Не могли бы вы предоставить полный воспроизводимый минимальный образец кода
Спасибо

@ТахаТессер


образец кода

import 'package:battery/battery.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

Future<void> main() async {
  await printBatteryLevel();
  await compute(printBatteryLevel, null);
}

Future<void> printBatteryLevel([dynamic message]) async {
  WidgetsFlutterBinding.ensureInitialized();
  print(await Battery().batteryLevel);
}



журналы

Launching lib/main.dart on Pixel 4 in debug mode...
Running Gradle task 'assembleDebug'...
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
Waiting for Pixel 4 to report its views...
Debug service listening on ws://127.0.0.1:50709/-SPs_6AmL2Q=/ws
Syncing files to device Pixel 4...
I/flutter ( 8708): 39
E/flutter ( 8708): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Exception: UI actions are only available on root isolate.
E/flutter ( 8708): #0      Window._nativeSetNeedsReportTimings (dart:ui/window.dart:1003:86)
E/flutter ( 8708): #1      Window.onReportTimings= (dart:ui/window.dart:996:29)
E/flutter ( 8708): #2      SchedulerBinding.addTimingsCallback (package:flutter/src/scheduler/binding.dart:272:14)
E/flutter ( 8708): #3      SchedulerBinding.initInstances (package:flutter/src/scheduler/binding.dart:209:7)
E/flutter ( 8708): #4      ServicesBinding.initInstances (package:flutter/src/services/binding.dart:27:11)
E/flutter ( 8708): #5      PaintingBinding.initInstances (package:flutter/src/painting/binding.dart:23:11)
E/flutter ( 8708): #6      SemanticsBinding.initInstances (package:flutter/src/semantics/binding.dart:24:11)
E/flutter ( 8708): #7      RendererBinding.initInstances (package:flutter/src/rendering/binding.dart:32:11)
E/flutter ( 8708): #8      WidgetsBinding.initInstances (package:flutter/src/widgets/binding.dart:257:11)
E/flutter ( 8708): #9      new BindingBase (package:flutter/src/foundation/binding.dart:59:5)
E/flutter ( 8708): #10     new _WidgetsFlutterBinding&BindingBase&GestureBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #11     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #12     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #13     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #14     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #15     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #16     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #17     new WidgetsFlutterBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #18     WidgetsFlutterBinding.ensureInitialized (package:flutter/src/widgets/binding.dart:1229:7)
E/flutter ( 8708): #19     printBatteryLevel (package:bug/main.dart:11:25)
E/flutter ( 8708): #20     _IsolateConfiguration.apply (package:flutter/src/foundation/_isolates_io.dart:83:34)
E/flutter ( 8708): #21     _spawn.<anonymous closure> (package:flutter/src/foundation/_isolates_io.dart:90:65)
E/flutter ( 8708): #22     Timeline.timeSync (dart:developer/timeline.dart:163:22)
E/flutter ( 8708): #23     _spawn (package:flutter/src/foundation/_isolates_io.dart:87:35)
E/flutter ( 8708): #24     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:304:17)
E/flutter ( 8708): #25     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 8708): 



флаттер доктор -v

[✓] Flutter (Channel stable, 1.20.4, on Mac OS X 10.15.6 19G2021, locale en-BY)
    • Flutter version 1.20.4 at /Users/nikitadold/development/flutter
    • Framework revision fba99f6cf9 (2 weeks ago), 2020-09-14 15:32:52 -0700
    • Engine revision d1bc06f032
    • Dart version 2.9.2

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.0)
    • Android SDK at /Users/nikitadold/Library/Android/sdk
    • Platform android-30, build-tools 30.0.0
    • Java binary at: /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.0.1, Build version 12A7300
    • CocoaPods version 1.9.3

[!] Android Studio (version 4.0)
    • Android Studio at /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] IntelliJ IDEA Ultimate Edition (version 2020.2.2)
    • IntelliJ at /Users/nikitadold/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app
    • Flutter plugin installed
    • Dart plugin version 202.7319.5

@ТахаТессер
Пожалуйста, посмотрите этот комментарий

Сообщения платформы поддерживаются только из основного изолята. [...]

Это поведение является _ожидаемым_ и имеет для метки severe: new feature
эту проблему следует рассматривать как _запрос функции_ или _ предложение_ , а не как _ошибку_

@iapicca

В вашем примере ошибка не связана с этой проблемой

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

@iapicca

В вашем примере ошибка не связана с этой проблемой

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

вы правы, мой пример не очень хороший, пожалуйста, не обращайте на него внимания

Была ли эта страница полезной?
0 / 5 - 0 рейтинги