Автор Чжан Хань по прозвищу Менлю.

Когда Flutter был впервые разработан Google, веб-экосистема не рассматривалась. Причина была проста: обе технологии имели разные концепции дизайна, и принудительная интеграция, скорее всего, лишила бы их соответствующих преимуществ. Однако многие команды в отрасли сейчас пытаются сделать именно это, что показывает, что спрос действительно существует. Сегодня Чжан Хань, эксперт по разработке беспроводных сетей в Alibaba, покажет вам, как связать Flutter с веб-экосистемой.

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

Почему мы хотим их соединить?

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

Для небольшого контекста: Flutter в настоящее время является одной из самых популярных кроссплатформенных технологий, способных запускать код на Android, iOS, ПК, устройствах с поддержкой IoT и браузерах. Из-за этого его часто считают кроссплатформенной технологией следующего поколения. По сравнению с Weex и React Native, которые могут решать проблемы согласованности между несколькими платформами и имеют аналогичную производительность нативного рендеринга, верхний уровень Flutter более легкий, чем уровень инкапсуляции JavaScript (JS), и общая производительность Flutter также немного выше.

Однако первый вопрос, который возникает у большинства людей, изучающих Flutter: почему Flutter использует Dart? Знакомство с совершенно новым языком означает, что вам нужно выучить гораздо больше. Но разве JS недостаточно хорош? Если вы не увлекаетесь JS, как насчет TypeScript (TS)? Фактически, Flutter отказался не только от языка JS, но также от HTML и каскадных таблиц стилей (CSS) и разработал улучшенную и несвязанную систему виджетов. Короче говоря, Flutter отказался от всей веб-экосистемы. Вместо этого он стремится создать новую экосистему, но эта экосистема не может повторно использовать код и решения веб-экосистемы. В частности, все предыдущие кроссплатформенные решения, такие как Hybrid, React Native и Weex, совместимы с веб-экосистемой. Это делает Flutter исключением и удерживает большинство разработчиков интерфейса от его использования.

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

Поскольку режим разработки Flutter аналогичен режиму фреймворка внешнего интерфейса, который мы можем более или менее считать копией React, стоимость обучения фреймворка невелика, а стоимость обучения Dart лишь немного выше. Однако нам также нужно научиться собирать пользовательские интерфейсы с виджетами. Хотя многие виджеты макета похожи по дизайну на программы на основе CSS, они обеспечивают гораздо меньшую гибкость. Чтобы использовать виджеты в реальных проектах, вам нужно преобразовать всю цепочку инструментов и реализовать разработку с точки зрения Native First. Процесс разработки Flutter похож на процесс разработки нативных приложений, но сильно отличается от разработки страниц внешнего интерфейса. Самая высокая стоимость заключается в стоимости экосистемы. Трудно повторно использовать код и технические решения, накопленные во фронтенд-экосистеме, что является самым существенным недостатком Flutter. Соответственно, самым слабым звеном Flutter становится экосистема.

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

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

Интеграция Flutter и веб-экосистемы может быть выполнена двумя способами:

  • От Интернета к Flutter. Вы можете использовать стек веб-технологий для разработки, а затем интегрировать его с Flutter для кроссплатформенного рендеринга. С точки зрения веб-экосистемы это решает проблемы производительности и межплатформенной согласованности. С точки зрения Flutter это решает проблему повторного использования экосистемы.
  • От Flutter к Интернету. Официальная реализация веб-поддержки Flutter компилирует приложения, разработанные с помощью Dart, в приложения на основе HTML, JS или CSS, а затем запускает их в браузере. Этот подход можно использовать в сценариях деградации и внешнего питания.

Как превратить веб во флаттер?

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

