Лекция 4. Кроссплатформенная разработка
Один код - несколько платформ: подходы, инструменты, компромиссы
План лекции
Основы
- Зачем нужна кроссплатформенность
- Спектр переиспользования кода
- Базовые понятия: рендеринг, мост, JIT/AOT
Семейства технологий
- PWA и веб-подход
- Гибридные (WebView) решения
- Нативный рендеринг (RN, NativeScript)
- Собственный движок (Flutter)
- Общая логика (KMP, .NET MAUI)
Инженерные аспекты
- Производительность и размер
- Доступ к нативным API
- Тестирование и CI/CD
- Безопасность и обновления
Выбор технологии
- Сводное сравнение
- Дерево решений
- Тренды 2026
Рынок мобильных ОС
- Android - около 70-72% мирового рынка
- iOS - около 28-30%, но в США и ряде стран Западной Европы доля выше 50%
- HarmonyOS (Huawei) - стремительный рост в Китае, вытесняет Android-без-Google-сервисов
- Нишевые ОС: KaiOS (кнопочные), Аврора (Россия), Wear OS, tvOS
Фрагментация Android
- Тысячи моделей устройств: разные экраны, плотности, процессоры
- Версии ОС: значительная доля пользователей на Android 10-13, свежие версии догоняют годами
- Вендорские прошивки (One UI, MIUI/HyperOS): свои настройки энергосбережения и уведомлений
- Следствие: нужно тестировать на реальном "зоопарке" устройств, а не только в эмуляторе
iOS-фрагментация почти отсутствует: новые версии принимаются быстро, моделей мало.
Проблема двух кодовых баз
- Две команды: Kotlin/Java и Swift/Objective-C
- Двойной объем тестирования, релизов, документации
- Функция, разработанная "за месяц", выходит на второй платформе позже
- Рост стоимости поддержки (TCO) со временем нелинейный
- Сложнее удерживать единообразие дизайна и логики
Идея кроссплатформенности: написать один раз - запускать везде, разделив общую часть и платформенную специфику.
Спектр переиспользования кода
Чем больше общего кода, тем выше экономия, но ниже контроль над платформой.
Раздельные коды - нативные приложения, общий только бэкенд
Общая логика - KMP / C++ / Rust ядро, UI нативный
Общая логика + UI через мост - React Native, .NET MAUI
Собственный движок UI - Flutter, NativeScript
Полностью общий код (веб) - PWA, Cordova/Ionic
Преимущества и недостатки
Преимущества
- Одна кодовая база и одна команда
- Быстрее выход на рынок (time to market)
- Ниже стоимость разработки и поддержки
- Единый дизайн и логика на всех платформах
- Общие инструменты, тесты, CI/CD
Недостатки
- Производительность ниже на тяжелых задачах
- "Почти нативный" вид, не всегда 1:1 с платформой
- Задержка поддержки новых API ОС
- Больше размер приложения
- Зависимость от вендора фреймворка
Экономика выбора
| Фактор | Нативная разработка | Кроссплатформенная |
| Размер команды (MVP, 2 платформы) | 3-6 инженеров | 1-3 инженера |
| Сроки первой версии | базовые | в 1,5-2 раза быстрее |
| Стоимость поддержки | растет линейно от числа платформ | растет слабее |
| Найм специалистов | 2 профиля (Kotlin + Swift) | 1 профиль (JS, Dart, C#) |
| Потолок производительности | максимальный | обычно достаточный |
Для большинства бизнес-приложений экономия перевешивает потери производительности; для игр и AR/VR - нет.
Критерии выбора технологии
Продуктовые
- Сложность UI и анимаций
- Нужны ли магазины приложений
- Целевые платформы (только mobile? desktop? web?)
Командные
- Есть ли опыт в JS, Dart, C#?
- Размер и бюджет команды
- Готовность поддерживать нативный код
Технические
- Требуемые нативные API и железо
- Требования к размеру и старту
- Долгосрочная поддержка
Организационные
- Риск зависимости от вендора
- Сообщество и зрелость
- Наличие экспертизы в компании
Три способа нарисовать пиксели
Нативные представления (views). Фреймворк создает реальные Android View / iOS UIKit-элементы. Так работают React Native, NativeScript, .NET MAUI. Нативный вид и поведение, но каждый вызов идет через границу языков.
Собственный рендеринг. Фреймворк сам рисует на Canvas, не используя нативные элементы. Так работает Flutter (Impeller). Полный контроль над внешним видом и одинаковый результат везде, но нет "нативности" из коробки.
Веб-рендеринг. UI строится браузерным движком (WebView) из HTML/CSS. Так работают Cordova, Ionic, PWA. Максимальная простота, но производительность и доступ к API ограничены.
Мост (Bridge) и JSI
Язык разработки (JS, Dart) и платформа (Kotlin/Swift) - разные миры. Их связывает прослойка.
Классический Bridge
- Асинхронные сообщения через очередь
- Данные сериализуются в JSON
- Задержка на каждом вызове
- Просто, но медленно при большом потоке
Современный JSI / FFI
- Прямой синхронный доступ к нативным объектам
- Без сериализации
- Минимальные задержки
- Основа новой архитектуры RN и Dart FFI
FFI (Foreign Function Interface) - общий принцип вызова "чужого" кода без прослойки-обертки.
JIT и AOT компиляция
JIT (Just-in-Time)
- Компиляция во время работы
- Быстрый цикл разработки, hot reload
- Медленнее старт и пиковая производительность
- Пример: Dart в debug, JS-движки
AOT (Ahead-of-Time)
- Компиляция заранее, при сборке
- Быстрый старт и высокая производительность
- Дольше сборка, hot reload недоступен
- Пример: Dart в release, .NET AOT, Kotlin/Native
На iOS сторонний JIT запрещен - код должен быть скомпилирован заранее (AOT), что объясняет различия поведения фреймворков.
Горячая перезагрузка (Hot Reload)
- Изменения в коде видны на устройстве без потери состояния
- Ускоряет разработку UI в разы: цикл "правка → результат" занимает секунды
- Есть в Flutter (sub-second), React Native (Fast Refresh), .NET MAUI
- Важнейший практический фактор выбора - спросите не "быстро ли", а "насколько быстро итерация"
PWA: три столпа
- Устанавливаемость - иконка на главном экране через Web App Manifest, без магазина
- Надежность - Service Worker кэширует ресурсы и обеспечивает работу offline
- Возможности - push-уведомления, фоновая синхронизация, доступ к части нативных API
PWA - обычный сайт, который умеет вести себя как приложение. Основное отличие от гибрида: запускается в браузере, а не в оболочке.
PWA: Service Worker
- Фоновый скрипт-посредник между страницей и сетью
- Перехватывает все сетевые запросы (fetch) и решает: отдать из кэша или из сети
- Не имеет доступа к DOM, работает в отдельном потоке
- Живет независимо от страницы - продолжает работать при закрытой вкладке
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(r => r || fetch(event.request))
);
});
Вместо ручного кэша на практике используют библиотеку Workbox: она дает готовые стратегии и интеграцию со сборщиками.
PWA: стратегии кэширования
| Стратегия | Логика | Для чего |
| Cache First | сначала кэш, при промахе - сеть | статика, шрифты, иконки |
| Network First | сначала сеть, при ошибке - кэш | API, динамический контент |
| Stale While Revalidate | отдать кэш и обновить в фоне | часто меняющиеся данные |
| Only Cache / Only Network | жесткий выбор источника | offline-страницы, аналитика |
Выбор стратегии под каждый тип ресурса - ключ к быстрой и предсказуемой PWA.
PWA: Web App Manifest
JSON-файл, описывающий приложение для операционной системы.
| Поле | Назначение |
name / short_name | полное и краткое имя на иконке |
start_url / scope | точка входа и область "приложения" |
display | standalone - без адресной строки браузера |
theme_color / background_color | цвет статус-бара и заставки |
icons | набор иконок, включая maskable |
share_target | прием данных из меню "Поделиться" ОС |
PWA: современные возможности
Push и Badge
Уведомления и значок с числом на иконке (iOS - поддержка с 16.4)
Background Sync
Отправка данных при восстановлении связи, даже после закрытия вкладки
File System Access
Работа с локальными файлами, ограниченно на iOS
WebAssembly
Высокопроизводительный код (C/C++/Rust) прямо в браузере
PWA: преимущества и ограничения
Преимущества
- Не нужен магазин приложений
- Обновляется автоматически
- Минимальный размер
- Индексируется поисковиками
- Единый код для всех платформ
Ограничения
- Ограниченный доступ к API ОС
- Разная поддержка на iOS и Android
- Нет в App Store / Google Play
- Заметно на тяжелой графике и анимациях
- Зависимость от браузера пользователя
PWA: где применять
- Каталоги, лендинги, медиа-сайты, интернет-магазины
- Внутренние инструменты (без распространения через магазины)
- Быстрый MVP и проверка спроса до вложений в нативные приложения
- Аудитории в регионах с медленным интернетом (offline-first критичен)
PWA - самый дешевый способ "быть на телефоне пользователя", но не полноценная замена магазинному приложению.
WebView: что это
- Встроенный браузерный компонент внутри нативного приложения
- Показывает HTML/CSS/JS, но живет в нативном контейнере
- Платформа дает компонент, который обычно в разы мощнее системного браузера по контролю
- Применяется в гибридных приложениях и для показа веб-контента (платежные формы, справка, виджеты)
Android: WebView (системный, на Chromium)
iOS: WKWebView (на WebKit)
Идея: HTML/CSS/JS + прослойка к нативным плагинам
Cordova: история и устройство
- Появился в 2009 как PhoneGap, позже передан Apache
- Ядро - приложение на HTML/CSS/JS внутри WebView
- Плагины дают доступ к камере, геолокации, файлам, контактам
- Платформы: Android, iOS, Windows
- Сейчас считается устаревающим, уступил Capacitor
Пользовательский интерфейс - это веб-страница, упакованная в APK/IPA вместе с движком.
Ionic и Capacitor
- Ionic - библиотека готовых UI-компонентов под Android/iOS, работает с Angular, React или Vue
- Capacitor - современная замена Cordova от той же команды
- Генерирует полноценные нативные проекты, к которым есть прямой доступ
- Поддерживает и веб-платформу (PWA) из той же кодовой базы
Cordova vs Capacitor
| Особенность | Cordova | Capacitor |
| Разработчик | Apache | Ionic |
| Нативный проект | скрыт в обертке | полноценный, доступен для правок |
| Плагины | cordova-plugin-* | @capacitor/*, можно и cordova-* |
| Live Reload | ограничен | полная поддержка |
| Веб/PWA | нет | да, из той же базы |
| Судьба | поддержка снижается | активно развивается |
Гибридный подход: итоги
- Плюсы: крайне низкий порог входа для веб-разработчиков, один код на mobile и web
- Минусы: производительность тяжелых экранов, сложность нативных плагинов, "веб-ощущение" интерфейса
- Хорошо подходит: формы, каталоги, корпоративные приложения, контентные сервисы
- Плохо подходит: игры, сложная анимация, работа с графикой и железом
.NET MAUI (наследник Xamarin)
- Язык C#, платформа
.NET
- Общая бизнес-логика и общий UI; при необходимости - прямой доступ к нативным API
- Проекты: Android, iOS, macOS, Windows из одной кодовой базы
- Исполняется на Mono/NativeAOT, вызывает нативный код через привязки
- Логичный выбор, если компания уже на .NET / C#
Xamarin.Forms vs .NET MAUI
| Особенность | Xamarin.Forms | .NET MAUI |
| Платформа .NET | старые версии | .NET 8 / 9+ |
| Структура проекта | отдельные проекты на платформу | единый проект |
| Desktop | нет | Windows, macOS |
| Производительность | средняя | выше, NativeAOT |
| Статус | прекращена (2024) | актуальная платформа |
NativeScript
- Язык: JavaScript / TypeScript, фреймворки Angular, Vue, Svelte
- Прямой доступ к нативным API без отдельной прослойки
- Нет WebView: интерфейс строится из настоящих нативных компонентов
- Ниша: небольшие команды, уже знающие JS/TS и желающие "нативности"
- Меньшее сообщество и экосистема по сравнению с React Native
React Native: обзор
- Язык: JavaScript / TypeScript, UI описывается декларативно (React/JSX)
- Строит интерфейс из реальных нативных компонентов
- Огромная экосистема npm и рынок труда
- Самый популярный кроссплатформенный стек для бизнес-приложений
const App = () => (
<View style={styles.box}>
<Text>Привет</Text>
<Button title="Нажми" onPress={handlePress} />
</View>
);
React Native: архитектура
- Классическая: JS-поток общается с нативным через асинхронный Bridge (сериализация сообщений - узкое место)
- Новая архитектура (по умолчанию с 0.76): избавляется от моста
JSI
Прямой синхронный вызов нативного кода из JS без сериализации
Fabric
Новый рендерер: синхронное дерево UI и конкурентные обновления
TurboModules
Ленивая загрузка нативных модулей и типобезопасная генерация кода
Итог: быстрее старт, плавнее анимации, меньше накладных расходов.
React Native: Expo
Managed (по умолчанию)
- Expo управляет нативной частью
- Быстрый старт, минимум конфигурации
- Богатый набор встроенных модулей
- OTA-обновления через EAS Update
Bare / prebuild
- Полный доступ к нативному коду
- Любые нативные SDK и библиотеки
- Нужны Xcode и Android Studio
- Сложнее сборка и обновления
Expo стал рекомендуемым способом начать проект на React Native.
React Native: навигация и состояние
Навигация (React Navigation)
- Stack - экран поверх экрана
- Tab - нижние вкладки
- Drawer - боковое меню
- Deep Linking - переходы по ссылкам
Состояние
- Встроенные хуки - для локального
- Zustand - минималистичный
- Redux Toolkit - крупные приложения
- Jotai/Recoil - атомарный подход
Отдельного "стандарта" нет: выбирайте по размеру приложения и опыту команды.
Flutter и другие экосистемы
Flutter: краткий обзор
- Создан Google, язык Dart
- Собственный движок рендеринга (Impeller вместо Skia) - не использует нативные элементы и WebView
- Одинаковый пиксель-в-пиксель вид на Android, iOS, Web, Windows, macOS, Linux
- Быстрый hot reload, богатый набор виджетов (Material и Cupertino)
- Детально - в лекциях 5-7.
Kotlin Multiplatform (KMP)
- Общая бизнес-логика на Kotlin для Android, iOS, desktop, web, сервера
- UI остается нативным: Jetpack Compose, SwiftUI, либо Compose Multiplatform
- Не заменяет Flutter/RN, а дополняет нативную разработку
- Логичный выбор, если команда уже пишет на Kotlin
Compose Multiplatform
- Декларативный UI на Kotlin от JetBrains, расширение Jetpack Compose
- Общий UI-код для Android, iOS, Desktop и Web (Wasm)
- Хорошо сочетается с KMP: общая и логика, и интерфейс
- Стабильность для iOS выросла к 2025 году, но экосистема меньше, чем у Flutter
Другие экосистемы
HarmonyOS (Huawei)
- Язык ArkTS, UI ArkUI
- Экосистема без Google-сервисов
- Магазин AppGallery
- Быстрый рост в Китае
Игры и 3D
- Unity (C#) - де-факто стандарт
- Godot - открытый движок
- Один код на mobile, desktop, консоли
- Отдельная категория со своими правилами
Производительность: где узкие места
- Старт приложения - размер бандла, инициализация движка
- Плавность анимаций - мост и лишние перерисовки на кадр
- Списки - виртуализация длинных списков (большие каталоги)
- Изображения и память - размеры, кэш, утечки
- Тяжелые вычисления - выносить в нативный код через FFI или изоляты
Производительность - это чаще про архитектуру и профилирование, чем про выбор фреймворка.
Доступ к нативным API
Готовые плагины
Камера, геолокация, уведомления, файлы - уже написаны сообществом
Нативные модули
Своя обертка на Kotlin/Swift вокруг SDK, доступная из основного языка
FFI / привязки
Прямой вызов C/C++/Rust библиотек с минимальными накладными расходами
Если нужен экзотический или свежий API - почти всегда придется писать нативный код самостоятельно.
Тестирование
- Юнит-тесты общей логики - самый большой выигрыш кроссплатформенности
- Тесты UI (widget/component) на уровне фреймворка
- Интеграционные и E2E на реальных устройствах (Appium, Maestro, Detox)
- Golden-тесты (скриншоты) для контроля визуальных регрессий
- Матрица устройств: эмуляторы + облачные фермы реальных устройств
CI/CD и публикация
- Единый конвейер: сборка → тесты → подпись → публикация
- Облачные сборки (EAS, Codemagic, GitHub Actions) снимают боль с macOS-раннеров
- Автоматизация версионирования и changelog
- Ревью в App Store строже, чем в Google Play; сроки публикации разные
- EU Digital Markets Act (2024) разрешила стороне установку на iOS - но только в ЕС
Обновления: OTA и магазины
Через магазин
- Полный контроль, но ревью и задержка
- Пользователь обновляет сам (или авто)
- Обязателен для смены нативного кода
OTA (over-the-air)
- Обновление логики и UI без магазина
- Мгновенный откат (rollback)
- Нельзя менять нативный код и нарушать правила сторов
Безопасность и данные
- Хранить секреты в keystore/secure storage, а не в коде и не в открытом виде
- Проверять SSL-пиннинг для критичных API
- Осторожно с плагинами третьих сторон: они получают доступ к нативному слою
- Минимизировать разрешения и прозрачно объяснять их пользователю
- Учитывать GDPR/152-ФЗ при хранении персональных данных
Доступность и локализация
- Поддержка экранных читалок (TalkBack, VoiceOver), размеров шрифта
- Контрастность, крупные зоны нажатия, семантические метки
- Локализация: строки в ресурсах, учет RTL-языков, форматы дат и чисел
- Кроссплатформенные фреймворки дают основу, но часть работы - платформая
Сводная таблица
| Критерий |
PWA |
Ionic / Capacitor |
React Native |
Flutter |
.NET MAUI |
KMP |
| Язык | JS/TS | JS/TS | JS/TS | Dart | C# | Kotlin |
| Рендеринг | WebView | WebView | нативные views | свой движок | нативные views | нативный UI |
| Производительность | средняя | средняя | хорошая | высокая | хорошая | нативная |
| Нативный вид | нет | нет | почти | кастомный | почти | да |
| Размер приложения | минимум | средний | средний | средний+ | средний+ | средний |
| Магазины приложений | нет | да | да | да | да | да |
| Desktop / Web | web | web | web + desktop | все | все | все |
| Порог входа | низкий | низкий | низкий (если React) | средний (Dart) | средний (C#) | средний (Kotlin) |
| Сообщество | большое | среднее | очень большое | очень большое | среднее | растущее |
Скорость и стоимость
| Технология | Time to market | Команда | Итерация |
| Нативная (2 платформы) | самая долгая | 2+ команды | быстрая |
| Flutter | быстрая | 1 команда | sub-second hot reload |
| React Native | быстрая | 1 команда | Fast Refresh |
| Ionic / Capacitor | самая быстрая | 1 команда | hot reload / live reload |
| .NET MAUI | средняя | 1 команда | hot reload |
| PWA | мгновенное развертывание | 1 команда | автоматически |
Кроссплатформенные решения ускоряют выход на рынок примерно в 1,5-2 раза при прочих равных.
Размер приложения (ориентиры)
| Технология | "Hello World" | Типичное приложение | Примечание |
| Нативная | ~1 МБ | 5-20 МБ | минимум |
| Flutter | ~7 МБ | 15-30 МБ | движок внутри |
| React Native | ~7 МБ | 10-25 МБ | JS-движок и бандл |
| Ionic / Capacitor | ~5 МБ | 10-20 МБ | веб-контент |
| PWA | ~100 КБ | 1-5 МБ | без установки |
Чем больше "своего" рантайма везет приложение, тем крупнее бандл; размер можно уменьшать (split, tree shaking, AOT).
Дерево решений
Нужны магазины приложений и максимальная нативность?
└─ да ──> Нужны только Android и iOS?
├─ да ──> Есть опыт React/JS? -> React Native
│ Есть опыт C#/.NET? -> .NET MAUI
│ Есть опыт Kotlin? -> KMP + Compose MP
│ Иначе -> Flutter
└─ нет ──> Нужны desktop/web тоже?
└─ да ──> Flutter / KMP / Compose MP
Нужен быстрый MVP, магазин не нужен?
└─ да ──> PWA (или Ionic/Capacitor, если нужен магазин)
Сложная графика, игры, AR/VR?
└─ да ──> Нативная разработка / игровой движок
Матрица "когда что выбирать"
Нативная: игры, AR/VR, сложная графика, максимум производительности, экзотическое железо
Flutter: стартапы, кастомный дизайн, единый вид, выход на desktop/web
React Native: команда знает React, крупная экосистема, "почти нативный" вид
PWA: простые приложения, каталоги, минимальный бюджет, магазин не нужен
Ionic / Capacitor: есть веб-приложение, нужно быстро "завернуть" в mobile
.NET MAUI / KMP: команда уже в C# или Kotlin, нужна долгосрочная native-first стратегия
Тренды 2026
- Flutter: рост в desktop/web, зрелый Impeller, но вопрос о стратегии Google
- React Native: новая архитектура стала стандартом, Expo - рекомендуемый путь
- KMP и Compose Multiplatform: общая логика и общий UI все чаще в enterprise
- PWA и веб-возможности: новые API, WebAssembly, push на iOS
- Регуляторика: DMA в ЕС меняет правила сторонней установки на iOS
- AI-ассистенты: генерация UI и кода, автоперевод и тесты - ускоряют разработку во всех стеках
Ключевые выводы
- Кроссплатформенность - это спектр: от общей логики до общего UI
- Нет универсально лучшей технологии - есть подходящая под задачу и команду
- Выбор определяют: требуемые API, производительность, опыт команды, сроки, бюджет
- Основные игроки сегодня: Flutter, React Native, .NET MAUI, KMP, PWA/гибриды
- Начинайте с MVP (часто PWA), затем масштабируйте до нативного рендеринга при необходимости
- Помните о цене поддержки: один код ≠ бесплатная кроссплатформенность