Реалізація контейнера для запуску міні-програм у 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 розв'язав це через агресивне preloading одного порожнього WebView та hot-standby пулу з 2-3 ініціалізованих, але без завантаженого контенту, екземплярів. Ми використовуємо подібний підхід, адаптований під конкретну матрицю цільових пристроїв клієнта.
Архітектура 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 розмежування доступу
Кожна міні-програма при реєстрації в маркетплейсі декларує дозволи: ["location.read", "camera", "contacts.read"]. Контейнер зберігає затвердені дозволи у зашифрованому сховищі (Keychain / Android Keystore) та валідує кожен bridge-виклик проти цього маніфесту. Спроба викликати незадекларований API повертає silent fail з логуванням в аналітику та флагом у системі моніторингу.
Як це будується
Аудит існуючої архітектури або проектування з нуля → вибір моделі ізоляції → проектування bridge API (зазвичай 2-4 тижні на узгодження, бо це контракт з розробниками міні-програм) → реалізація runtime-контейнера → нагрузкове тестування (100+ одночасних міні-програм в automated тесті) → інтеграція з marketplace та системою дозволів → підтримка та еволюція bridge API.
Терміни на контейнер «з нуля» під обидві платформи: від 3 до 6 місяців залежно від вимог до ізоляції, набору нативних API у bridge та наявності готових специфікацій. Тільки Android або тільки iOS — вдвічі швидше.