Flutter можно разделить на две части: фреймворк и движок. Движок находится на нижнем слое и относительно стабилен, поэтому его лучше не переделывать. С другой стороны, инфраструктура, реализованная с использованием Dart, нуждается в модификации. Чтобы интегрировать веб-экосистему, нам нужно ввести движок JS, вопрос о том, следует ли сохранить виртуальную машину Dart, находится на рассмотрении. Как показано на предыдущем рисунке, библиотеки Material и Cupertino UI в верхней части не требуются, так как интерфейс имеет свои собственные замены. Здесь виджеты являются главной проблемой. Будете ли вы разрабатывать пользовательские интерфейсы в HTML или CSS или сохраните виджеты, но будете использовать JS в качестве языка разработки? Ну, разные ситуации требуют разных решений.

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

  • Трансформация TS:используйте движок JS для замены виртуальной машины Dart и используйте JS или TS для повторной реализации платформы Flutter или компилируйте напрямую с помощью dart2js.
  • Интеграция с JS: внедрите механизм JS, сохранив при этом виртуальную машину Dart, и подключитесь к платформе Flutter с помощью внешней среды.
  • Преобразование C++: используйте движок JS для замены виртуальной машины Dart и используйте C++ для повторной реализации платформы Flutter.

Трансформация ТС

Подход к преобразованию TS полностью отказывается от виртуальной машины Dart и использует TS для повторной реализации инфраструктуры Flutter, написанной на Dart.

Тогда почему в данном случае используется TS, а не JS? Это не потому, что TS в настоящее время является модным языком и обратно совместим с JS. Скорее, это решение начинается с вопроса: «Можно ли Dart для Flutter заменить на JS?» Самый простой способ сделать это — перевести Dart в TS или напрямую скомпилировать код в JS с помощью dart2js. Однако с JS скомпилированный код содержит множество инкапсуляций библиотек, таких как dart:ui, сгенерированный пакет огромен, и сложно настроить API для экспорта. Поэтому лучше переписать код с TS, потому что тулчейн более привычный и разрешены настройки.

Теоретически большинство функций Flutter по-прежнему будут поддерживаться после перевода, а решение может повторно использовать различные пакеты npm и быть динамическим. Однако мы потеряем возможность AOT. Следовательно, производительность выполнения JS, вероятно, будет ниже, чем у Dart. Кроме того, операции компоновки для всех узлов выполняются в JS, а для нижележащего слоя требуются только базовые графические возможности. Это напоминает разработку UI-фреймворка на основе Canvas API. В этом решении производительность не обязательно должна быть такой же высокой, как у существующих интерфейсных фреймворков.

Однако самая большая проблема заключается в том, как поддерживать согласованность с официальной версией Flutter. Предполагая, что мы переведем код из версии 1.13, потребуется ли обновление, когда будет доступна версия 1.15? Этот процесс не требует особых технических навыков, но требует постоянных инвестиций, что весьма раздражает.

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

JS-интеграция

Теперь, когда перевод кода не является элегантным решением, поэтому давайте сохраним Dart и подключим JS или CSS к виджетам.

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

Этот метод обеспечивает хорошую совместимость с интерфейсной средой, но процесс слишком длительный. Бизнес-код вызывает интерфейс фреймворка внешнего интерфейса для рендеринга, и инструкция по рендерингу выдается после выполнения некоторых операций. Затем эта инструкция рендеринга передается в структуру Flutter в режиме связи. Это общение включает межъязыковое преобразование из JS в C++, а затем в Dart. После получения инструкции по рендерингу ее необходимо преобразовать в соответствующее дерево виджетов, а преобразование CSS в виджет по-прежнему очень сложно. Более того, сам виджет имеет состояние и оперативно обновляется. Когда он обновляется, создается новый виджет, и для нового виджета используется алгоритм сравнения. Если пользовательский интерфейс обновляется во внешнем интерфейсе, алгоритм diff используется один раз для vdom в JS во внешнем интерфейсе, а затем используется для виджета после того, как инструкция рендеринга передается во Flutter.

Если вы хотите обойти виджеты и напрямую подключиться к слою рендеринга, показанному на рисунке, вы можете обойти операцию сравнения виджетов, но должны изменить процесс рендеринга платформы Flutter. Теперь, когда вам нужно изменить инфраструктуру Flutter, почему бы просто не использовать подход преобразования TS и также избежать необходимости обмена данными между JS и Dart? В результате это возвращает нас к первому решению.

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

Преобразование С++

