«По душе» и «По любви» · 24 сентября 2026

Почему греются телефоны

Нашёл корень в обоих приложениях и исправил его в коде, не меняя дизайн. Правки лежат в отдельных ветках. Сборки пока никому не выложены.

Коротко

  1. Корень в «По душе»: приложение расточительно рисует. Сглаженные углы, тени, стекло и свечение из макета оно рисовало заново на каждом кадре прокрутки и смахивания, то есть 60–120 раз в секунду, хотя картинка не менялась. Теперь каждая такая картинка делается один раз, а дальше берётся готовой. Работы на один кадр стало меньше: в прокрутке афиши примерно в 2,5 раза, в колоде карточек в 4 раза.
  2. Корень в «По любви»: ошибки. Видео-кружок в анкете крутился без остановки, в том числе в свёрнутом приложении, и на каждом круге заново скачивал ролик: 1,9 МБ за 30 секунд, даже когда приложение свёрнуто. Спрятанные вкладки продолжали анимироваться: неподвижный экран «Чаты» перерисовывался 39 раз в секунду. Соединение чата не закрывалось никогда. Всё это исправлено.
  3. Дизайн не изменился. Сверил все 623 экрана макета «По душе» на старой и новой версии. Ни на одном нет отличий, заметных глазом: разница только в отдельных пикселях по краю кнопок, на уровне сглаживания края. Живые экраны карты, афиши и колоды совпадают так же.
  4. Что осталось. Стекло панели вкладок и шапки над списком, который прокручивается, смешивается с тем, что под ним, на каждом кадре: так устроен сам эффект из макета, я его не трогал. Карта при перетаскивании тоже работает, как в любом приложении с картой. Итог стоит проверить на настоящем телефоне.

«По душе»: что было и что сделано

Кадр — одна картинка экрана. Всё, что делается «на каждом кадре», при прокрутке повторяется 60–120 раз в секунду.

Кнопки и чипы-«таблетки»

вид тот же
Было

Форма рисовалась особой кривой, как в Figma. Когда кнопка ехала по экрану, телефон заново вырезал её на процессоре и отправлял в видеочип. В прокрутке афиши набиралось около 100 таких вырезаний на кадр.

Стало

У «таблетки» кривая Figma математически совпадает с обычным скруглением: сглаживание углов там равно нулю. Поэтому телефон рисует её своим быстрым встроенным способом. Это та же форма, нарисованная дешевле.

Тени

вид тот же
Было

Каждая тень на каждом кадре собиралась на отдельном черновом слое, и из неё вырезалось место под кнопкой.

Стало

Тень с вырезом готовится один раз на размер элемента и дальше рисуется готовой картинкой.

Карточка колоды

вид тот же
Было

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

Стало

Карточка рисуется один раз и едет за пальцем готовой картинкой.

Белые блоки афиши, лист карты, шапка поиска, листы

вид тот же
Было

Весь контур со сглаженными углами вырезался заново при каждом сдвиге.

Стало

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

Блок «Просмотр по интересам»

вид тот же
Было

Шесть стеклянных чипов смешивались с белым фоном блока заново на каждом кадре прокрутки.

Стало

Блок вместе с чипами рисуется один раз. Фон под стеклом у чипов — этот же белый блок, поэтому результат тот же.

Свечение мини-карт, кольца, тени кругов онбординга

вид тот же
Было

В регистрации, фильтрах, профиле, листе места, окне «Повысьте доверие», на экране успеха и в онбординге размытие считалось на процессоре при каждой перерисовке. Если лист в это время ехал, это происходило на каждом кадре.

Стало

Размытие считается один раз на размер.

Сколько работы уходит на один кадр

Время процессора на один кадр при непрерывной прокрутке или смахивании, в миллисекундах. Меньше значит холоднее телефон и плавнее картинка. Замер на эмуляторе Android, по 2–3 прогона на каждую сборку. Для афиши «стало» снято до последней правки, блока «Просмотр по интересам». Итоговая сборка в одном прогоне с исходной даёт в 2,7 раза меньше работы на кадр.

Прокрутка «Афиши города»
Смахивание карточки в колоде
Те же числа таблицей
ЭкранСборкамс на кадркадров в секунду

«По любви»: что было и что сделано

Видео-кружок в анкете

вид тот же
Было

Ролик крутился без остановки, в том числе на других вкладках и в свёрнутом приложении, и на каждом круге заново скачивался. Замер на экране правки анкеты: 1,7 МБ за 30 секунд, пока кружок на экране, и 1,9 МБ за 30 секунд в свёрнутом приложении. Раскодирование видео в фоне при этом продолжалось.

Стало

Кружок встаёт на паузу, когда его не видно или приложение свёрнуто. Ролик скачивается один раз и дальше берётся из памяти телефона: 1 КБ за те же 30 секунд, на экране и в фоне. Кружок без звука больше не раскодирует звук.

Спрятанные вкладки

вид тот же
Было

Пока в скрытой ленте висела подсказка «Свайпни вправо», неподвижный экран «Чаты» перерисовывался 39 раз в секунду и занимал две трети ядра процессора.

Стало

0 перерисовок и 0% процессора. Видео, подсказка и блики загрузки в спрятанной вкладке стоят, а при возврате на вкладку снова идут.

Соединение чата

вид тот же
Было

После первой открытой переписки соединение с сервером не закрывалось никогда. Раз в 30 секунд шёл сигнал, а при обрыве приложение без конца переподключалось, в том числе когда было свёрнуто.

Стало

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

