Аудит в подарокАудит сайта в подарокдо 31 декабряПодробнее
Тема
Приложения
Дата
Спрос на вопрос
80 показов в месяц

Нативные плагины: когда гибрид упирается в потолок

Нативные плагины Capacitor на Swift и Kotlin: как они устроены, восемь примеров из практики, сколько стоит поддержка и когда пора уходить в чистый натив.

Потолок гибридного приложения наступает не там, где «не хватает производительности», а там, где нужен системный API, которого в вебе просто нет. Браузер не умеет зарегистрировать обработчик «Поделиться», не отдаст отпечаток пальца и не запустит фоновую обработку видео. В этот момент пишутся нативные плагины Capacitor: механизм для них встроен во фреймворк, класс на Swift и класс на Kotlin получают типизированный интерфейс в TypeScript, и на одну возможность обычно уходит 2-5 дней.

Архитектура от этого не разваливается. Приложение остаётся гибридным, 90% кода остаётся общим, а вниз, в платформу, спускается ровно та часть, которая иначе не решается. За два с половиной года владения приложением на iOS и Android я написал таких плагинов больше восьми.

Кроссплатформенная разработкаот 350 000 до 1 200 000 ₽сроки: 8-16 недель

Как устроен плагин

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

Класс на Swift. Наследует CAPPlugin, методы помечаются @objc и принимают CAPPluginCall. Внутри - обычный нативный код: AVFoundation, LocalAuthentication, PhotosUI, что нужно. Результат отдаётся через call.resolve(), ошибка через call.reject().

@objc(MediaPlugin)
public class MediaPlugin: CAPPlugin {
  @objc func compress(_ call: CAPPluginCall) {
    guard let path = call.getString("path") else {
      return call.reject("path is required")
    }
    // работа в фоновой очереди, resolve по завершении
  }
}

Класс на Kotlin. Наследует Plugin, методы помечаются @PluginMethod и принимают PluginCall. Отдельная боль - жизненный цикл активити: если плагин открывает системный экран (камеру, выбор файла), результат приходит в колбэк, и вызов надо сохранить через bridge.saveCall(), иначе после поворота экрана или убийства процесса вы получите потерянный промис.

Интерфейс на TypeScript. Объявляется тип, регистрируется через registerPlugin, и дальше веб-слой работает с обычной асинхронной функцией:

export interface MediaPlugin {
  compress(options: { path: string; bitrate?: number }): Promise<{ path: string; size: number }>;
}
export const Media = registerPlugin<MediaPlugin>('Media');

Всё, что ездит между слоями, сериализуется в JSON. Это накладывает главное ограничение проектирования: через мост нельзя гонять большие данные. Видео, фото и файлы передаются путями или идентификаторами, а не байтами. Если пытаться протащить base64 на 40 мегабайт, приложение упадёт по памяти - проверено.

Нативные плагины Capacitor, которые я писал

Share Extension на iOS и share-intent на Android. Самый сложный из всех. Задача: пользователь в любом чужом приложении жмёт «Поделиться» и отправляет фото или видео в наше. На iOS это отдельный таргет в Xcode с интерфейсом на SwiftUI, который живёт в собственном процессе и не имеет доступа к памяти основного приложения. Обмен идёт через App Group: расширение кладёт файлы в общий контейнер и пишет метаданные, основное приложение при следующем запуске их забирает. Плюс IPC, чтобы приложение узнало о новых данных, если оно уже запущено. На Android проще концептуально (intent-filter в манифесте), но всплывает своя история с временными URI и разрешениями на чтение чужого контента. Две недели работы, из них половина на пограничные случаи.

Обработка видео на FFmpeg. Обрезка, транскодирование, сжатие перед загрузкой. Пользователь снимает ролик на 200 мегабайт, а на сервер должно уехать 20. Всё считается на устройстве, чтобы не платить за трафик и серверные мощности. Отдельная возня с аппаратными кодеками: на разных Android-чипсетах поведение различается, и приходится держать программный запасной путь.

Поддержка HEIC. Айфоны снимают в HEIC, а половина мира его не читает. Плагин конвертирует в JPEG на устройстве с сохранением EXIF и ориентации. Мелочь, которая ломает загрузку фото у половины пользователей, если её не сделать.

Запись аудио и своя аудиоволна. Нативная запись с контролем формата и битрейта, отрисовка волны через wavesurfer в веб-слое. Гибридный случай в чистом виде: тяжёлое - в нативе, интерфейс - в вебе.