Чтобы избавиться от Dart VM, вам нужно повторно реализовать платформу, разработанную с помощью Dart, на другом языке, таком как JS, TS или C++. Самый тщательный метод — использовать C++ для повторной реализации инфраструктуры Flutter, интеграции механизма JS и предоставления API-интерфейсов C++ для среды JS посредством привязки. Вы по-прежнему можете разрабатывать приложения верхнего уровня на JS.

Разрабатывая слой фреймворка на C++, вы можете повысить производительность и увеличить число поддерживаемых языков. Фреймворк Flutter изначально был построен на виртуальной машине Dart и полагается на нее для работы. Поэтому фреймворк Flutter сильно зависит от Dart. После повторной реализации на C++ движок JS строится поверх фреймворка C++, поэтому сам фреймворк не зависит от движка JS. Поэтому он может взаимодействовать с другими языками, такими как Java и Kotlin, или подключаться к виртуальной машине Dart для дальнейшей поддержки Dart.

Это решение может повысить производительность и обеспечить согласованность с Flutter, но затраты на преобразование и обслуживание очень высоки. Разработка на C++, несомненно, не так эффективна, как Dart. Также сложно угнаться за быстрой итерацией Flutter. Если вы не успеваете или реализация непоследовательна, вероятным результатом будет дифференциация. Преобразование CSS в виджет — еще одна проблема, с которой приходится сталкиваться.

Сравнение решений

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

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

Начиная снизу, двигатель Flutter не нуждается в модификации. Этот уровень является ключом к кросс-платформенной разработке. Фреймворк доступен на трех языках: JS/TS, Dart и C++. C++ обеспечивает наилучшую производительность, тогда как Dart отличается наименьшей стоимостью. Двигаясь вверх, вам нужно решить проблемы с HTML или CSS и виджетами. Здесь вы можете напрямую подключиться к фреймворку внешнего интерфейса или напрямую реализовать его на уровне C++. В противном случае будет слишком много API-интерфейсов привязки для раскрытия, и будет происходить частый обмен данными.

Как превратить Flutter в Web

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

Принципы реализации

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

Основной зависимостью фреймворка от движка является dart:ui, библиотека, реализованная в движке. Библиотека абстрагирует API-интерфейсы для рендеринга уровня пользовательского интерфейса, в то время как базовый уровень интегрирует реализацию Skia и предоставляет API-интерфейсы Dart для верхних уровней. Как видите, метод интеграции относительно прост:

  • Вы можете использовать dart2js для компиляции фреймворка в формат JS.
  • Затем вы повторно реализуете dart:ui (в частности, dart:web_ui) на основе API-интерфейсов браузера.

Скомпилировать Dart в JS не проблема. Производительность может немного пострадать, но функции могут быть полностью сохранены. Ключом является реализация dart:web_ui. В собственном движке dart:ui полагается на SkCanvas, предоставляемый Skia для рендеринга. Это набор базовых графических API, которые определяют только базовые возможности, такие как рисование линий, полигонов и текстур. По-прежнему сложно реализовать этот набор API-интерфейсов через API-интерфейсы браузера. Как показано на предыдущем рисунке, механизм веб-версии реализован на основе DOM и Canvas. Базовый слой определяет два графических API, DomCanvas и BitmapCanvas. Переданное дерево слоев будет отображено в дереве элементов браузера. Однако узел содержит только такие стили, как положение, преобразование и непрозрачность, которые являются лишь небольшим подмножеством CSS. Скорее, некоторые более сложные визуализации реализуются непосредственно в 2D.

Существующие проблемы

Я собрал довольно сложную демонстрацию и попробовал ее. Производительность не идеальна, прокрутка не плавная, иногда изображения мелькают. Сгенерированный JS-код имеет размер 1,1 МБ (без gzip после минимизации), а уровень узла относительно глубокий. По моим оценкам, при разработке страницы во внешнем интерфейсе размер кода не превысит 300 КБ, а количество узлов можно будет сократить как минимум вдвое.

Если вы посмотрите на проблемы в репозитории Flutter и отфильтруете данные платформы-веб, вы увидите большое количество проблем, таких как сбои редактирования текста, невозможность найти курсор, ListView не прокручивается на iOS, неожиданный флажок или кнопка поведение, задержка прокрутки и мигание изображения при прокрутке изображений на Android, недопустимые шрифты, невозможность воспроизведения видео на некоторых моделях, невозможность копирования выделенного текста и сбои отладки. С Flutter для Интернета мы попали в ловушку. Это напоминает мне о кошмаре, с которым пришлось столкнуться разработчикам внешнего интерфейса, чтобы обеспечить совместимость с разными браузерами.

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