Свайп анкеты и переписка

вид тот же
Было

На каждый свайп заново распаковывались картинки подсветки «лайк/нет» размером 2400 на 2400 точек, около 23 МБ каждая. Тексты анкеты перемерялись на каждом кадре жеста. Прокрутка переписки на каждом движении заново раскладывала все сообщения.

Стало

Картинки подсветки распаковываются один раз, замеры текста берутся из памяти. Переписка раскладывается заново, только когда меняется её шапка.

Переписку вживую не проверил: у тестового аккаунта нет чатов. Анкет с кружком в ленте на стенде тоже нет, поэтому видео проверено на экране правки своей анкеты. Там тот же плеер.

Как проверял, что вид не изменился

Что дальше

  1. Выложить сборки тестировщикам — по вашему слову. Обе сборки готовы. Правки лежат в ветках perf/podushe-heat и perf/polyubvi-heat и пока не отправлены ни на GitHub, ни в Gitea.
  2. Проверить на настоящем телефоне: 15 минут прокрутки и смахивания на старой и новой сборке, с температурой телефона. Нужен любой телефон на Android — или ваше разрешение взять Pixel 7a, подключённый к макмини.
  3. У тех, кто жалуется, узнать: какое приложение, модель телефона, что делали, когда грелось, и греется ли телефон, когда приложение свёрнуто.

Для разработчиков

Ветки и коммиты

  • perf/podushe-heat от release/2026-09-23 (7807dc8e): 6e4ca99c, 68ec4406, 6b87aace. Карточка DUSHA-99.
  • perf/polyubvi-heat от того же коммита: c66b70a9. Карточка LYUBVI-52.
  • Ветки локальные, не запушены. release/2026-09-23 с GitHub снова стёрло зеркало, локально она цела.

«По душе» — что и где

  • ui/kit/Shapes.kt: PdSmoothCornerShape.createOutline — если радиус каждого угла ≥ половины короткой стороны или равен 0, отдаётся Outline.Rounded; сглаживание у Figma там вырождается в 0. pdBackground — сглаженная заливка готовыми углами ALPHA_8 плюс прямоугольники. pdCachedLayer — CompositingStrategy.Offscreen для непрозрачных узлов, которые ездят.
  • ui/kit/FigDraw.kt: figDropShadow без saveLayer — вырез DST_OUT печётся в FigCutShadow (LRU 32 МБ, ключ — форма, размер, R, смещение); figInnerShadow без clipPath на кадре; FigDrawCache для Canvas { }.
  • Размытие на CPU при каждой перерисовке: CityNodeScreen.PdMapGlow, SuccessNodeScreen.SuccessRings, ProfileNodeScreen.PromoRings, OnboardingArt.OnboardingRing.
  • pdBackground: блоки AfishaPageNodeScreen, лист MapNodeScreen, шапка SearchPageNodeScreen, листы CalendarSheet, RadiusSheet, CategoryPage. pdCachedLayer: SwipeCard, блок интересов афиши.
  • Не тронуто: стекло pdGlassRegular / pdGlassButton над прокручиваемым содержимым (панель вкладок, шапка) — ColorDodge, ColorBurn и Darken требуют чтения фона; PdPageLayer и размытие подложки в чате; карта MapLibre.

«По любви» — что и где

  • ui/kit/Video.kt: playWhenReady = playing && LocalPlTabActive && lifecycle ≥ STARTED; SimpleCache 200 МБ + CacheDataSource для http; звуковая дорожка выключена у плеера, созданного без звука.
  • ui/kit/TabActive.kt + MainTabsScreen.KeepAliveTab; FeedScreen (подсказка), ScreenLoaders.shimmerBrush.
  • core/network/ChatWebSocket.kt: pause() и resume() по ON_STOP и ON_START в MainActivity; колбэки прежнего сокета больше не трогают состояние нового.
  • FeedCardNodeScreen.SwipeOverlay — SwipeOverlayBitmaps (мягкие ссылки); rememberTextMeasurer(cacheSize) в FeedCardLive, ChatThreadLive, SympathyTabLive; ChatThreadLive.collapsed через derivedStateOf.
  • Не тронуто: dragX по-прежнему читается в композиции (FeedScreen → FeedDeckLive). Перенос в graphicsLayer требует переделки геометрии задней карточки и пока не сделан.

Замеры

СценарийБылоСтало
«По душе», прокрутка афиши: мс CPU на кадр / кадр/с60,6 / 14,025,9 / 29 · в итоговой сборке ещё меньше
«По душе», смахивание колоды: мс CPU на кадр / кадр/с32,5 / 22,87,8 / 40,8
«По душе», маски путей, загружаемые на кадр (афиша / колода)7 / 160–4 мелких / 0
«По любви», неподвижные «Чаты» при подсказке в скрытой ленте: кадр/с / CPU38,7 / 66%0 / 0%
«По любви», видео-кружок на экране: скачано за 30 с1 676 КБ1 КБ
«По любви», видео-кружок, приложение свёрнуто: скачано за 30 с / CPU1 884 КБ / 5%1 КБ / 1%
Системные «Настройки», прокрутка (ориентир)5,7 / 54,1—

Эмулятор Android 15 (API 35), 1170×2532, 480 dpi, на макмини; «По любви» — Android 10. Кадры HWUI из dumpsys gfxinfo, время процессора из /proc, трасса Skia из atrace, трафик из /proc/net/dev. Эмулятор рисует через GL хоста, поэтому абсолютные миллисекунды на телефоне будут другими.