Реализация контейнера для запуска мини-приложений в Super App
Когда WeChat запустил свою платформу мини-программ в 2017-м, стало понятно: Super App — это не просто большое приложение, это операционная система поверх операционной системы. Контейнер для мини-приложений — самая сложная часть этой архитектуры. Не сама логика мини-приложений, а именно runtime-оболочка, которая их изолирует, запускает и управляет их жизненным циклом.
Что такое контейнер мини-приложений и почему его сложно сделать правильно
Контейнер — это не просто WebView с URL. Это система изоляции, управления ресурсами, маршалинга вызовов к нативным API и контроля над жизненным циклом каждой мини-программы. Ошибка в проектировании контейнера ведёт к утечкам памяти между сессиями, краш-петлям при переключении мини-программ и дырам в безопасности — когда мини-приложение одного вендора получает доступ к данным другого.
На Android типичная реализация строится вокруг нескольких изолированных процессов через android:process в манифесте, собственного ClassLoader-а для каждого мини-приложения и кастомного WebViewClient с перехватом всех запросов к bridge:// URI. На iOS — WKWebView с отдельным WKProcessPool на каждую мини-программу, изолированным WKWebsiteDataStore и хуками в WKScriptMessageHandler для вызовов нативного bridge.
Проблема, с которой сталкиваются почти все — memory budget. На устройствах с 3-4 GB RAM держать 5-6 активных WKWebView-процессов нереально. WeChat решил это через aggressive preloading одного пустого WebView и hot-standby пула из 2-3 инициализированных, но без загруженного контента, экземпляров. Мы используем похожий подход, адаптированный под конкретный target device matrix клиента.
Архитектура runtime-изоляции
Ключевое решение — выбор между single-process и multi-process моделью контейнера.
Single-process (всё в одном процессе хоста): проще в реализации, быстрее запуск мини-программы (нет fork overhead), но любой краш мини-приложения роняет весь Super App. Подходит для закрытых экосистем, где мини-приложения пишет доверенная команда.
Multi-process (каждая мини-программа в своём процессе): стабильнее, но на Android — дополнительные 30-50 MB RAM на процесс и latency при первом запуске 400-800ms из-за fork+zygote. На iOS WKWebView процессы управляются системой, поэтому изоляция там де-факто.
Мы реализуем гибридную схему: фоновые мини-приложения (аудио, геолокация) — в отдельном процессе с FOREGROUND_SERVICE, активные UI-мини-программы — в пуле WebView внутри основного процесса хоста с жёсткими ограничениями через WebSettings.setJavaScriptEnabled и кастомный ContentProvider для межпрограммного обмена данными.
JavaScript Bridge: сердце контейнера
Bridge — это протокол между JS-кодом мини-приложения и нативными API хоста. Его дизайн определяет и возможности, и ограничения всей экосистемы.
Типичная реализация на Android:
webView.addJavascriptInterface(new NativeBridge(context), "__miniapp_bridge__");
Но @JavascriptInterface в чистом виде небезопасен — любой JS в WebView получает доступ к bridge. Поэтому поверх добавляем Origin Validator: каждый вызов bridge содержит подписанный токен, сгенерированный при инициализации мини-приложения и привязанный к его bundle hash.
На iOS используем WKScriptMessageHandler:
configuration.userContentController.add(self, name: "miniAppBridge")
С обязательной проверкой message.frameInfo.isMainFrame — иначе iframe внутри мини-приложения тоже получает доступ к нативным API.
Схема вызовов асинхронная с correlation ID: JS отправляет {callId: uuid, method: "getLocation", params: {}}, нативная сторона резолвит промис через webView.evaluateJavaScript("window.__resolve__('\(callId)', \(result))"). Тайм-ауты — 5 секунд для обычных вызовов, 30 секунд для медленных (файловые операции, Bluetooth).
Управление жизненным циклом и памятью
Жизненный цикл мини-приложения: loading → active → background → suspended → destroyed. Контейнер слушает системные события памяти (onTrimMemory на Android, UIApplicationDidReceiveMemoryWarningNotification на iOS) и агрессивно переводит фоновые мини-программы из background в suspended (WebView заморожен, но контекст сохранён) или destroyed (всё сброшено, при следующем открытии — холодный старт).
Типичный сценарий, который роняет конкурентов: пользователь открыл 8 мини-приложений подряд, не закрывая. На iPhone 12 с 4 GB RAM это ~1.6 GB только под WebView-процессы. Система присылает memory pressure: critical, iOS убивает несколько фоновых процессов — и пользователь видит белый экран вместо мини-приложения. Наше решение: мониторинг через os_proc_available_memory() (доступно с iOS 13), проактивное уничтожение suspended мини-программ при давлении >70%, и автоматическое восстановление состояния через serialized snapshot перед уничтожением.
Безопасность: capability-based разграничение доступа
Каждое мини-приложение при регистрации в маркетплейсе декларирует permissions: ["location.read", "camera", "contacts.read"]. Контейнер хранит approved permissions в зашифрованном хранилище (Keychain / Android Keystore) и валидирует каждый bridge-вызов против этого манифеста. Попытка вызвать незадекларированный API — silent fail с логом в аналитику и флагом в системе мониторинга.
Как строится работа
Аудит существующей архитектуры или проектирование с нуля → выбор модели изоляции → проектирование bridge API (обычно 2-4 недели на согласование, потому что это контракт с разработчиками мини-приложений) → реализация runtime-контейнера → нагрузочное тестирование (100+ одновременных мини-приложений в automated тесте) → интеграция с marketplace и системой permissions → поддержка и эволюция bridge API.
Сроки на контейнер «с нуля» под обе платформы: от 3 до 6 месяцев в зависимости от требований к изоляции, набора нативных API в bridge и наличия готовых спецификаций. Только Android или только iOS — вдвое быстрее.