FaceID и TouchID. Вход и подтверждение операций через LocalAuthentication на iOS и BiometricPrompt на Android. Важная часть - не сама биометрия, а корректная работа с Keychain и Keystore: где лежит секрет, что происходит при добавлении нового отпечатка, как откатиться на пин-код.

Sign in with Apple. Обязателен, если в приложении есть вход через сторонние сервисы. Отдельная логика с тем, что почту Apple отдаёт один раз при первой авторизации, а дальше присылает только идентификатор. Если не сохранить её сразу, восстановить неоткуда.

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

Клавиатура и safe area. Скучный, но обязательный плагин: он сообщает веб-слою высоту клавиатуры и системные отступы. Без него на iOS поле ввода уезжает под клавиатуру, а кнопка внизу оказывается под полосой жестов.

Разрез приложения: приложение в сторах

Куда встраивается нативный плагин

Веб-слой общий, мост общий, а ниже под каждую платформу пишется свой код на Swift и Kotlin.

iOS

App Store, ревью 1-3 дня

Android

Google Play, ревью до суток

Одна сборка расходится на две площадки
02

Оболочка Capacitor

  • WebView
  • мост JS ↔ Native
  • сборка .ipa и .aab

Нативные плагины

  • камера и галерея
  • push-уведомления
  • геолокация
  • биометрия
  • файлы и шаринг
01

Один код Vue 3 + TypeScript

  • экраны и навигация
  • бизнес-логика
  • состояние
  • запросы к API

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

Когда плагин писать не надо

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

Порядок принятия решения у меня такой:

  1. Проверить, решается ли задача в вебе. Многое, что кажется нативным, давно в браузере: работа с файлами, геолокация, определение ориентации, доступ к камере для простых сценариев.
  2. Проверить официальные плагины Capacitor. Они поддерживаются вместе с ядром, а это половина проблемы.
  3. Проверить сообщество, но смотреть на дату последнего коммита. Плагин, который два года не обновлялся, сломается на первом же мажорном апдейте, и чинить его придётся вам.
  4. Только после этого писать свой.

Своя реализация оправдана, когда возможность критична для продукта и вы готовы её поддерживать. Плагин ради одной кнопки, которой пользуется полпроцента аудитории, - плохая сделка: написать его два дня, а тащить придётся годами.

Что стоит дорого

Не написание, а сопровождение.

Обновление Xcode и целевых SDK. Apple и Google регулярно поднимают минимальную версию SDK, под которую собирается приложение. Каждое такое поднятие - это пересборка всех нативных зависимостей и проверка, что ничего не отвалилось. Свежий Xcode может перестать собирать код, который компилировался вчера: изменились требования к синтаксису, устарел API, поменялись настройки подписи. Раз в год это гарантированно несколько дней работы, даже если в продукте ничего не менялось.

Разрешения и ревью App Store. Каждый новый системный доступ требует строки в Info.plist с человеческим объяснением: зачем вам микрофон, зачем фотогалерея, зачем контакты. Формулировка «для работы приложения» гарантированно получит отказ. Объяснение должно быть конкретным, и ревьюер проверяет, что заявленное совпадает с фактическим поведением. Доступ, который вы запросили «на будущее», - готовая причина отказа.

Расхождение поведения платформ. Один и тот же плагин на iOS и Android почти всегда ведёт себя по-разному в мелочах: порядок колбэков, поведение при отказе в разрешении, что происходит после сворачивания. Эти различия либо прячутся внутри плагина, либо всплывают в бизнес-логике, и лучше первое.

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

Когда плагины уже не спасают

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

Уходить в натив целиком стоит, если:

  • приложение постоянно работает с железом в фоне: непрерывный GPS-трекинг, обмен по Bluetooth с устройством, обработка потока с камеры в реальном времени;
  • интерфейс и есть продукт, и его отзывчивость - конкурентное преимущество: редакторы, инструменты для творчества, игры;
  • нужна тяжёлая графика: 3D, AR, фильтры реального времени;
  • больше половины экранов оказались нативными. Это уже нативное приложение, к которому зачем-то приделан WebView, и от гибрида остались одни накладные расходы.

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

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

Посчитаем вашу задачу

Расскажите, что нужно. Отвечу в течение рабочего дня: сроки, вилка бюджета и что можно не делать, чтобы сэкономить.

Обсудить задачу
Обсудить задачу

Перезвоню сам

Оставьте имя и номер. Позвоню в рабочее время, обычно в течение пары часов.