В общем, была задача на оптимизацию плана работ: есть набор заказов, и в каждом N тасков. Таски могут зависеть друг от друга или нет, имеют длительность и тип. Ещё есть M работников, каждый из которых может выполнять таски только определённых типов, причём, у каждого своя скорость.
Если все таски в заказе выполнены, фирма получает сколько-то денег. У заказа есть дедлайн, за просрочку штраф (за каждый день просрочки), который вычитается из цены заказа. И ещё фирма тратит деньги за каждый день своей работы независимо от того, как загружены её сотрудники (то есть просто платит зарплаты по сути). Ещё важен учёт праздников и выходных.
Нужно заработать на определённом наборе заказов и работников как можно больше денег. Полный текст задачи и код программы проверки есть в репозитории. Эта задача является изменённым заказом, который мы с напарником решали в реальной жизни: оптимизация работы печатных станков для типографии. Правда, тогда и мы справились так себе, и заказчик в итоге сначала захотел всё вручную сам делать, а потом и вовсе похоронил проект при смене директора.
!!! Не читайте дальше, если хотите сначала попробовать решить самостоятельно, потому что я сейчас опишу эффективные подходы и результаты.
________________________________________
Так вот. Мы с другим экспертом Андреем сразу сели и написали разные варианты, чтобы задать шкалу, по которой будем оценивать решения команд. За пару часов я собрал примитивный жадный алгоритм, который сортировал заказы по прибыльности и укладывал в сетку кое-как, это дало нам нижнюю оценку. Мой алгоритм заработал ~75 млн виртуальных рублей, мы решили для оценки поставить нижней границей 40 млн. То есть всё, что ещё ниже, оценивалось в ноль баллов за качество оптимизации.
Затем Андрей закрылся дома на три дня и вышел на свет со сложным жадным алгоритмом, который очень хитро сортировал заказы и очень хитро укладывал их в сетку, попутно выбирая разные способы этой укладки в зависимости от конкретного заказа. Это позволило заработать ~275 млн рублей. Мы сделали верхней границей для оценки 280 млн.
К сожалению, в итоге только две команды из десяти прошли нижнюю границу, заработав, соответственно, ~91 и ~105 млн. К верхней границе не приблизился никто. И у четырёх команд алгоритм вообще не смог уложить без нарушений задачи в сетку (то есть, например, произвольно менял длительности, накладывал задачи друг на друга, давал одному работнику две задачи в один момент и так далее). Важной ошибкой команд, на мой взгляд, являлся тот факт, что никто не воспользовался возможностью запустить алгоритм на несколько минут и дать ему поработать. По условиям задания, можно было тянуть до 5 минут на одну оптимизацию, но по факту решения команд отрабатывали за единицы и десятки секунд.
На самом конкурсе, пока команды работали, я решил попробовать сделать быстрое (по времени написания) но эффективное решение. Сначала попробовал жадную сортировку + доведение до лучшего варианта методом имитации отжига. В качестве нового состояния я просто менял местами заказы целиком. Этот вариант работал пару минут и дал мне около ~200 млн дохода. К слову, команда-лидер использовала такой же подход, просто не докрутила число итераций и температуру.
Ну и потом я взял готовую либу по реализации классической генетики с особями и скрещиванием. Особью был массив с приоритетами заказов (которые конечно же нужно было аккуратно уложить чистым алгоритмом). Тип скрещивания: scattered. Всего 15 поколений по 20 особей, и это за минуту-полторы давало ~230-240 млн. Считаю, что для конкурса это самый лучший выбор: пишется одним человеком за день и даёт почти максимум, при этом легко настраивается на нужную длительность работы, легко параллелится.
Ну а потом уже дома я посидел и накодил свой вариант сложного жадного с плавающим окном перебора отсортированных заказов и плавающей же укладкой по работникам. Такая штука за две минуты зарабатывает ~281 млн. Но за три дня в условиях стресса я бы такое не сделал, скорее всего.
#dev
🚀 Вышел Room 2.8.0
Самая важная (она и единственная) фича свежего релиза - получение SupportSQLiteDatabase, являющегося оберткой над RoomDatabase с SQLiteDriver. Честно, не знаю кому это пригодится, поэтому будет полезно узнать от вас в комментариях практический опыт.
Менее масштабные, но всё же важные изменения:
👉 minSdk подняли с 21 до 23
👉 Добавили поддержку KMP таргетов: watchOS и tvOS
🛠 Исправили баги
#android#jetpack#androidjetpack
⚙️Jetpack Compose 1.11 — стабильный релиз! 🚀
Что нового:
👉 Визуальная отладка переходов Shared Elements
👉 Переработанная поддержка трекпадов — теперь работают как мышь и распознают жесты
👉 Preview Wrappers для более быстрой работы с превью
👉 Host Defaults для KMP-проектов
👉 Testing APIs v2 для корутин
👉 Экспериментальные API: Styles, MediaQuery, Grid, FlexBox
Подробности по каждому пункту — отдельными постами в @compose_broadcast
#Compose#Android#AndroidJetpack
🚀Вышла новая Jetpack библиотека - Security State Provider (пока в статусе Альфа)
Основная цель библиотеки — предоставить действительные (actionable) данные о состоянии безопасности устройства и его компонентов, в частности:
👉 Версии обновляемых компонентов (updateable system components).
👉 Наличие применённых исправлений безопасности (security patches / applied fixes).
👉 Общий “security state” — то есть агрегированное представление безопасности системы.
То есть, библиотека даёт вам API, чтобы “спросить у Android”: насколько актуальна система, есть ли уязвимости, какие компоненты нуждаются в обновлении.
Она не заменяет шифрование/криптографию (как, скажем, security-crypto), но с дополняет стек безопасности: помогает принимать решения на основании состояния платформы.
#android#androidjetpack#безопасность
🤖Вышел Jetpack Window 1.5.0
Что нового:
👉WindowSizeClass появились разделения для больших (Large - от 1200 до 1600) и очень больших (XLarge - более 1600 dp) экранов
👉 Получение WindowMetrics теперь работает с Application Context, а не только с UI Context
👉 Добавили API для автоматического сохранения и восстановления статуса встраивания Activity при перезапуске процесса
👉 Удалили экспериментальное API WindowInsets
#android#androidjetpack#jetpackupdate
🚀Вышел Core SplashScreen 1.2.0
Что нового:
🌙 Добавлен атрибут isLightTheme для тем SplashScreen — можно корректно выбирать оформление под светлую/тёмную тему
🖼 Иконка загрузки теперь корректно подтягивается через AppCompatResources (улучшена поддержка ресурсов и векторных изображений)
👉 Убрали ручное объявление обращения к новым API — теперь всё автоматически моделируется при сборке с AGP ≥ 7.3 и R8 ≥ 3.3
🐘 Для AGP 8.1 и выше — всё работает “из коробки” без дополнительных настроек ⚙️ (рекомендуется обновление до этой версии AGP или выше)
#android#jetpack#androidjetpack#jetpackupdate
🤖Как проверить, какие комбинации фичей поддерживает камера смартфона
Современные смартфоны умеют снимать с HDR, 120 FPS и стабилизацией, но вот какая комбинация этих фич реально доступна — загадка. Например, стабилизация часто работает только при 1080p, а 60 FPS не всегда доступны в 4K.
С выходом Jetpack CameraX 1.5 появилось API GroupableFeature и новые параметры для SessionConfig, которые позволяют из кода проверить, какие функции камера может использовать одновременно.
// Пример проверки неподдерживаемых комбинаций
val unsupportedFeatures =
mutableSetOf<GroupableFeature>()
val appFeatureOptions =
setOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION)
appFeatureOptions
.filterNot { it in currentFeatures }
.filter { featureOption ->
cameraInfo.isFeatureGroupSupported(
SessionConfig(
useCases = useCases,
requiredFeatureGroup =
currentFeatures + featureOption
)
)
}
.forEach { unsupportedFeatures.add(it) }
Так можно заранее отсеять неподдерживаемые сочетания — например, когда HDR и стабилизация не работают вместе при 60 FPS 🎥
Подробнее в статье (EN)
#android#camerax#Jetpack#androidjetpack
🚀Jetpack Telecom 1.1.0 добавил то, чего не хватало VoIP-приложениям с самого начала — историю звонков прямо в системном дайлере.
До этого момента пропущенный звонок в Telegram или любом другом VoIP-приложении требовал переключиться в само приложение, найти вкладку с историей и перезвонить оттуда. Теперь это можно делать прямо из нативного дайлера Android.
Работает через два новых механизма. Первый — интеграция лога звонков. Приложение регистрирует звонки через TelecomManager.addCall, и они автоматически попадают в системную историю. Для обратного звонка нужно зарегистрировать intent-фильтр на TelecomManager.ACTION_CALL_BACK и правильно обработать EXTRA_UUID, который система отдаёт вместе с вызовом. UUID генерирует CallControlScope.getCallId в момент регистрации звонка — его и нужно хранить, чтобы потом разрезолвить детали вызова.
Второй механизм — исключение из лога. Не каждый звонок стоит светить в системной истории, и теперь это явно контролируется через флаг isLogExcluded = true в CallAttributesCompat. Например, эфемерные или приватные звонки можно просто не записывать.
‼️ ВАЖНО: фичи доступны только на Android 16.1 (SDK 36.1) и выше. Кроме того, нативные дайлеры используют package allowlist для контроля того, какие VoIP-приложения отображаются в истории. Роллаут идёт поэтапно, первым подключился Google Meet. Для локального тестирования Google рекомендует Telecom Sample Dialer из platform-samples.
Меня радует сам вектор: Google последовательно закрывает разрыв между VoIP и нативной телефонией. Жаль, что allowlist пока ограничивает возможности сторонних приложений. Печально то, что это делают так поздно, когда звонки не по телефону делают уже 5–10 лет.
🔗 Источник: android-developers.googleblog.com
#Android#Telecom#AndroidJetpack
🚀Google взялась за упрощение Picture‑in‑Picture
PiP на Android долго был зоопарком: разный API на версиях, разный UI‑стейт, много if (SDK_INT…) и бойлерплейта. Новая Jetpack‑библиотека androidx.core:core-pip как раз и создана, чтобы это спрятать: она выравнивает вызовы PiP между версиями Android, даёт единый способ задавать параметры (особенно для видео/плееров), объединяет разрозненные колбэки состояния PiP и уменьшает количество кода за счёт готовых пресетов действий для типовых сценариев.
Требования: обновляем Activity
Чтобы всё это заработало, мало просто подключить core-pip — нужно обновиться до свежей Activity 1.13.0 (пока в альфе). В этой версии есть новые API для отслеживания состояния PiP (PictureInPictureUiStateCompat и слушатели), на которых удобно строить логику поведения UI, когда окно уходит в PiP или, например, «прячется» в угол.
// Пример кода: реагируем на состояние PiP
class PlayerActivity : ComponentActivity() {
private val pipUiStateListener =
Consumer<PictureInPictureUiStateCompat> { state ->
if (state.isStashed) {
/* спрятать контролы плеера */
} else {
/* показать контролы плеера */
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... инициализация UI / плеера ...
addOnPictureInPictureUiStateChangedListener(pipUiStateListener)
}
override fun onDestroy() {
removeOnPictureInPictureUiStateChangedListener(pipUiStateListener)
super.onDestroy()
}
}
🔗 Подробности про библиотека в документации
#Android#AndroidDev#AndroidJetpack#PIP
🚀 Вышел SavedState 1.4.0 — теперь с поддержкой nullable типов
Saved State — это библиотека Jetpack, которая помогает сохранять и восстанавливать состояние при пересоздании, например из-за изменений конфигурации в Android или при выгрузке процесса системой. Поддерживает Kotlin Multiplatform
Вышел androidx.savedstate:1.4.0, и в этом обновлении основное внимание уделено улучшению API. Ключевое изменение - добавлена нативная поддержка nullable типов в SavedStateRegistryOwner.saved, а также в функциях encodeToSavedState() и decodeFromSavedState() — теперь можно напрямую сохранять и восстанавливать null-значения без костылей.
// Теперь это работает корректно без дополнительных проверок.
var name: String? by savedState(saved = "name", default = null)
#android#jetpack#kotlin#kmp#jetpackupdate#androidjetpack
Вот реальная история, как знание механики работы ViewModel спасло мне вечер 👇
У меня приложение на Compose и Jetpack Navigation 3 (работает на основе состояния Back Stack). Экран «Навигатор файлов» открывает папки рекурсивно: по сути это тот же экран, но с другими данными. Все данные — из одной и той же ViewModel.❗️Баг: при переходе в папку навигация срабатывает, UI не меняется. Современные ИИ подсказали общие вещи, но не помогли — промты, видимо, подвели (тут мне ещё надо прокачать знания) 🙂
Вспомнил ключевой факт про архитектуру:
👉 Все ViewModel живут в ViewModelStore.
👉 В пределах одного ViewModelStoreOwner (Activity/Fragment/NavBackStackEntry) получение ViewModel по умолчанию идёт по типу.
👉 Если нужно несколько экземпляров одного типа на одном owner’е — используем key.
Решение в одну строку — привязать ключ к ViewModel, связанный с текущей папкой:
@Composable
fun FileNavigator(
folderId: Id,
modifier: Modifier = Modifier,
) {
// новый folderId → новый экземпляр ViewModel → новый UI-стейт
val viewModel: FileNavigatorViewModel = viewModel(
key = "files(rootId='$folderId')"
)
// ...
}
Мини-чеклист, если ловите такой баг
👉 Один экран используется повторно с разными параметрами? → Нужен key.
👉 Меняется route, но owner тот же? → key обязателен.
👉 Используете Hilt/Koin? → У этих функций тоже есть параметр key (hiltViewModel(key=...), koinViewModel(key=...)).
👉 key должен детерминированно зависеть от входных данных (например, folderId).
👉 При навигации назад убедитесь, что ViewModel освобождается ожидаемо.
Ещё нюанс - если у вас сложная иерархия графов, проверьте, к какому ViewModelStoreOwner вы реально привязаны.
Рекомендую посмотреть мои видео по теме:
📹Разбор Jetpack Navigation 3
🪙Полный разбор Jetpack ViewModel в Android и Kotlin Multiplatform
#android#compose#androidjetpack#viewmodel#архитектура
🚀Вышел Jetpack WorkManager 2.11.0
Что важного в новой версии:
❗️ minSdk: 23 (Marshmallow). Lollipop (API 21–22) больше не поддерживается.
🎉 Новое API: setRemoteSessionTimeoutMillis() в Configuration.Builder — задаёт время жизни сессии RemoteWorkManager после последнего использования.
class App : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
// Сколько живёт RemoteWorkManager-сессия после последнего использования
.setRemoteSessionTimeoutMillis(TimeUnit.MINUTES.toMillis(5))
.build()
}
}
🎉 Тестирование: TestDriver.stopRunningWorkWithReason(...) — можно останавливать выполняющуюся работу с указанием причины.
Зачем: удобно эмулировать внешние условия (остановку системой/приложением) и проверять, что код корректно реагирует на «принудительное завершение».
// Arrange
val request = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context).enqueue(request).result.get()
// Act
val testDriver = WorkManagerTestInitHelper.getTestDriver(context)!!
testDriver.stopRunningWorkWithReason(
request.id,
/* reason */ 0 // укажите код причины, например ваш тестовый/константу из проекта
)
// Assert — далее проверяете ожидаемое состояние
🛠 Также сделали исправление багов
#android#jetpack#workmanager#jetpackupdate#androidjetpack
‼️Google отменяет Compose Material Icons
С релизом Compose Material 3 — версии 1.4.0 Google сделала радикальный шаг: библиотека androidx.compose.material.icons исключена из Material3 и больше не рекомендуется к использованию.
Что произошло
👉 Material Icons удалены из актуального релиза Material3 1.4.0
👉 Поддержка библиотеки прекращена
👉 Можно подключить вручную, но это временная мера для тех, кто не успел мигрировать
❌ Основная причина отказа - негативное влияние на скорость сборки. Библиотека Material Icons тащит весь огромный набор иконок. Это ощутимо увеличивает build time крупных Android и Compose Multiplatform проектов.
Google предлагает переходить на Vector Drawable XML в Android или Compose Multiplatform ресурсах. Скачивайте иконки с вкладки Android на странице Material Symbols и храните в ресурсах проекта
Преимущества использования Material Symbole:
✔️ актуальный набор иконок
✔️ минимальный вес и отсутствие лишних зависимостей
✔️ предсказуемое время сборки
✔️ лучший контроль над ресурсами проекта
❗️ Обновлений библиотеки Material Icons не будет!
Источник - официальная документация Android Jepack
Благодарю подписчика Evgeny F. за то, что обратил внимание на изменение.
#Android#AndroidJetpack#Material3#Compose#CMP