Движку Flutter требуются базовые графические API и системные возможности. Несмотря на то, что предоставляются аналогичные графические API, если все они реализованы с помощью Canvas, трудно решать такие проблемы, как доступность, выделение текста, жесты и формы. Это также приведет ко многим проблемам совместимости. Поэтому верное решение сочетает Canvas с DOM, но уровень инкапсуляции слишком глубокий, а процесс рендеринга слишком долгий. Это как если бы во Flutter framework после ряда сложных операций генерируются узлы, рассчитывается макет, а также обрабатываются атрибуты рисования. Все, что осталось сделать, это отрисовать холст с этими ресурсами. Затем процесс передается браузеру, где элементы снова генерируются, а макет снова рассчитывается в процессе рендеринга. Только после этого мы переходим к базовой графической библиотеке для рисования.

Другой пример — прокрутка длинной страницы. Только код CSS (overflow:scroll) в браузере может сделать элементы прокручиваемыми. Прослушивание жестов, прокрутка страницы и анимация прокрутки реализованы браузером изначально. Нет необходимости взаимодействовать с JS или даже выполнять компоновку и рисование снова, но композитно. Как показано на предыдущем рисунке, анимация и жесты реализованы Dart во Flutter и компилируются с помощью JS. Браузер не знает, является ли этот элемент прокручиваемым, но постоянно отправляет событие touchmove. JS вычисляет смещение узла в соответствии с атрибутом события, вычисляет анимацию, а затем применяет преобразование или новую позицию к узлу. Затем браузер снова проходит полный процесс рендеринга.

Оптимизации

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

1. Используйте CSS Painting API для рендеринга.

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

2. Используйте Skia в версии WebAssembly для рендеринга. https://skia.org/user/modules/canvaskit

а. Такой подход в полной мере раскрывает преимущества производительности Wasm и поддерживает согласованность функций Skia. Однако Wasm не обязательно обеспечивает преимущества производительности в среде браузера. Здесь мы не будем вдаваться в этот вопрос.
б. Эта функция реализована частично. Для получения дополнительной информации о том, как его включить, см. конфигурацию по адресу: https://github.com/flutter/flutter/issues/41062#issuecomment-533952994.

Оба решения пытаются шире использовать базовые возможности браузера. Здесь идея состоит в том, что только когда браузер предоставляет больше базовых возможностей, он может лучше реализовать веб-движок Flutter. Однако это займет много времени, и мы не можем участвовать в этом процессе. Чтобы использовать Flutter для Web на текущем этапе, нам все еще нужно поддерживать существующую архитектуру и работать вместе над решением проблем, отдавая приоритет обеспечению функциональности. На данном этапе оптимизация производительности имеет второстепенное значение.

Более адаптируемая архитектура

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

Возвращаясь к диаграмме архитектуры в начале статьи, фреймворк (Dart) находится сверху, а движок (C++) — под ним. Разделенные базовым слоем, взаимодействие между обоими компонентами использует информацию о геометрической фигуре. Если эту архитектуру сохранить, мы можем переместить разделение между обоими компонентами вверх, между слоями Widgets и Rendering, как показано на следующем рисунке. Теоретически это изменение будет незаметно для разработчиков Flutter, поскольку язык разработки верхнего уровня и API-интерфейсы виджетов остаются неизменными.

Если это так, взаимодействие между фреймворком и движком больше не будет включать геометрические фигуры, а информацию об узлах. Комбинация виджетов, адаптивное обновление setState и операции сравнения виджетов по-прежнему будут выполняться в Dart, в то время как компоновка, рендеринг, обрезка и анимация расширенных RenderObjects будут выполняться на C++. Это решение обеспечит лучшую производительность и улучшенную интеграцию с движком.

В качестве альтернативы мы могли бы сохранить первоначальный дизайн движка и инкапсулировать утонченную логику в слой Renderer, как показано на следующем рисунке:

Каждый из этих трех слоев имеет четкую цель:

  • Framework: служит основой для разработки. Платформа предоставляет программируемые API-интерфейсы для реализации адаптивного режима разработки и детализированные виджеты, которые разработчики могут свободно инкапсулировать и комбинировать.
  • Renderer: служит механизмом рендеринга. Этот слой обрабатывает макет, рендеринг, анимацию и жесты. Эти функции относительно независимы и могут быть отделены от среды разработки без привязки к конкретному языку.
  • Движок: служит графическим движком. Этот уровень обеспечивает согласованные графические API на разных платформах. Он объединяет входные слои и рисует результат на экране, а также обрабатывает введение и адаптацию возможностей платформы.

Это решение не только обеспечивает преимущества в производительности, но также устраняет зависимость механизма рендеринга от Dart и поддерживает несколько языков и режимов разработки. Если вы интегрируете виртуальную машину Dart, вы можете использовать Dart для написания кода. Если вы интегрируете движок JS или JVM, вы можете соответственно использовать JS или Java для той же цели. В любом случае базовые возможности рендеринга остаются неизменными независимо от используемого языка, а алгоритмы компоновки, анимация и поведение обработки жестов также одинаковы во всех случаях.

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

Это также упростит реализацию Flutter для Интернета. При интеграции на уровне движка мы всегда страдаем от недостаточных базовых возможностей, предоставляемых браузером. Эта ситуация улучшается, когда мы интегрируем на уровне Renderer. Мы можем инкапсулировать набор API-интерфейсов рендеринга на основе возможностей JS, CSS, DOM и Canvas для вызовов виджетов, упрощая процесс рендеринга. Тем не менее, мы по-прежнему должны решать проблемы совместимости между виджетами и DOM или CSS.

Назад к началу: действительно ли мы хотим интегрировать Flutter и Web?

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

Во-первых, официальное позиционирование Google Flutter неоднозначно. Когда они впервые разработали Flutter, они не учитывали веб-экосистему или даже намеренно избегали ее. Вместо этого команда выступала за более естественный метод разработки. Причина, по которой я сказал, что обе экосистемы не должны быть связаны в начале статьи, проста: концепции дизайна двух технологий разные, и они не движутся в одном направлении. Их экосистемы и технические решения не связаны между собой, поэтому принудительная интеграция, скорее всего, приведет к потере преимуществ обеих экосистем. Тем не менее, многие команды в отрасли пытаются сделать именно это. Это показывает, что существует реальная потребность в решении. Однако если Google будет сопротивляться этой тенденции, будет сложно найти удовлетворительный подход. Тем не менее, Flutter для Интернета теперь официально поддерживается, что знаменует собой шаг ближе к веб-экосистеме. Также возможно, что Google упростит интеграцию с Интернетом в будущем.

Еще одна трудность, с которой мы сталкиваемся в наших попытках интегрировать эти экосистемы, — это сама кроссплатформенная технология. Браузеры разрабатывались 20 или 30 лет и уже являются очень мощными кроссплатформенными продуктами. На самом деле, они почти синонимы Интернета. Однако они также раздуты и несут в себе массу исторических проблем. Например, они не обеспечивают удовлетворительной производительности или взаимодействия с пользователем и имеют плохую совместимость с собственными приложениями, особенно на мобильных платформах и платформах информации вещей (IoT). Хотя производительность оборудования постоянно улучшается, это не относится к программному обеспечению. Производительность и опыт, предоставляемые браузерами, всегда хуже, чем у нативных страниц. Этот разрыв дает возможность для новых предприятий и сценариев. Если мы посмотрим на бизнес-сценарии, созданные в последние годы, мы увидим, что многие из них, такие как искусственный интеллект (ИИ), дополненная реальность (AR), потоковое видео и потоковое вещание, становятся популярными только после использования преимуществ нового «родные» возможности, предоставляемые Android и iOS. Есть ли бизнес-модель, ожидающая появления новых веб-API?

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

Хотите узнать о последних технологических тенденциях в Alibaba Cloud? Услышьте это от наших ведущих экспертов в недавно запущенной серии Tech Show!

Оригинальный источник: