Реализация AI-персонализации главного экрана мобильного приложения
Главный экран — это первое, что видит пользователь при открытии приложения. Для e-commerce это выкладка товаров, для медиаплатформы — подборки, для суперапп — набор виджетов. Статический главный экран отражает усреднённого пользователя, которого не существует. Персонализация превращает его в индивидуальный интерфейс.
Техническая задача сложнее ленты контента: здесь персонализируется не порядок однотипных элементов, а сама структура экрана — какие секции показывать, в каком порядке, с каким контентом внутри каждой.
Уровни персонализации главного экрана
Секции и их порядок
Первый уровень — какие блоки вообще показывать. Пользователь, который никогда не открывал «Акции», не должен видеть промо-баннер на первом экране. Тот, кто регулярно смотрит истории — получает их в топе.
Персонализация порядка секций — задача контекстного бандита (contextual bandit). Каждая секция — это «рука бандита». Награда — клик или время взаимодействия. Алгоритм UCB или Thompson Sampling балансирует exploration (показываем секции, по которым мало данных) и exploitation (показываем секции с высоким историческим CTR).
from vowpalwabbit import pyvw
# Contextual Bandit через VowpalWabbit
vw = pyvw.vw("--cb_explore_adf --epsilon 0.1 --quiet")
def get_section_order(user_features: dict, sections: list[str]) -> list[str]:
# формируем VW-формат запроса
context = f"|user age_group:{user_features['age_group']} time_of_day:{user_features['hour']}"
actions = "\n".join(
f"|section name:{s} historical_ctr:{user_features.get(f'ctr_{s}', 0.1):.2f}"
for s in sections
)
example = f"{context}\n{actions}"
scores = vw.predict(example)
return [s for _, s in sorted(zip(scores, sections))]
Контент внутри секций
Второй уровень — что показывать внутри каждой секции. «Рекомендованные товары», «Для вас», «Продолжить просмотр» — каждый блок наполняется через соответствующий recommendation API (CF, CB или гибрид из предыдущих услуг).
Персонализированные баннеры и CTA
Промо-баннеры с разным текстом и изображениями под разные сегменты пользователей. Сегментация через кластеризацию (KMeans на поведенческих признаках) или правиловую логику: частые покупатели видят «Новинки», давно не заходившие — «Мы скучали, вот скидка».
Конфигурация экрана через сервер
Жёстко хардкодить структуру главного экрана в мобильном клиенте — плохая практика. Персонализированная конфигурация приходит с сервера при каждом открытии приложения:
// Android: HomeScreen конфигурация с сервера
data class HomeScreenConfig(
val sections: List<SectionConfig>
)
data class SectionConfig(
val type: SectionType, // BANNER, PRODUCTS, STORIES, CATEGORIES, CONTINUE_WATCHING
val title: String?,
val items: List<HomeItem>,
val layout: LayoutType // HORIZONTAL_SCROLL, GRID, CAROUSEL
)
// ViewModel загружает конфиг при старте
class HomeViewModel(private val api: HomeApi) : ViewModel() {
private val _config = MutableStateFlow<HomeScreenConfig?>(null)
val config = _config.asStateFlow()
init {
viewModelScope.launch {
_config.value = api.getPersonalizedHome(userId = currentUser.id)
}
}
}
// Composable рендерит динамическую структуру
@Composable
fun HomeScreen(config: HomeScreenConfig) {
LazyColumn {
items(config.sections) { section ->
when (section.type) {
SectionType.BANNER -> BannerSection(section)
SectionType.PRODUCTS -> ProductsSection(section)
SectionType.STORIES -> StoriesSection(section)
SectionType.CONTINUE_WATCHING -> ContinueWatchingSection(section)
else -> {}
}
}
}
}
Jetpack Compose + LazyColumn с динамическим рендерингом секций — чистое решение. Добавление нового типа секции — только новый when-ветка без изменения layout-логики.
Аналогично на iOS через SwiftUI ForEach по конфигурации секций и @ViewBuilder фабрику.
Кэш и мгновенный старт
Конфигурация кэшируется локально. При следующем открытии — мгновенно показываем прошлую конфигурацию, параллельно загружаем новую. Когда новая приходит, обновляем экран. Это паттерн stale-while-revalidate: пользователь никогда не видит пустой экран.
// iOS: stale-while-revalidate для конфигурации главного экрана
func loadHomeConfig() {
// сразу показываем кэш
if let cached = configCache.load() {
homeConfig = cached
}
// обновляем в фоне
Task {
let fresh = try await api.getPersonalizedHome()
configCache.save(fresh)
homeConfig = fresh
}
}
Процесс работы
Аудит текущей структуры главного экрана и доступных сигналов персонализации.
Проектирование server-driven UI протокола — формат конфигурации, типы секций.
Реализация алгоритма ранжирования секций (от простых правил до contextual bandit).
Разработка клиентского рендерера динамических конфигураций.
Метрики: клики по секциям, глубина скролла первого экрана, возврат на следующий день.
Ориентиры по срокам
Server-driven UI с простой персонализацией на правилах — 1–2 недели. Contextual bandit для ранжирования секций + полный динамический рендерер — 3–5 недель.







