Последний пост 2019 года, рассказ об опыте разработки и оптимизации приложений Electron за последние полгода. Есть также много галантерейных товаров, я надеюсь вдохновить вас.
Во второй половине года мы можем придумать проект, о котором можно было бы рассказать.Предполагается, что мы использовали Electron для реконструкции десктопного приложения. Это приложение похоже на钉钉
или企业微信
, основные функции — обмен мгновенными сообщениями, голос/видео, конференц-связь, основные функции и интерактивный опыт аналогичны WeChat на стороне ПК (по сути, это имитация), конкретные детали не будут раскрываться, они не важны для эта статья. Как показано ниже
Схема статьи
- Почему выбирают Электрон?
- модель процесса
- Выбор технологии и организация кода
- Оптимизация производительности (товары длительного пользования)
- все равно будут дырки
- Расширенная информация
Почему выбирают Электрон?
Причина тоже очень проста:Наше приложение должно быть совместимо с несколькими платформами, эффективность собственной разработки низкая, и у нас нет ресурсов..
Сказать это то же самое, что сказать это напрасно, большинство мотивов выбора фреймворка Electron схожи, не более чем нищета, особенно для компаний, которые выживают в щелях.
Чтобы оптимизировать ресурсы разработки клиента,«Гибридизация» — тема нашего рефакторинга на стороне клиента в этом году..
Давайте взглянем на нашу текущую базовую архитектуру клиента:
Гибридизация означает для нас две вещи:
- Архитектура нашего приложения «смешивает» несколько технологий. Общий базовый C/C++, нативная платформа (iOS, Android, ПК, MacOS), веб-технологии
- Кроссплатформенность
Исходя из нашей исходной клиентской базы и ситуации, гибридная реконструкция естественно делится на два направления:
- бизнес тонет. Мойка общего и основного бизнеса. Например, основные модули, такие как обработка сообщений, голос/видео, конференция, хранение данных и т. д., основные протоколы — XMPP, SIP. Эти модули изменяются реже, предъявляют более высокие требования к производительности и кроссплатформенности, поэтому подходят для реализации на C/C++.
- Смешивание пользовательского интерфейса. Также существует множество решений для гибридизации слоев представления, таких как Electron, React Native, Flutter или HTML Hybrid. Мы решили начать с Electron, потому что он имеет очень зрелую производительность в настольной разработке, и на рынке есть много крупных приложений Electron, таких как VSCode, Atom и Slack. Что касается мобильных устройств, мы консервативны с React Native и Flutter и, возможно, попробуем их в будущем.
Поняв нашу мотивацию, теперь посмотрите на картинку выше, она должна быть более понятной, это типичная трехуровневая структура, очень похожая на MVC:
- M -- общий слой смешения. C/C++ инкапсулирует ядро, общие бизнес-модули и хранилище бизнес-данных.
- V — уровень пользовательского интерфейса. Уровень представления, использующий кросс-платформенное решение представления, использует собственные реализации для частей с высокими требованиями к производительности. такие как Электрон
-
C -- уровень платформенного моста. между M и V, перемычка
通用混合层
интерфейс, но также выставить некоторые для слоя пользовательского интерфейсаСвязанные с платформойхарактеристики. Например, на стороне рабочего стола уровень гибридного приложения будет объединен с собственным модулем Node, а также будут добавлены некоторые отсутствующие или несовершенные функции Electron.
модель процесса
Модель процесса «ведущий-ведомый» в Electron основана на здравом смысле. Каждое приложение Electron имеет только один основной процесс и один или несколько процессов визуализации, соответствующих нескольким веб-страницам. В дополнение к этому есть процессы GPU, процессы расширения и так далее. в состоянии пройтиElectron Application ArchitectureУзнайте о базовой архитектуре Electron.
Основной процесс отвечает за создание окон страниц, координацию взаимодействия между процессами и распределение событий. Из соображений безопасности к собственным API-интерфейсам, связанным с графическим интерфейсом, нельзя получить прямой доступ в процессе рендеринга, и они должны вызывать основной процесс через IPC.Недостаток этой модели процесса «ведущий-подчиненный» также очень очевиден, то есть единственная точка отказа главного процесса. Основной процесс аварийно завершает работу или блокируется, что повлияет на реакцию всего приложения.. Например, основной процесс, выполняющий долгосрочные задачи ЦП, будет блокировать события взаимодействия с пользователем в процессе рендеринга.
Для нашего приложения в настоящее время существуют следующие процессы и их обязанности:
① основной процесс
- Межпроцессное взаимодействие, управление окнами
- Глобальная общая служба.
- Что-то, что можно или можно сделать только в основном процессе. Например, загрузка браузера, глобальная обработка горячих клавиш, трей, сессия.
- поддерживать некоторое необходимое глобальное состояние
- сказано выше
通用混合层
Также запустите этот процесс. Откройте интерфейс через подключаемый модуль Node C++.
② Процесс рендеринга
Отвечает за отображение веб-страниц и бизнес-обработку определенных страниц.
③ Сервисный работник
Отвечает за кэширование статических ресурсов. Кэшируйте некоторые сетевые изображения и аудио. Обеспечить стабильную загрузку статических ресурсов.
Выбор технологии и организация кода
Расскажите о нашем выборе технологий.
- Фреймворк пользовательского интерфейса -
React
- Государственное управление -
Mobx
- глобализация -
i18next
- Пакет -
自研 CLI
организация исходного кода
bridge/ # 桥接层代码
resources/ # 构建资源,以及第三方DLL
src/
main/ # 🔴主进程代码
services/ # 📡**通过 RPC 暴露给渲染进程的全局服务**
tray.ts # 托盘状态管理
shortcut.ts # 全局快捷键分发
preferences.ts # 用户配置管理
windows.ts # 窗口管理
screen-capture.ts # 截屏
bridge.ts # 桥接层接口封装
context-menu.ts # 右键菜单
state.ts # 全局状态管理, 保存一些必要的全局状态,例如主题、当前语言、当前用户
...
lib/ # 封装库
bridge.ts # 桥接层API 分装
logger.ts # 日志
...
bootstrap.ts # 启动程序
index.ts # 🔴入口文件
renderer/ # 🔴渲染进程
services/ # 📡主进程的全局服务的客户端
windows.ts # 窗口管理客户端
tray.ts
...
assets/ # 静态资源
hooks/ # React Hooks
components/ # 通用组件
Webview
Editor
toast
...
pages/ # 🔴页面
Home
ui/ # 🔴视图代码,由前端团队维护
store/ # 🔴状态代码,由客户端团队维护,前端Store的公开状态
translation/ # 国际化翻译文件
index.tsx # 页面入口
Settings
Login
page.json # 🔴声明所有页面及页面配置。类似小程序
Внимательные читатели заметят, что под каждой страницей естьui
а такжеstore
Каталоги, соответствующие представлениям и состояниям соответственно. Почему так разделились?
Прежде всего потому, что этот проект разрабатывается совместно двумя командами: изначальной клиентской командой и нашей фронтенд-командой. Разделение представления и состояния имеет два преимущества:
- Внешний интерфейс не должен заботиться о базовом бизнесе клиента на ранней стадии, а клиенту не нужно заботиться о реализации внешнего интерфейса. Обязанности ясны, и каждый занимается своим делом.
- Сократить расходы на обучение. Мы выбираем Mobx для управления состоянием.Для студентов-клиентов им нужно лишь немного освоить язык Typescript, чтобы сразу приступить к работе. Если вы знакомы с Java и C#, то проблем нет. Каждый магазин — это просто класс:
class CounterStore extends MobxStore {
@observable
public count: number = 0
@action
public incr = () => {
this.count++
}
private pageReady() {
// 页面就绪,可以在这里做一些准备工作
// 事件监听
// addDisposer 将释放函数添加到队列中,在页面退出时释放
this.addDisposer(
addListener('someevent', evt => {
this.dosomething(evt)
})
)
// ...
this.initial()
}
private pageWillClose() {
// 页面释放,可以在这里做一些资源释放
releaseSomeResource()
}
// ....
}
Используя Mobx в качестве управления состоянием, объектно-ориентированное мышление для них лучше, чем Redux. В этом сценарии простота является правдой;
Состояние и бизнес-логика разделены, а реализация страницы интерфейса также упрощена.Вид — это просто карта состояния, что упрощает поддержку и повторное использование наших страниц и компонентов.
Оптимизация производительности (товары длительного пользования)
После того, как прелюдия закончена, некоторые оптимизации производительности Electron являются основным моментом этой статьи.
Электрон не серебряная пуля, у вас не может быть и того, и другого. Electron повышает эффективность разработки, но у него также есть много недостатков, таких как высокое использование памяти, на которое часто жалуются, и отличия в производительности от собственных клиентов. Мы также проделали большую работу по оптимизации приложений Electron.
Оптимизация производительности обычно делится на два этапа:
- ① Анализировать и выявлять проблемы. Ссылаться наИзмерение и анализ производительности React
- ② Решите проблему в соответствии с проблемой. не более трех направлений, см.«О направлении оптимизации производительности React»
1. Анализ производительности
Лучший инструмент анализа — Chrome DevTools.Performance
. Через график пламени можно интуитивно увидеть любой след процесса выполнения JavaScript.
Для основного процесса после включения отладки также можно передатьProfile
Инструмент собирает информацию о выполнении JavaScript.
Если вы хотите проанализировать процесс выполнения определенного фрагмента кода, вы также можете создать файл анализа с помощью следующей команды, а затем импортировать его в Chrome Performance для анализа:
# 输出 cpu 和 堆分析文件
node --cpu-prof --heap-prof -e "require('request’)”“
2. Стратегия оптимизации
2.1 Продолжаем бороться с белым экраном
Несмотря на то, что Electron обычно загружает код JavaScript из локальной файловой системы, без задержек загрузки сети, нам все равно нужно продолжать бороться с белым экраном страницы, потому что загрузка, синтаксический анализ и выполнение таких ресурсов, как JavaScript, по-прежнему требует значительных затрат (см. кThe cost of JavaScript in 2019). В качестве настольного приложения пользователи могут почувствовать небольшую задержку белого экрана. Мы стараемся сделать так, чтобы пользователь не заметил, что это веб-страница.
Основными факторами, влияющими на белый экран Electron, являются: создание окон страниц, загрузка статических ресурсов, парсинг и выполнение JavaScript..
Смотрите хитрости, мы сделали эти оптимизации для белого экрана страницы:
① Скелетный экран
Самый простой способ. Перед загрузкой ресурсов сначала отображается скелет страницы. Избегайте того, чтобы пользователи видели пустой экран.
Кроме того, вам нужно установить цвет фона или задержать отображение окна, чтобы избежать мерцания.
Скелетный экран VSCode② Ленивая загрузка
Основные функции загружаются первыми, чтобы обеспечить эффективность начальной загрузки, чтобы пользователи могли взаимодействовать как можно скорее.
-
разделение кода + предварительная загрузка: Разделение кода — наиболее распространенная оптимизация. Мы выделяем скрытый контент или вспомогательные модули и сохраняем только критический путь в модуле запуска. Мы также можем предварительно загружать эти модули, когда браузер бездействует.
-
Ленивая загрузка модулей Node.: Модули Nodejs загружаются и выполняются с большими затратами, такими как поиск модуля, чтение файла модуля, а затем синтаксический анализ и выполнение модуля. Все эти операции синхронизированы, не забывайте, черная дыра node_modules, модуль может ссылаться на большое количество зависимостей....
Приложения Node отличаются от приложений Electron.Обычно серверные приложения Node размещают модули в верхней части файла, а затем загружают их синхронно. Это невыносимо в интерфейсе Electron. Скорость запуска и интерактивная блокировка пользовательского интерфейса могут быть восприняты пользователем, и уровень допуска будет низким.
Так что полностью оцените размер и зависимости модуля. Или, при желании, оптимизируйте и объедините модули Node с помощью инструментов упаковки.
-
Приоритет загрузки: поскольку мы не можем загрузить все сначала, мы будем загружать их постепенно в соответствии с приоритетом. Например, когда мы используем VSCode для открытия файла, VSCode сначала отобразит панель кода, а затем дерево каталогов, боковую панель, подсветку кода, панель проблем, инициализацию различных плагинов...
③ Используйте современный код JavaScript/CSS.
Каждая версия Electron поставляется с предустановленной последней версией Chrome на тот момент, что является самой крутой вещью для внешнего интерфейса:
- Используйте новейшие функции JavaScript без лишних хлопот
- Нет полифилла, нет помощника во время выполнения. Меньше кода и лучшая производительность, чем в старых браузерах
- Нам нужно активно отбрасывать некоторые старые зависимости. продолжайте использовать последнюю библиотеку
④ Оптимизация упаковки
Инструменты упаковки полезны даже в самых последних и лучших браузерах.
- уменьшить размер кода: Современные инструменты упаковки имеют множество методов оптимизации, таких как расширение области поддержки Webpack, встряхивание дерева и сжатие кода, предварительное выполнение... Это может объединять код, уменьшать размер кода, обрезать избыточный код и снижать нагрузку во время выполнения.
- Оптимизация ввода/вывода: После того, как мы объединим модули, мы сможем сократить циклы ввода-вывода для поиска и загрузки модулей.
У Atom есть много отличных статей, в которых они делятся своим опытом оптимизации Atom. Например, они используютСнимок V8 для оптимизации времени запуска.
ЭтоAOT
Стратегия оптимизации, короче говоря, Snapshot — это моментальный снимок кучи, вы можете думать о нем как о представлении в памяти кода JavaScript в V8.
У него есть два преимущества: во-первых, он загружается быстрее, чем обычный JavaScript, а во-вторых, он двоичный.Если вы «безопасны», вы можете преобразовать модуль в снимок, который труднее «взломать».
Однако он также имеет больше ограничений. Воздействие на структуру относительно велико. Например, требуется, чтобы во время инициализации не было «побочных эффектов», таких как доступ к DOM. Потому что во время компиляции эти вещи не существуют.
В этой статье подробно описано, как применять моментальные снимки v8 в Electron:How Atom Uses Chromium Snapshots
Более широко используемым решением являетсяv8 Code Cache. NodeJS 12НачинатьСоздавайте кэши кода для встроенных библиотек заблаговременно во время сборки, что сокращает время запуска на 30 %.
Подробно изучите расширение Code Cache, прочитав следующие статьи:
- Code caching for JavaScript developers
- JavaScript Start-up Performance
- Improved code caching
- Как ускорить запуск вашего приложения Node.js
⑥ Предварительный обогрев окон и оконный бассейн, постоянный подогрев окон
Чтобы догнать скорость открытия и отображения нативных окон, мы использовали множество уловок, чтобы обменивать пространство на время.
Например, на домашней странице нашего приложения, когда пользователь открывает страницу входа, мыФоновая разминка, загруженные ресурсы готовы, и после успешного входа в систему отображение может быть немедленно отображено. Задержка открытия окна очень короткая, что в основном близко к родному опыту работы с окном.
Здесь используются некоторые методы Hack, мы убираем эти окна с экрана и устанавливаемskipTaskBar
Для достижения эффекта скрытия или закрытия.
Для часто открываемых/закрывающихся окон также можно использоватьокно бассейноптимизировать. Например, для страницы веб-просмотра, когда страница веб-просмотра открывается, она сначала будет выбрана из пула окон. Когда пул окон пуст, будет создано новое окно. После закрытия страницы оно будет возвращено на место. в пул окон для последующего повторного использования.
Кроме того, для бизнес-независимых и общих окон вы также можете использоватьрезидентный режим, такие как уведомления, средства просмотра изображений. Эти окна не освобождаются после создания и лучше открываются.
⑦ Следите за последней версией Electron
Держите версию обновленной.
2.2 Догнать родной интерактивный опыт
Оптимизация времени белого экрана — это только начало, и интерактивный опыт во время использования приложения также является очень важной частью. Вот некоторые из наших оптимизаций:
① Кэш статических ресурсов
Для некоторых сетевых ресурсов мы предприняли некоторые меры кеширования, чтобы обеспечить скорость их отображения. В настоящее время мы используем метод Service-Worker + Workbox. С помощью Service-Worker мы можем перехватывать сетевые запросы с нескольких страниц, чтобы добиться межстраничного статического кэширования ресурсов. Этот метод относительно прост в реализации.
Помимо Service Worker, его также можно реализовать через перехват протокола. Видеть:protocol. У меня есть время, чтобы попробовать это позже, чтобы увидеть, как это работает.
② Механизм предварительной загрузки
если ты видел мой«Возможно, это самый популярный способ открыть React Fiber (с разделением времени)»., должны увидетьrequestIdleCallback
Сила React использует его для планирования некоторых задач рендеринга, чтобы гарантировать, что браузер реагирует на действия пользователя.
Этот API также важен для оптимизации нашего приложения. Благодаря этому мы можем узнать об использовании ресурсов браузера и использовать время простоя браузера для предварительного выполнения некоторых задач с низким приоритетом. Например:
- Визуализация скрытой вкладки
- Ленивая загрузка кода модуля
- ленивая загрузка изображений
- неактивный сеанс
- Выполнение низкоприоритетных задач
- ...
Например, разделение кода React:
export default function lazy(factory, Fallback) {
const Comp = l(factory)
// 预加载调度
scheduleIdle({
name: 'LazyComponent',
size: TaskSize.Heavy,
task: factory,
timeout: 2000,
})
return function LazyComponent(props) {
return (
<Suspense fallback={Fallback ? <Fallback /> : null}>
<Comp {...props} />
</Suspense>
)
} as typeof Comp
}
использовать:
const List = lazy(() => import('./List'))
③ Избегайте синхронной работы
Electron может выполнять операции ввода-вывода через NodeJS, но мы должны стараться избегать синхронного ввода-вывода. Например, синхронные файловые операции, синхронное межпроцессное взаимодействие. Они блокируют отрисовку страницы и взаимодействие с событиями.
④ Уменьшите нагрузку основного процесса
Основной процесс Электрона очень важен. Это родительский процесс для всех окон, и он отвечает за планирование различных ресурсов. Если основной процесс заблокирован, это повлияет на скорость отклика всего приложения.
Вы можете провести простой эксперимент, поставить точку останова в основном процессе, и вы обнаружите, что все окна страниц перестают отвечать на запросы, даже если они находятся в отдельных процессах. Это связано с тем, что все взаимодействия с пользователем распределяются основным процессом на процесс рендеринга.Если основной процесс заблокирован, процесс рендеринга не может получать пользовательские события.
Так что не позволяйте основному процессу делать грязную работу, то, что можно сделать в процессе рендеринга, просто делайте это в процессе рендеринга.Не запускайте ресурсоемкие задачи и синхронный ввод-вывод в основном процессе..
⑤ Разделите операции, интенсивно использующие ЦП, на отдельные процессы или рабочие процессы, чтобы избежать блокировки пользовательского интерфейса.
⑥ Реагировать на оптимизацию
Видеть«Направление оптимизации производительности React»
⑦ Откажитесь от CSS-in-js
Чтобы снизить производительность во время выполнения, мы можем делать во время компиляции то, что мы можем делать во время компиляции, отказаться от решения CSS-in-js и использовать чистый CSS + БЭМ для написания стилей. Есть две основные причины:
- Electron использует более новый Chrome, современный CSS уже мощнее
- Мы используем механизм прогрева окна, который может первым разобрать эту часть кода CSS. Решение CSS-in-js создается динамически при рендеринге компонента.
⑧ Если выхода нет, то можно использовать только нативный модуль Node.
Отлично, выход есть
2.3 Оптимизация процесса коммуникации
Когда дело доходит до приложений Electron с несколькими страницами/окнами, IPC будет очень частым, и это может стать узким местом в производительности.
① Не злоупотребляйте удаленным
remoteОбеспечивает простую, ненавязчивую форму доступа к API и данным основного процесса.Его базовый уровень основан на синхронном IPC.. ты можешь пройти мимо меняэта статьячтобы понять, как это работает.
Где яма?
① Это синхронно ② Динамическое приобретение атрибутов. Чтобы убедиться, что вы можете получить самое последнее значение, удаленный нижний слой не будет кэшироваться, а будет динамически извлекаться из основного процесса каждый раз при получении атрибута.
Например, чтобы получить объект в основном процессе:
// 主进程
global.foo = {
foo: 1,
bar: {
baz: 2
}
}
Доступ к процессу визуализации:
import {remote} from 'electron'
JSON.stringify(remote.getGlobal('foo'))
Это вызовет 4 IPC синхронизации: getGlobal, foo, bar, bar.baz. Для сложных данных такое потребление невыносимо.
Избегайте использования пульта дистанционного управления, если вы не знаете, что делаете.
② Упаковать библиотеку IPC
Чтобы оптимизировать связь IPC, мы инкапсулируем собственный набор библиотек RPC на основе интерфейса IPC Electron. Основные особенности:
- Асинхронный. Нет возможности синхронизировать. избегать глупых поступков
- Объединение сообщений. Слияние push-уведомлений, пакетная доставка
- Сериализация. Передайте строку JSON напрямую, не позволяя Electron вмешиваться в сериализацию. Внутренняя сериализация Electron немного сложнее, например, она обрабатывает специальные типы, такие как Buffer.
- Последовательный, простой в использовании API. Использование одного и того же интерфейса поддерживает основной процесс и процесс рендеринга, а также двустороннюю связь между процессом рендеринга и процессом рендеринга.
Например:
import rpc from 'myrpc'
// 注册方法
rpc.registerHandler('echo', async data => {
return data
})
// 事件监听
rpc.on('some-event', (data, source) => {
// dosomething
})
Клиент:
import rpc from 'myrpc'
rpc.emit(target, 'some-event') // target 为接收的窗口或者主进程。
// 方法调用
const res = await rpc.callHandler(target, 'echo', 'hello-world')
Этого недостаточно, мы все еще оптимизируем его и поделимся с вами позже.
все равно будут дырки
По дороге было много ям. Боль и счастье.
- Оконные тени, закругленные углы
- Буфер обмена недостаточно силен
- некоторые проблемы с совместимостью
- Сбой основного процесса, процесс рендеринга не завершается, что приводит к «переполнению» процесса.
- Снимок экрана. Сначала это было реализовано с помощью Electron, и эффект был не очень хорошим, теперь это реализовано нативно.
- ...
Расширенная информация
- Глядя на техническую архитектуру больших IDE от VSCode
- Electron Performance
- CovalenceConf 2019: Visual Studio Code — первая секунда
- Get Started With Analyzing Runtime Performance
- electron-link
- How to Create a V8 Heap Snapshot of a Javascript File and Use It in Electron
- The State of Atom's Performance
- Improving Startup Time
Отвечать:
ivan
в группу