Jetpack Room 3.0 теперь stable с кучей преимуществ для мультиплатформы и не только. Запланируйте интеграцию нового мажора.
У меня как раз есть статья: https://habr.com/ru/articles/1019598

Пишем под самую популярную мобильную ОС
Jetpack Room 3.0 теперь stable с кучей преимуществ для мультиплатформы и не только. Запланируйте интеграцию нового мажора.
У меня как раз есть статья: https://habr.com/ru/articles/1019598
[пожалуйста, не злоупотребляйте эмодзи]
😻 Привет Халчане! Новое обновление HalChat for Android 1.0.3 (Pin, read and react)
Что нового?
🥷 Теперь можно закреплять сообщения
🤗 Поиск по чату
😳 Реакции на сообщения
🤓 Возможность выделять и копировать текст
🤖 Синхронизация действий в реальном времени
🫢 Возможность вернуться в самый низ, когда зашли далеко
Что исправлено?
🤠 Проведена оптимизация от улучшения распределения задач до ускорение процессов синхронизации
👻 Исправлены баги (опросы показывали зашифрованный текст, не всегда загружались люди, терялась синхронизация и данные и т.п.)
Добавлен выбранный вариант в опросе
Что дальше?
🙀 В следующем обновлении будут дополнительно добавлен локальный ИИ. Благодаря однобитным ИИ моделям, это новая технология уже доступна на HalChatWeb.
🤔 Также я дообучил собственную ИИ модель HalChat-RP с которой сняты больше ограничений и дообучена на RP общении, в том числе все 1к+ ИИ персонажей из генератора HalChatRP
И хотел бы попросить вас всем пройти опрос, проголосовало мало людей, а от этого выбора зависят новые звуковые эффекты мессенджера: https://halwarsing.questionpro.com/t/AddsYZ9TdN
😈 До новых встреч!
Google Play: https://play.google.com/store/apps/details?id=halwarsing.net.halchatandroid
RuStore: https://www.rustore.ru/catalog/app/halwarsing.net.halchatandroid
HalChat Web: https://halch.at/c/tZgWWT
GitHub: https://github.com/halwarsing/HalChat/tree/dev
Новинки синтеза речи для Андроид. В Гугл Плэй появилось приложение для офлайн синтеза речи высокого качества VoxSherpa TTS.
VoxSherpa имеет настройки “умные паузы”, тэги эмоций, и т.д. Позволяет установить в качестве системных 3 нейро-движка синтеза речи:
Kokoro. Мультиязычный, но русский язык не поддерживает.
Piper. С ним нужно дополнительно загрузить отдельные голоса для разных языков. Есть довольно хороший русский голос Irina. Среди английских голосов есть несколько очень высокого уровня.
Supertonic 3 TTS. Очень интересный новый движок. Полностью мультиязычный (русский поддерживает), хорошо переключается между английскими и русскими словами даже внутри одного предложения. Очень высокое качество (настраиваемо), но есть лёгкий английский акцент. Сейчас плохо работает с изменением скорости (лучше оставить на 1.0). Есть в Гугл Плэй отдельным приложением. Есть также на Гитхаб:
https://github.com/davnozdu/supertonic-android
Перед каждым релизом прохожу по одному и тому же списку. Не потому что умный, а потому что каждый пункт там появился после того как я облажался.
Три вещи которые горели чаще всего.
Разные версии Android. На эмуляторе всё красиво. На реальном устройстве со старой версией что-то обязательно едет. Держу под рукой старый телефон с Android 10, туда ставлю перед каждым релизом.
Разрешения. Забываешь добавить в манифест, на новых версиях система спрашивает пользователя, пользователь жмёт «запретить» и половина функций молча перестаёт работать. Без каких-либо ошибок в логах.
ProGuard и минификация. Локально всё работает. В release сборке падает что-то что ты вообще не трогал. Потому что минификатор убрал класс который использовался через рефлексию.
Список не длинный но каждый раз спасает от как минимум одного стыдного бага в продакшене.
Что у вас в чеклисте перед релизом чего нет у большинства?
Прямая Web3-монетизация без посредников (Peer-to-Peer) для артистов на радио.
Буквально вчера закончил написание сервисного бота для коммерческих нужд в мессенджере "Concord". Основной целью проекта является автоматизация процессов публикации авторского материала на интернет-радио. Бот был создан как помощник для авторов аудиоконтента: музыкантов, продюсеров, подкастеров и т. п.
Задача была непростой. Нужно было объединить возможности мессенджера с его токеномикой и реализовать передачу медиаконтента (картинок, аудиофайлов, текстовых данных) на удаленный сервер в формате JSON. Для этого я написал серверную страницу на PHP, в которой реализовал весь необходимый API.
При передаче медиаданных пользователь запускает нужного бота и отправляет ему в личные сообщения весь необходимый материал. Отправка изображения артиста и аудиофайла выполняется напрямую, без каких-либо команд. Бот сам идентифицирует тип данных, полученных от пользователя, и записывает их в локальные файлы. Вот как я реализовал проверку принадлежности данных к типу файлов:
function isJpeg(string $data): bool
{
return substr($data,0,2) === "\xFF\xD8";
}
function isMp3(string $data): bool
{
if (substr($data,0,3)==="ID3") {
return true;
}
return isset($data[1])
&&
ord($data[0])===0xFF
&&
(ord($data[1]) & 0xE0)===0xE0;
}После получения данных нужно сразу определить что именно пришло - команда или файл:
if (preg_match('/^\/(help|bio|title|tracks|done)\b/i', $data))
{
processCommand($db, $uuid, $data);
exit;
}
if (isBase64($data))
{
saveBinary($db, $uuid, $data);
exit;
}
reply("Unknown command, please use /help.", false);Описание профиля артиста и названия его треков передаются в текстовом виде используя специальный набор команд:
/help - Show this help
/bio text - Update artist biography
/tracks - Show info of all tracks
/title text - Update current track title
/pay amount - Pay for service
/done - Finalize current track
Send JPG image to update artist image
Send MP3 audio to update current track
Используя бота, артист может отправлять свои материалы в ротацию на радио для продвижения своего творчества. Протокол взаимодействия с ботом довольно простой:
артист отправляет фото профиля
артист отправляет описание профиля
артист загружает трек
артист отправляет описание трека
артист выполняет оплату сервиса
артист финализирует трек
Пока трек не финализирован, артист может изменять любые данные. Финализация трека возможна только после выполнения всех вышеуказанных действий. После этого любые отправленные артистом данные активируют запись уже нового трека, и процесс повторяется. При этом данные профиля артиста можно изменять без ограничений.
МОНЕТИЗАЦИЯ
Отправка материала артистом требует платы за сервис. Когда артист подтверждает оплату, бот списывает с его кошелька требуемую сумму и зачисляет её на кошелек владельца радиосервиса. Это значительно упрощает обмен активами между плательщиком и получателем.
Таким образом, технология Web3 с использованием блокчейна позволяет оперативно выполнять оплату за предоставленный сервис без лишних трат и банковских процедур. При этом все транзакции становятся видны в блокчейне уже через несколько минут.

Следующим этапом разработки я планирую реализовать возможность начисления роялти каждому автору музыкального материала. Для этого потребуется создать публичную таблицу рейтинга на сайте радио, где слушатели смогут голосовать за понравившиеся треки, продвигая артиста на верх списка. Исходя из количества прослушиваний конкретного трека, можно будет рассчитать сумму роялти и автоматически выплачивать её на личный кошелек автора в мессенджере.
Таким образом, объединение двух разных сущностей, а именно интернет-радио с приложением для обмена сообщениями, является неким ноу-хау для оказания помощи в развитии молодых дарований. Лично для меня как для разработчика это отличный вызов и прекрасная возможность "пошевелить мозгами".
Если у вас появятся предложения, буду рад подискуссировать.
На Android вышло новое приложение CatchCat для любителей кошек. Это аналог Pokémon Go, но бесплатный и без встроенных покупок. Нужно ходить по улице и городам и фотографировать различных котов. Нейросеть сама определяет породу и выдаёт пользователю карточку питомца. Есть здесь и карта, на которой можно отслеживать найденных другими игроками котов. Чем больше снимаете, тем выше уровень. Версия для iOS у проекта появится позднее.
Как я научил свою читалку различать примечания и комментарии в «Войне и мире»
Я не профессиональный программист. Пишу Android-читалку MRead для себя и для тех, кому тоже надоели комбайны. Код закрытый, всё работает локально, без серверов и трекинга. Раздаю через RuStore, 4PDA и GitHub.
В версии 1.4.0 я наконец победил баг, который меня лично бесил как читателя. Расскажу именно про него, потому что задача оказалась интереснее, чем выглядела.
Проблема
Открываю «Войну и мир». В одном предложении сразу два маркера сноски: примечание (перевод французского) и научный комментарий. В разных изданиях они нумеруются независимо, поэтому в тексте легко встретить два маркера с одинаковой цифрой. Старая логика по тапу вытаскивала из текста ближайшую цифру и искала абзац, начинающийся с этого числа, по всем главам, и показывала первое совпадение. Итог: тап по «6» открывал не ту сноску, а тап по числу вроде «1805» вообще пытался найти несуществующую сноску.
Почему наивный подход не работает
Цифра это не идентификатор. Она повторяется и в каждой главе, и между разделами «Примечания» и «Комментарии». А вот в исходнике всё однозначно: каждый маркер это ссылка с уникальным адресом.
EPUB: 1 FB2: [6] и {4}
То есть книга всегда знает правильный ответ. Беда была в том, что мой парсер при импорте выбрасывал href и оставлял только видимый текст маркера.
Что я поменял
При разборе HTML сохраняю не только текст абзаца, но и ссылки-сноски с точной позицией маркера. Позицию считаю надёжно: оборачиваю маркер невидимыми символами из Private Use Area до очистки текста, а после очистки нахожу их и вычисляю смещение. Так цифры в обычном тексте (годы, числа) не путаются с маркерами.
Позиции храню в абсолютных координатах главы. Это важно, потому что мой пагинатор режет абзацы на куски по строкам. Абсолютные смещения переживают нарезку без отдельной возни в пагинаторе.
По тапу беру не цифру, а ссылку под пальцем, и резолвлю сноску по точному id из нужного файла. Для FB2 пришлось ещё и сохранить id секций сносок при конвертации в HTML, потому что примечания и комментарии лежат в отдельных главах.
Старый поиск по цифре оставил как запасной вариант для книг без нормальной разметки, но убрал срабатывание на голые числа.
Результат: тап по примечанию открывает перевод, тап по комментарию открывает комментарий, а год «1805» больше никого не трогает.
Контекст для тех, кому интересно
Рендер текста у меня не WebView, а нативный движок на Canvas с собственной пагинацией (Jetpack Compose, Kotlin). Это даёт контроль над переносами, выравниванием по ширине и стабильной привязкой цитат и закладок, но за каждую такую фичу приходится платить ручной работой вроде этой истории со сносками.
Что ещё в 1.4.0, кратко
• Полнотекстовый поиск по PDF (для PDF с текстовым слоем)
• Озвучивание (TTS): голоса, скорость, таймер сна, автопереход, пауза с памятью места
• Передача слова во внешний словарь и контекст предложения в экспорт Anki
• Настраиваемые блоки настроек чтения, межабзацный интервал, Bold/Italic
• Полки, фоновый импорт из папок, оценки и заметки, Material You
Ссылки
• RuStore
• GitHub
• 4PDA
Если у вас была книга FB2, переоткройте её, чтобы заработали точные сноски (HTML генерится при импорте). EPUB подхватит сам.
Буду рад замечаниям по подходу. Если делали резолв сносок иначе, расскажите как, мне правда интересно.
Как я ускорил бэкапы в 20 раз и обошёл ловушку Jsoup: развитие самописной Android‑читалки MRead (v1.3.0)
Всем привет! Не так давно я рассказывал, как боль от перегруженных интерфейсов заставила меня открыть Android Studio и написать собственную читалку с кастомным движком рендеринга и точным выделением текста.
Статья получила теплый отклик и в комментариях набежало много отличных предложений. В этом посте я хочу поделиться техническими решениями, которые вошли в крупное обновление 1.3.0.
1. Бэкапы и боль от Storage Access Framework (SAF)
В приложении есть функция бэкапа: упаковка базы данных Room, настроек и распакованных HTML‑глав с картинками в один ZIP‑архив. Изначально я писал файлы напрямую в OutputStream, полученный через ContentResolver (SAF). Итог: библиотека на 500 МБ архивировалась около 5 минут. SAF проводит проверки безопасности для каждого записываемого чанка, что убивает I/O операции.
Решение: сборка архива переехала во внутренний кэш приложения. Туда пишем без ограничений SAF — буфером по 64 КБ и уровнем сжатия BEST_SPEED (картинки уже сжаты, гнать их через BEST_COMPRESSION бессмысленно). Когда ZIP готов целиком, он одним куском копируется в пользовательскую папку через SAF — вместо тысяч мелких защищённых записей получается одна
2. Material You: как получить правильные цвета обоев
При внедрении динамических тем (Android 12+) я столкнулся с тем, что стандартный вызов dynamicLightColorScheme().background на многих устройствах выдает просто унылый белый или бледно‑серый цвет, игнорируя сочные оттенки обоев.
Решение: Самые насыщенные цвета из системной палитры Monet хранятся в secondaryContainer и surface. Решение нашлось в самой палитре Monet: наиболее насыщенные цвета живут в secondaryContainer и surface, а не в background. Переориентировал маппинг цветов приложения на эти слоты и интерфейс действительно ожил. Теперь интерфейс действительно реагирует на смену обоев. Плюс привязал OnSharedPreferenceChangeListener, чтобы тема менялась мгновенно на всех экранах без перезапуска.
3. Странности парсинга FB2 и баги Jsoup
Иногда вместо обложки FB2 парсер ставил черно‑белую картинку из середины книги. FB2 хранит все изображения в тегах <binary> в конце файла в хаотичном порядке. Если тег <coverpage> отсутствует, старый алгоритм просто брал первую попавшуюся картинку из бинарной кучи.
Я переписал фоллбэк: теперь, если явной обложки нет, Jsoup ищет первый тег <image> прямо внутри <body> книги.
Попутно всплыло неочевидное поведение Jsoup: если атрибут отсутствует, attr() возвращает пустую строку, а не null — это задокументировано, но интуитивно ожидаешь null. Из‑за этого Элвис‑операторы (?:) молча проглатывали пустую строку вместо ухода в fallback. Написал строгую обертку takeIf { it.isNotEmpty() }, и теперь обложки извлекаются безошибочно.
4. Изолированный свайп яркости в Compose
Нужно было добавить регулировку яркости свайпом по левому краю экрана. Проблема: в режиме вертикального скролла (VerticalPager) свайпер страниц перехватывает вертикальные жесты на себя.
Решение: перехватывать жест на фазе Initial — до того, как пейджер успевает его обработать. Если касание началось в левых 15% ширины экрана, событие забирается себе и до пейджера не доходит.
Помимо этого в релизе 1.3.0:
Добавлен полноэкранный просмотрщик иллюстраций с pinch‑to‑zoom (на основе detectTransformGestures).
Написан собственный File Picker со сканированием вложенных папок и извлечением книг прямо из ZIP‑архивов на лету.
Добавлен поворот страниц для PDF с сохранением состояния в SharedPreferences.
Разделен UI верхнего меню: закладки теперь можно переименовывать, а тап по номеру страницы открывает быстрый переход.
Добавлен множественный выбор в библиотеке (массовое добавление на полки/удаление/скрытие).
Ссылки:
🤗 Привет всем!
😻 Обновление HalChat for Android: v1.0.2 (Vote or don't vote)
Что нового?
😌Теперь есть описание чатов.
🤖 Добавлено обновление данных чата.
😉 Добавлены метаданные файлов HD - мгновенное определение что за файл или какие размеры изображения.
😇 Доступны опросы/голосования в чатах.
Что исправлено?
🤓 Если получение или расшифровка пароля была прервана, то теперь он запросит заново, и пароль будет доступен в любом случае.
🫢 Теперь при получении пароля от чата, в списке чатов сообщения будут расшифроваться.
😎 Исправлена система файлов в чате, они будут отображаться всегда и в том числе изображения (исправлен баг).
До новых встреч!
Google Play: https://play.google.com/store/apps/details?id=halwarsing.net.halchatandroid
RuStore: https://www.rustore.ru/catalog/app/halwarsing.net.halchatandroid
HalChat Web: https://halch.at/c/tZgWWT
GitHub: https://github.com/halwarsing/HalChat/tree/dev
Собеседование. Часть 3: Магия коллекций в Kotlin — от лямбд до скрытых аллокаций
В прошлых частях мы препарировали алгоритмы и заглянули под капот хэш-таблиц. Сегодня поговорим о том, за что разработчики так искренне любят Kotlin — о коллекциях.
На собеседованиях этот блок вопросов — мой любимый детектор. Он отлично показывает, умеет ли человек отличать красивый синтаксический сахар от суровой реальности JVM.
Уровень 1: Синтаксический сахар и интерфейсы
Я спрашиваю: «В чем фундаментальная разница между List и MutableList в Kotlin?» Ожидаемый ответ: Kotlin изящно разделил интерфейсы на читаемые (read-only) и изменяемые. У List просто нет методов add или remove. Это спасает нас от случайных мутаций состояния, особенно когда мы гоняем данные в реактивном UI.
Далее переходим к функциям-расширениям. Кандидат пишет классическую цепочку: users.filter { it.age > 18 }.map { it.name }
Выглядит чисто. Здесь же я подкидываю вопрос про reduce и fold. Оба метода сворачивают коллекцию, но если кандидат бездумно использует reduce, я с легкой улыбкой спрашиваю: «А что будет, если список окажется пустым?» Будет больно и UnsupportedOperationException. Поэтому fold с его стартовым значением — выбор тех, кто хочет спать спокойно.
Уровень 2: Срываем покровы компилятора
Когда кандидат уверенно жонглирует лямбдами, пора заглянуть под капот. Я задаю вопрос: «А что конкретно создается в памяти, когда ты вызываешь listOf(1, 2, 3) или mapOf(1 to "A")?»
Многие верят в уникальную магию Kotlin. Но продвинутый разработчик знает, что мы всё еще живем в мире JVM.
mapOf() под капотом вернет java.util.LinkedHashMap. Но! Контракт Map не гарантирует сохранение порядка. Если вам критически важен порядок вставки, не надейтесь на реализацию «под капотом» — пишите linkedMapOf() явно, иначе в один прекрасный день обновление языка сломает вам логику.
listOf(1, 2, 3) вернет не обычный java.util.ArrayList. Он вернет внутренний класс java.util.Arrays$ArrayList. И разница тут колоссальная. Это fixed-size обертка над массивом. Если любитель хардкора решит сделать (list as MutableList).add(4), он моментально получит краш в рантайме.
Далее вопрос на засыпку: «Если мы в цикле вызываем filter { ... }, не убиваем ли мы Garbage Collector созданием анонимных классов для лямбд?» Отличный кандидат вспомнит про модификатор inline. Компилятор физически вставляет тело функции в место вызова, поэтому аллокации объектов лямбд не происходит.
Уровень 3: Экспертный взгляд и ловушка Sequences
Вроде бы inline нас спас? И да, и нет. Тут начинается территория архитектуры и производительности. Я возвращаю кандидата к его коду: users.filter { it.age > 18 }.map { it.name }
«Модификатор inline спас нас от лямбд, — говорю я. — Но что будет, если в списке 100 000 юзеров? Что произойдет с памятью?»
Эксперт должен увидеть угрозу: каждая стандартная функция (filter, map) создает новую промежуточную коллекцию. Сначала создастся список из 50 000 взрослых юзеров, а потом еще один из 50 000 их имен. Это колоссальная аллокация памяти.
«Как этого избежать?» Ответ: использовать Sequences — users.asSequence().filter {...}.map {...}.toList(). Последовательности работают лениво, протаскивая каждый элемент по цепочке поштучно, без создания огромных временных списков.
Финальная ловушка. Я спрашиваю: «Значит ли это, что нужно использовать asSequence() вообще везде?» Истинный эксперт скажет: Нет. У Sequence есть свои скрытые налоги: создание объектов-оберток (TransformingSequence, FilteringSequence) и накладные расходы на виртуальные вызовы next()/hasNext() для каждого элемента. На коротких списках обычная цепочка filter/map отработает быстрее. Бенчмарки показывают, что Sequence начинает выигрывать только на объемах примерно от 1 000 элементов (в зависимости от тяжести цепочки). Не стоит заниматься преждевременной оптимизацией там, где она не нужна.
Резюме
Коллекции в Kotlin невероятно удобны. Но за синтаксический сахар всегда нужно платить. Важно видеть, как человек переходит от слепого восторга красивым кодом к прагматичному пониманию
Помогите, кто чем может. Яндекс пробил дно
У меня айфон с маленьким экраном. Предпочитаю компактные модели, чтобы умещались в любом кармане. Это доставляет и некоторые неудобства. Например, часть приложений плохо работают, элементы интерфейса перекрывают друг друга, и из-за этого некоторые функции становятся недоступны. Иногда с такой проблемой я сталкивался в приложении Яндекс.Такси.
В связи с тем, что с продукцией Apple в России в последнее время ситуация постоянно ухудшается, планирую перейти на Android. Нашел подходящую модель на Яндекс.Маркете смартфон Conquest F3 Plus. Одна проблема — в этой модели экран еще меньше, чем у меня сейчас. Значит, есть риск, что приложения, которые глючили на старом смартфоне, вообще не смогут работать на новом.
С данным вопросом я обратился в поддержку Яндекса. Я был уверен, что получу точный ответ, будет работать приложение Яндекс.Go на интересующем меня устройстве или не будет. Ведь что может быть проще? Любой разработчик может, даже если не знает точно, в эмуляторе задать указанное разрешение экрана и прогнать тесты.
Ответом поддержки я был, мягко говоря, ошарашен. Ниже привожу текст нашего диалога со скринами.
Здравствуйте! Будет ли работать ваше приложение на вот такой модели смартфона?
https://market.yandex.ru/cc/9aPY2a
Разрешение экрана 540x1200.
Данную информацию вы можете уточнить в магазине приложений, из которого вы хотите скачать наше приложение
Я хочу купить новый смартфон, но мне нужно знать, будут ли работать нужные приложения. Ваше приложение очень плохо работает на экране с разрешением 1344x750. Хоть и работает. Какое разрешение поддерживает yandex.go? Будет ли оно работать на экране 540x200?
Данную информацию уточнить не получится, подсказать смогут в магазине приложений
Что значит, не получится? Напишите разработчикам, и они вам скажут.
Пожалуйста, обратитесь в магазин приложений для уточнения минимальных системных требований для корректной работы приложения
Какой магазин?
Магазин приложений вашего устройства

Итак, поддержка Яндекс.Go не смогла ответить на вопрос о системных требованиях собственного приложения. Неожиданно. У меня нет ни малейших идей, как такое стало возможно.
В связи с этим я решил обратиться за помощью к товарищам по отрасли. Может, кто-нибудь из читателей Хабра пробовал ставить Яндекс.Go на Android 12 с разрешением экрана 540x1200. Нормально работает?
Собеседование. Часть 2: От структур данных до магии Load Factor и data class’ов
В прошлом выпуске мы выяснили, как простая задача на разворот массива вскрывает понимание вычислительной сложности. Сегодня мы поговорим о структурах данных и специфике языка программирования.
Мой второй любимый блок вопросов плавно перетекает от базовых коллекций к особенностям Kotlin и внутреннему устройству хэш-таблиц. Я оцениваю знания градационно: от того, что должен понимать начинающий специалист, до глубокого видения платформы.
Уровень 1: Начинающие специалисты и базовые структуры
Начинаем с разминки. Я прошу объяснить разницу между Array, ArrayList и LinkedList. Это фундамент, без которого сложно двигаться дальше. Если кандидат понимает структурную разницу, я спрашиваю про скорость доступа к произвольному элементу (Time Complexity):
Array (Массив): Непрерывный блок памяти фиксированного размера. Чтение по индексу происходит мгновенно, вычислительная сложность O(1).
ArrayList: Умная обёртка над массивом, умеющая динамически расширяться (путем копирования элементов в новый массив при переполнении). Доступ по индексу также O(1).
LinkedList (Связный список): Элементы разбросаны в памяти, каждый узел знает только о своем соседе. Чтобы найти нужный элемент, нужно последовательно пройти по цепочке. Скорость доступа — O(N).
Если специалист отвечает на это уверенно, значит, базовое понимание Computer Science заложено верно.
Уровень 2: Переход к Kotlin
Дальше я меняю плоскость и перехожу к синтаксису. Вопрос: «В чем разница между обычным class и data class в Kotlin?» Ожидаемый ответ на этом этапе: data class из коробки генерирует полезные методы, избавляя разработчика от написания бойлерплейта. Компилятор самостоятельно создает equals(), hashCode(), toString(), метод copy() и componentN() для деструктуризации.
Затем я прошу уточнить целевое использование. Кандидат должен пояснить, что data class нужен для хранения данных (например, моделей из сети) или состояния UI. Главная особенность в том, что объекты data class’ов сравниваются по содержимому (значениям полей), а не по ссылке в памяти.
Уровень 3: Углубленное понимание платформы А теперь самое интересное — мы сплетаем теорию структур данных и специфику Kotlin воедино.
Я спрашиваю: «Отлично, data class переопределяет метод hashCode(). А для чего именно он нужен? Как он используется под капотом?» Здесь требуется рассказать про принципы работы HashMap или HashSet: Метод hashCode() возвращает число, определяющее, в какую «корзину» (bucket) внутреннего массива попадет объект. Если хэши совпадают (коллизия), применяется метод equals(), чтобы найти точный объект внутри этой корзины.
И: «Что такое Load Factor в хэш-таблице? И что произойдет, если мы установим его слишком высоким (например, 0.95)?»
Правильный ответ: Load Factor (по умолчанию 0.75) — это метрика того, насколько может быть заполнена таблица до автоматического увеличения её размера (rehash). Если установить высокое значение, корзины переполнятся. Возникнет лавина коллизий. В результате хэш-таблица внутри одной корзины деградирует в LinkedList! Скорость доступа падает до линейной O(N) (или O(log N) для деревьев в новых версиях), лишая структуру её главного преимущества.
Резюме: Алгоритмы и структуры данных — это, по сути, сухая теория. Для меня как для интервьюера гораздо важнее то, как человек применяет её на практике.
В мобильной разработке нам гораздо реже приходится реализовывать сложные алгоритмы с нуля, чем ребятам на бэкенде. Но у нас своя специфика — жесткие ограничения по ресурсам устройства. Я не требую энциклопедических знаний. Я задаю простые, последовательные вопросы, чтобы понять: осознает ли человек, что неверно выбранная коллекция может привести к жесточайшим просадкам UI, фризам и неконтролируему расходу памяти.
Именно умение связать теоретическую алгоритмику с физическими ограничениями мобильного устройства показывает мне, насколько специалист действительно готов к реальной коммерческой разработке.
Собеседование. Часть 1: Как простая задача на разворот массива вскрывает понимание Computer Science
За свою карьеру я провел сотни технических собеседований на самые разные грейды — от джунов до системных архитекторов. И делал я это в разных локациях: в России, Европе и США. Процессы найма везде немного отличаются, но есть подходы, которые работают безотказно в любой точке земного шара.
Многие кандидаты боятся алгоритмических секций, ожидая зубодробительных задач с LeetCode. Но моя цель — не завалить, а понять инженерное мышление. Поэтому я часто начинаю с элементарной задачи: дан массив чисел, его нужно отзеркалить (перезаписать в обратном порядке).
Эта задача — идеальная «лесенка», раскрывающая реальный уровень инженера. Давайте пройдем по ней вместе.
Шаг 1: Уровень Джуниора. Просто сделай это
От джуна я жду умения перевести бизнес-требование в код. Самый очевидный способ решить задачу в лоб — создать второй массив и скопировать туда элементы с конца.
fun reverseArrayNaive(arr: IntArray): IntArray {
val result = IntArray(arr.size)
for (i in arr.indices) {
result[i] = arr[arr.size - 1 - i]
}
return result
}
Если код написан без ошибок с индексами — отлично. Если человек путается и не может подойти к задаче — для меня это красный флаг. Если код готов, я задаю первый вопрос: «Какова вычислительная сложность?». Ожидаемый ответ: сложность O(N) по времени, так как мы проходим массив один раз.
Шаг 2: Уровень Мидла. Экономим память
Переходим на следующий уровень. Я спрашиваю: «А что со сложностью по памяти?». Кандидат логично отвечает, что раз мы создаем массив того же размера, сложность — O(N).
Усложняем задачу: «Представь устройство с жестким лимитом ресурсов. Нам нельзя выделять память под второй массив. Как переписать алгоритм, чтобы сложность по памяти стала O(1)?»
Продвинутый разработчик сразу предложит in-place решение: менять элементы местами с начала и с конца.
fun reverseArrayInPlace(arr: IntArray) {
var left = 0
var right = arr.size - 1
while (left < right) {
val temp = arr[left]
arr[left] = arr[right]
arr[right] = temp
left++
right--
}
}
Шаг 3: Уровень Синьора. Психологическая ловушка
Если in-place вариант написан, я подкидываю вопрос с подвохом: «В первом варианте цикл делал N итераций. Во втором указатели встретились посередине, то есть цикл выполнился N/2 раз. Уменьшилась ли вычислительная сложность по времени?»
И тут многие радостно отвечают: «Да! Мы сократили операции в два раза, код стал быстрее!». И это ловушка.
Правильный ответ: Нет, сложность осталась O(N). Давайте посчитаем атомарные операции присваивания:
В наивном подходе мы делали 1 присваивание за итерацию. Цикл шел N раз. Итого: N операций.
В in-place подходе мы делаем swap. Это три операции (temp = a, a = b, b = temp). Цикл идет N/2 раз. Умножаем 3 на N/2 и получаем 1.5 × N операций!
С точки зрения процессора мы не сэкономили время, а совершили даже больше базовых действий. Мы просто обменяли такты на память. В нотации Big O константы отбрасываются, поэтому оба алгоритма линейные. Но синьор обязан видеть код насквозь, понимая его цену на уровне регистров.
За 10 минут с помощью одной задачи мы проверили:
Умение писать циклы (Джун).
Понимание Big O и расхода памяти (Мидл).
Понимание реальной цены оптимизаций (Синьор).
Это не спортивное программирование с хитрыми математическими трюками. Это проверка базовой инженерной гигиены.
TalkBack в 2ГИС
Уже несколько лет мы поддерживаем VoiceOver на iOS — это был сложный и многоступенчатый путь к доступности приложения. После релиза мы увидели, что это реально работает, и людям с нарушением зрения стало проще пользоваться 2ГИС. В этом году мы решили, что пришло время для Android.
Под капотом — собственный мини-фреймворк
Дальше рассказывает ведущий разработчик Дмитрий Торопчин.
Когда начинаешь делать доступность под Android, кажется, что всё уже предусмотрено. Каждый View знает, как предоставить своё описание для служб доступности, как выполнять действия и какие события отправлять после их выполнения.
2ГИС устроен немного иначе: наш интерфейс написан на Qt Quick и с точки зрения Android это один пустой View. Никаких кнопок, списков и текстовых полей TalkBack «внутри» не видит. Сам Qt предоставляет кроссплатформенный API для работы со службами доступности из Qt Quick, но его платформенная интеграция для Android на данный момент поддерживает весьма ограниченный набор методов, которого категорически не хватает для разработки по-настоящему удобных и доступных приложений.
Поэтому мы решили написать свой мини-фреймворк для работы со службами доступности Android из Qt Quick. Основную архитектуру мы подсмотрели в Qt:
визуальные элементы в Qt Quick размечаются через attached-свойства;
из этой разметки получается дерево accessibility-узлов, управляемое из C++;
это дерево accessibility-узлов предоставляется службам доступности Android через virtual view hierarchy посредством JNI.
При этом мы добавили поддержку многих недостающих API из Android SDK:
научились работать с текстовыми полями ввода;
научились описывать коллекции элементов;
поддержали автоматическую прокрутку списков при перемещении accessibility-фокуса;
поддержали детализацию навигации по тексту: озвучку любого элемента можно прослушать как целиком, так и по словам или по символам.
Пользовательские сценарии
Мы честно признали, что озвучить всё приложение сразу невозможно. Поэтому сделали MVP. Теперь можно:
найти место и узнать актуальную информацию о нём;
прослушать карточку организации и быстро сориентироваться в деталях;
позвонить по контактному номеру прямо из карточки;
построить маршрут и пройти его шаг за шагом;
понять, какой транспорт подходит, где садиться и на какой остановке выходить.
Для этих сценариев мы разметили 555 UI‑элементов.
Мы продолжим собирать обратную связь, будем расширять сценарии и думать, как сделать автоматическую проверку доступности в тестах.
Кому нужен качественный и бесплатный движок синтеза речи в Андроид (работает оффлайн, на уровне системы) ?
Недавно в Гугл Плей появилось приложение
BookFusion Voice
которое даёт возможность установить нейро-голоса Piper в качестве системных. Соответственно, они будут доступны в любых приложениях.
Русских голосов 4, но хорошего качества из них один- “Irina” (звучит заметно лучше, чем стандартные оффлайн-голоса от Гугл ).
Есть небольшая проблема- если установить несколько голосов, то могут быть проблемы с выбором конкретного (это зависит от приложения, которое использует синтез).
5 бесплатных уроков марта для мобильных разработчиков

12 марта 20:00
>> Профессиональные модульные тесты в Android: как тесты улучшают код
Открытый вебинар курса «Android-разработчик. Продвинутый уровень»
Урок о том, как писать в Android осмысленные модульные тесты для ViewModel, репозиториев и бизнес-логики, чтобы они не маскировали проблемы, а реально улучшали архитектуру и поддержку кода. Записаться на урок
18 марта 20:00
>> Пишем простой проигрыватель на SwiftUI
Открытый вебинар курса «IOS-разработчик»
Соберете на SwiftUI простой медиапроигрыватель с интерактивным интерфейсом, освоите работу с локальными аудио- и видеофайлами в iOS и наметите путь к интеграции внешних сервисов. Записаться на урок
19 марта 20:00
>> Современная архитектура приложения и внедрение зависимостей
Открытый вебинар курса «Android-разработчик. Продвинутый уровень»
Разберемся, как выстроить Android-приложение на основе чистой архитектуры, связать слои через MVVM и настроить внедрение зависимостей с помощью Koin без лишней магии. Записаться на урок
23 марта 20:00
>> Навигация Pro-уровня в SwiftUI: как строить масштабируемые iOS-приложения без хаоса в переходах
Открытый вебинар курса «IOS-разработчик. Продвинутый уровень»
Как в SwiftUI проектировать навигацию без архитектурного хаоса: отделять переходы от интерфейса, управлять deep link и модальными экранами, строить масштабируемую структуру приложения. Записаться на урок
25 марта 20:00
>> Как писать Flutter-код так, чтобы ИИ правильно его дописывал
Открытый вебинар курса «Flutter-разработчик»
Поймете, почему искусственный интеллект ошибается при генерации Flutter-кода, и освоите приёмы, которые улучшат подсказки, повысят читаемость проекта и ускорят дальнейшую разработку. Записаться на урок
Еще больше бесплатных уроков от преподавателей курсов по всем ИТ-направлениям можно посмотреть в календаре мероприятий.
Открытый проект WebToApp позволяет превратить сайт в полноценное Android‑приложение прямо на саартфоне без ПК, Android Studio или знаний кодинга. Можно сделать приложение из обычного HTML‑сайта, React, Vue или Next.js. Также можно добавить иконку, включить блокировку рекламы и защиту приватности, тёмную тему, медиа‑инструменты и даже собственные скрипты.

Для Android вышло приложение‑брандмауэр ShizuWall, которое делает смартфон безопаснее:
умеет полностью отключать доступ к интернету для выбранных приложений;
допускает к сети только избранные приложение;
запрещает фоновую интернет‑активность нежелательных приложений;
при этом никаких VPN‑туннелей и Root‑прав не требуется — всё работает из коробки;
бесплатно приложение на Kotlin доступно на GitHub, есть версия и в Google Play.

Как ускорить прогон с 3 часов до 12 минут?

Когда в Android-проекте ≈800 модулей и 37 000 unit-тестов, полный прогон на CI легко превращается в полдня ожидания. У нас было ровно так: больше 3 часов на полный запуск — и ощущение, что локально это вообще не вариант. А потом команда нашла настоящие причины тормозов и довела прогон до 12 минут.
В статье «37 000 unit-тестов против Gradle: как мы добились 12-минутного прогона» конкретная инженерная история без магии. Приглашаем к чтению Android-разработчиков, техлидов и тех, кто отвечает за CI/скорость поставки. Тут много идей, которые можно примерить на себя!
Делитесь вашими подходами к решению проблем производительности в комментах)
Ахиллесова пята SharedPreferences
Статья про то, о чём не спрашивают на собесeдованиях и не рассказывают на курсах по Android-разработке — о неявной особенности Android, которая влияет на деградацию производительности и приводит к невоспроизводимым ANR в вашем приложении.
SharedPreferences часто используют «по привычке» — сохранить токен, флажок, пару строк. Но в какой-то момент это начинает тормозить интерфейс и даже приводить к ANR, особенно если запись/чтение происходит не там и не тогда, где вы ожидаете. Автор делится измерениями производительности, показывает, как деградация превращается в потерю кадров при переходах между экранами, а затем сравнивает варианты.
Эта статья будет особенно интересна Android-разработчикам и тимлидам, которые уже сталкивались с мистическими ANR, просадками перформанса и фризами на слабых девайсах, а также тем, кто держит в приложении много сторонних SDK и хочет понимать, как неявные записи в SharedPreferences могут незаметно копить нагрузку.
Читайте статью «Ахиллесова пята SharedPreferences и стоит ли внедрять Datastore как альтернативу»
Клиент YouTube для Android под названием Download YT PRO весит всего 60 кБ (48 кБ в архиве). Приложение не требует Root-прав, убирает рекламу, даже спонсорскую. Видео не ставится на паузу, если свернуть приложение или заблокировать экран. Есть встроенный загрузчик видео и шортсов. Добавлен ИИ Gemini, который сразу сделает саммари даже часовых лекций и выдаст факты и советы по контенту.

Не нравится скроллить длинные тексты, поэтому искал веб-браузер для Андроид, в котором можно перелистывать касаниями. Поиск и нейросети подсказали несколько вариантов, из которых часть оказалась устаревшей или просто ошибочной. К примеру- в Mozila Firefox была такая встроенная возможность , но её убрали.
С остальными дело такое-
У Firefox есть много расширений, среди них нашёл подходящий режим чтения с перелистыванием. Однако оно работало плохо .
EinkBro. Его пришлось ставить из APK. Тоже глючил.
UC Browser. Обещают такую функцию. Из Гугл Плей его удалили, но в магазине Xiaomi он есть. Среди разрешений требует возможность изменять системные настройки. Поэтому решил не устанавливать.
4. Наконец нашёл Via Browser. Очень маленький, но с богатыми настройками, среди них можно назначить на "длительные нажатия" на стандартные элементы интерфейса( к примеру, на "вперёд") разные действия на выбор. Среди них есть и перелистывание.
Кроме того Via поддерживает скрипты и в режиме чтения очень хорошо сохраняет уже переформатированный текст( с крупным шрифтом) в PDF и MHT( даже сложные статьи с Хабра).
В Telegram заработала система входа в аккаунт через Passkey, но только для российских номеров телефона. Ключевое преимущество Passkeys — возможность войти в аккаунт в одно касание, не вводя номер телефона и одноразовый код.
Как создать ключ:
Убедитесь, что у вас последняя версия мессенджера (Android — 12.2.10; iOS — 12.2.3).
Как и вход по почте, новую функцию нужно предварительно настроить. Для этого откройте Настройки › Конфиденциальность › Ключи доступа.
Если пункт «Ключи доступа» отсутствует, то эта опция недоступна для вашего аккаунта. На текущий момент Passkeys доступны только для аккаунтов, к которым привязан российский номер.
Нажмите «Добавить ключ доступа» и подтвердите его создание.
Устройство может запросить код экрана блокировки или биометрию, чтобы разблокировать хранилище ключей.
Созданный ключ появится в списке.
Как войти с помощью ключа:
На актуальной версии Telegram для Android или iOS приложение автоматически предложит выбрать ключ доступа для входа.
Если это не происходит, через несколько секунд под заголовком «Номер телефона» появится ссылка «используйте ключ доступа», на которую следует нажать.
Нажатие на кнопку запустит ваш менеджер паролей, который предложит выбрать ключ, проверит вашу личность по лицу, отпечатку пальца либо PIN-коду экрана блокировки, а затем передаст выбранный ключ мессенджеру.
Ключ доступа выполняет функции как номера телефона, так и одноразового кода подтверждения одновременно.
Если вы включили двухфакторную авторизацию для аккаунта, вам потребуется ввести свой облачный пароль, который вы задали самостоятельно.

В Telegram появилась опция авторизации через ключи доступа. Новая функция для Android и iOS под названием Passkey позволит входить в аккаунт без дополнительных подтверждений в виде СМС-кодов и паролей. Активировать ключи доступа можно в разделе «Конфиденциальность». Чтобы подключить функцию, нужно создать ключ и подтвердить личность с помощью сканирования лица (Face ID), отпечатка пальца (Touch ID) или код-пароля. Созданный Passkey будет храниться на устройстве. Функция поможет обойти ограничения при регистрации в мессенджере.

Здравствуйте, уважаемые читатели. Обращаем ваше внимание, что в блоге SSP-Soft вышел детальный обзор нашей новой книги о технологии Jetpack Compose для Android. Jetpack Compose (в книге разобрана версия 1.6) - это передовой инструментарий для Kotlin-разработчиков, предназначенный для проектирования и модернизации пользовательских интерфейсов, рассчитанных именно на работу с мобильными устройствами. В книге также рассмотрены основы языка Kotlin для Android и работа с Android Studio. Заказывайте книгу у нас на сайте и читайте с удовольствием!
P.S. Эта книга - одна из наших лучших находок в области англоязычного самиздата, однако нас в целом интересует тема разработки на Kotlin. Если у вас есть гитхаб с черновиками, либо вы прямо сейчас готовите рукопись - не стесняйтесь написать об этом Валентину Холмогорову @Holmogorov, Олегу Сивченко @OlegSivchenkoили просто в личные сообщения в этом блоге.
Спасибо вам за ваш интерес и Сергею Березину @sergbeза вышеупомянутую рецензию.
Как я сделал blur и линзу в Jetpack Compose

Всем привет! Меня зовут Владимир, я мобильный разработчик в «Финам». В одном из недавних проектов нужно было добавить в интерфейс Jetpack Compose визуальные эффекты поверх контента, например размытый хедер или движущуюся «лупу».
Обычно такие приемы встречаются в играх, где весь экран — это фактически полотно для рисования OpenGL. В классической XML-разметке UI я с таким не сталкивался, поэтому пришлось довольно глубоко погрузиться во внутреннюю кухню Compose. Этот разбор может быть полезен тем, кто решает похожие задачи.
Сначала на Stack Overflow я нашел неплохой пример создания эффекта размытия на определенном участке экрана — к сожалению, это решение не было универсальным и зависело от верстки. Однако мое внимание привлекли два класса из фреймворка: RenderNode и GraphicsLayer.
Если коротко, можно захватить часть экрана через GraphicsLayer, а в RenderNode записать контент. Но перед этим его можно обработать. После обработки метод drawWithContent() выводит результат в canvas.
Сначала я попытался модифицировать эффект размытия из ответа на Stack Overflow, затем сделал размытие в форме круга, который движется вслед за пальцем, и постепенно пришел к окончательному варианту с движущейся прозрачной линзой. Код для отрисовки эффекта я показал в статье.
В результате можно получить эффект линзы, которая будет перемещаться за пальцем, если водить им по экрану.
Какие выводу я могу сделать:
в Compose можно делать крутые визуальные эффекты, если покопаться в RenderNode;
это неочевидный, но мощный инструмент, он дает простор для кастомизации.
Мой пример не самый изобретательный, но способ, который я показал, открывает почти безграничные возможности для реализации визуальных эффектов в Android-разработке, чем мы в «Финам» и пользуемся очень активно в наших финтех-проектах. Итоговый результат оформил в GitHub-репозитории — берите и пробуйте в своих проектах.
Открываем набор на бесплатное обучение по IT-направлениям по программе Альфа-Будущее Кампус
Открываем новый набор на обучение по разным IT-направлениям в рамках образовательной программы Альфа-Будущее Кампус:
Сроки программ — от 3 месяцев до полугода, за которые вы научитесь создавать современные цифровые продукты под руководством ведущих экспертов Альфа-Банка в разработке, системной аналитике, QA и бизнес-аналитике.
Выбирайте курс, переходите по ссылке, изучайте программу и подавайте заявку. Успейте присоединиться до 27 октября! ❤️
Обучение бесплатно.
А если хотите узнать, есть ли польза от курсов, как проходит обучение (на примере факультета аналитики), и подойдет ли вам онлайн-обучение IT-специальности, читайте нашу статью по ссылке ниже. На примере факультета системной аналитики рассказали, как готовится программа, как проводятся собеседования с людьми и помогло ли ученикам обучение:
Разбор полётов: вся правда о падениях Android-приложений

Почему Android-приложение вылетает с ошибкой и что на самом деле происходит под капотом? Абакар, главный технический лидер разработки в Альфа-Банке, раскрывает наглядно и увлекательно этапы срабатывания необработанных исключений: от простого деления на ноль до глубоких слоёв архитектуры Android
В статье «Почему моё Android-приложение крашится?» — понятные пошаговые разборы, примеры реальных трейс-лодов, ссылки на исходный код Android и даже инсайты, почему приложение лучше сразу завершить, чем пытаться спасти после критической ошибки.
Идеально для разработчиков, которые хотят разобраться в механике аварийных завершений своих приложений, настроить обработку ошибок и узнать, как глобальные обработчики реально работают в Android.
Вспомнил холивары на первой работе на тему: что такое Activity?
Тогда, среди Android-разработчиков, в моде была MVC и общение было примерно такое:
"Activity - это контроллер" - говорили одни.
"Activity - это вью" - говорили другие.
"Activity - это модель" - так к сожалению никто не говорил, иначе было бы еще интереснее 😁
Позиции противоположные и бескомпромиссные, противостояние зацикливалось и вызывало бурю эмоций. Пока не договорились (читай как одни продавили других)
Кто из них прав?
Никто.
Или и те и другие.
Правильный же ответ такой:
Я создатель приложения и какую роль я дам этому классу(Activity) такую он и будет выполнять.
Это если смотреть со стороны архитектуры приложения.
А если смотреть со стороны OS Android, то Activity - это интерфейс через который пользовательское приложение взаимодействует с операционной системой.
Вот и всё :)
P.S. А какие холивары вспоминаются вам?)
Пост о том как я разработчиков нанимал, история из жизни :)
Однажды мне написали из IT-компании что занимается разработкой решений для бизнеса под ключ, в общем аутсорс, и попросили меня помочь им с наемом.
У меня большой опыт в мобильной разработке, в основном нативный Android приправленный мультиплатформой на Compose Multiplatform и Flutter.
Руководитель компании попросил меня помочь сформировать команду Android-разработки на доработку и поддержку достаточно масштабного и сложного проекта X.
Проект тащил свое приятное но от того не менее сложное Legacy: многомодульность, Clean Architecture, DI, MVVM, Kotlin, Coroutines, Flow работа с БД и клиент-серверное взаимодействие - в общем популярный набор современного бизнес-приложения.
Строго говоря у меня было две задачи:
1. Помочь джуниору разобраться в проекте чтобы он мог уже что-то делать
2. Набрать команду нативных Android-разработчиков
Условия усложнялись тем, что компания что до этого разрабатывала проект не очень-то шла на контакт и обе стороны принимали друг-друга в штыки. По этому сам проект и задачи передавались со скрипом и негативными эмоциями, "проект теперь ваш, вот вы и разбирайтесь, мы сделали конфетку, там все понятно" - говорили одни, "у нас команда профессионалов и задачи задерживаются потому что вы написали чрезмерно сложный недо-код" - говорили другие, а владелец продукта говорил- "ничего не волнует делайте как хотите за все уплочено" (бизнес и конкуренция как ни крути 😁)
На тот момент у компании уже было пару молодых Android-джуниоров и мидл на других проектах, плюс один на проекте X, и хотелось нанять еще крепких мидлов, сеньера, и парочку стажеров на проект. Эмоции эмоциями, а работа должна быть сделана, и чтобы ее сделать нужны люди способные её сделать за приемлемый оклад и другие печеньки от компании.
Ситуацию в целом обрисовал.. (хотя ситуация конечно масштабнее там еще и менеджмент с загонкой джунов и эйчар структура направленная на формирование командного духа и внутреннего обучения.. ( как я вижу из своего опыта и то и другое скорее мешает чем помогает в моменты когда надо работу работать и должно быть сделано еще вчера ). Вот хорошая метафора пришла: ситуация как в "Лебедь, Рак и Щука", воз - это работа, а все остальные это Лебедь, Рак и Щука.)
Идем дальше:
Эйчары стали лить на меня трафик из соискателей на должность Android-разработчика. И на протяжении 2-х недель я каждый день собеседовал по 2-3 кандидата, иногда 1-го. Уровень у всех очень отличался. Приходили как совсем свежие пирожки прямо после курсов, так и уже матерые обкатанные на серьезных проектах колобки. У меня был стек проекта и потому вопросы тоже считай были сформированы, диалог строился так: Привет-привет, расскажи про свой опыт, и от этого опыта погнали ветвиться по темам в сторону нужных компетенций и стека. С кем-то укладывались в 30-минут, с кем-то залипали на 2 часа. Т.е. с кем-то шли дальше в глубину, а с кем-то прошли по верхам и на этом все.
По одному только времени общения можно судить про уровень подготовки, если бы не одно но - попадались ребята подкованные на язык, они хорошо отвечали по темам, но это все пустые слова из документации не подкрепленные реальным опытом.
Чтобы как-то передавать то что я узнавал в процессе собеседования о кандидатах, мы ввели оценку, грубо говоря число от 1 до 5, насколько человек подходит на проект + моя рекомендация. Дальше уже другие люди делали какой-то оффер на работу, или не делали.
В итоге за 2 недели мы наняли 5 человек, + тот разработчик что изначально был на проекте с моей (и божьей) помощью вкатился в проект и начал работу по задаче.
На этом история не закончилась, их ждал неизбежный онбординг, притирка и командообразование, но это уже совсем другая история..
Я еще пару недель занимался онбордингом на проекте + выступал адвокатом разработчиков + переводчиком с менеджерского на разработческий и обратно, та еще задачка 😁.
1-н нанятый стажер в итоге не потянул и отвалился, остальные 5-ро продолжили работу, уже без меня, а я отправился дальше :)
Проблема: Кадровый голод по специалистам, и при этом рекордные количества откликов на вакансии.
Причина: Плохая воронка наема специалиста (по аналогии с воронкой продаж, хорошие воронки способствуют продажам, а плохие нет), читай как - существующий процесс наема не помогает нанять специалиста, притом что специалистов на рынке более чем достаточно.
Общее решение: Изменить процесс наема так чтобы он помогал нанять специалиста для решения задач бизнеса.
Конкретные варианты решения:
Использовать зарекомендовавшие себя решения в других воронках\процессах получения чего-либо. Например сарафанное радио и нетворкинг, кумовщину и рефералки, при этом отказаться от существующих фильтров в пользу доверия на старте.
Устранить причину мешающую текущему процессу наема. Например сократить цепочку ЛПР-ов на пути соискателя до оптимума.
(Сейчас это авто-скрипт, эйчар(или ряд эйчаров), собеседующий специалист(или ряд специалистов), представитель команды(или ряд представителей), опционально тут еще какие-то посредники, и вот тут уже можно выйти на работу и работать 😁. Причем на каждом этапе у лже-ЛПР-ов есть цель отсеять человека на основании формального фильтра. Тогда как лучшие работники обычно "неформалы" ибо они про работу работать как Стив Возняк, а не про продукт(себя) продавать как Стив Джобс. Очевидно что не каждая птица долетит даже до середины.. 😢)
Оптимум - это ван ту ван, один ЛПР-соискатель на одного ЛПР-нанимателя. Собеседовать можно сколько угодно, но в конце один ответственный человек собирает всю информацию в кучу (и это не оценки и выжимки специалистов, а прям сесть и посмотреть портфолио, видео собеседования, пересказ нейронки прочитать как минимум по этому видео, переписку, на свою текущую ситуацию по задачам и срокам посмотреть и т.д.) и на основе всех имеющихся данных принять полноценное решение.
ЛПР - это тот кто принимает решение что считает лучшим на этот момент, а не тот который работает по прописанному скрипту (иначе это не ЛПР, а человек которого настоящий ЛПР назначил отрабатывать строго по скрипту 😜).
Устранить еще одну причину мешающую процессу наема. Например монолитность условий для прохождения собеседования. Можно искать пол жизни рыцаря на белом коне, до момента когда ни конь ни рыцарь уже будут не нужны, а можно взять то что само приползло и докрутить его под свои хотелки уже здесь и сейчас (то что само приползло должно быть согласно на докрутку, чтобы ни одно живое существо не пострадало в процессе наема 😁).
P.S. Все 3 варианта решения на самом деле про одно и то же, только заход с разных сторон: убрать не то что мешает обрабатывать заявки на чиле, в пол уха, левой пяткой, а убрать то что действительно мешает нанять сотрудника для решения конкретных задач. (Да стало много спама, ну и что? Разве то что много спама говорит о том что среди спама нет специалистов способных делать работу? Нет. Как раз таки в этом и заключается задача\работа нанимателя - нанять сотрудника в этих конкретных условиях. Ну да, придется поработать. Соискатель, наниматель и работодатель в одной лодке как ни крути, если кто-то не хочет грести, то далеко ли уплывет такая лодка?🛶)
Хорошего дня, наема и трудоустройства!
Обнял 🤗
Дело не в том как ты проходишь собеседование
Дело в том хочет человек тебя нанять или нет
Т.е. проходишь ты дальше или нет основывается не на твоих желаниях и знаниях, а на желаниях и знаниях собеседующего
Так что не парься, будь счастлив 😁
Воспринимай собеседования не как путь именно к этой работе, а как путь к чему-то вообще :)
В любом случае этот шаг делает тебя на шаг ближе к цели
Если ты не прошел собес, то выбор простой: сражайся с этим или наслаждайся этим, и даже если сражаешься, то насладись сражением 😁
P.S. И так во всём..
Я проснулся, делал зарядку и размышлял о поиске работы, и вот что я осознал:
Эйчары не понимают бизнес.
Бизнес не понимает эйчаров.
Эйчары не понимают технарей.
Технари не понимают эйчаров.
Бизнес не понимает технарей.
Технари не понимают бизнес.
Как следствие - эйчары нанятые бизнесом чтобы нанять непонятных для них технарей, нанимают еще хуже чем нанял бы бизнес, потому что эйчары не понимают ни бизнес, ни технарей.
Делегирование делегированием, а эффективность эффективностью.
Еще:
Бизнес конкурирует с бизнесом.
Эйчары конкурируют с эйчарами.
Технари конкурируют с технарями.
И все конкурируют со всеми.
Как следствие - на технических собесах технари с обеих сторон стремятся доминировать друг над другом. Так как решение о наеме принимает собеседующая сторона, то она имеет преимущество над соискателем и ему приходится подстраиваться.
Как следствие, на технических собеседованиях не может быть никакой объективной оценки, идеальный кандидат здесь - не человек который решает реальные задачи, а человек который идеально подстраивается под собеседующих.
И служит такое техническое собеседование больше не для того чтобы помочь бизнесу решить свои задачи, а для того чтобы не пустить сильных конкурентов в команду и набрать более слабых и лояльных конкурентов, согласных и похожих на человека собеседующего. Это работает на подсознательном уровне.
Выводы:
Собеседования которые проводит не "бизнес" - не помогают бизнесу найти нужных людей, а наоборот мешают ему это сделать.
Бизнес который делегирует наем рано или поздно оказывается в положении "свой среди чужих, чужой среди своих", фактически самым ценным ресурсом компании - людьми - теперь владеет не Бизнес, а Технарь и HR.
Такие дела.
Вот еще интересные мысли о наеме, с которых все началось:
Бизнес строит воронку наема, из эйчаров и технарей, в несколько этапов, и вроде все логично, происходит отсев неподходящих и выбор подходящих.
Первичный отсев простейший и почти не требует затрат. Затраты начинаются на этапе собеседований. Фактически компания тратит силы, время и деньги на каждое собеседование.
Понимают ли они то что происходит во всей полноте? Похоже что нет, иначе процесс был бы устроен другим образом.
А происходит следующее: соискатель приходит на собеседование, обучается на нем правильно отвечать на вопросы, если проходит то устраивается на работу, если не проходит, то идет на следующее собеседование, и так пока не устроится.
Т.е. фактически компания которая дала отказ соискателю, затратила ресурсы, обучила соискателя, и передала его другой компании. Вот что происходит на самом деле.
В итоге человек все равно будет работать и решать технические задачи что ставит компания, иначе он и не пошел бы на собес в эту компанию, но, делать это он будет в другой компании.
А теперь вернись к началу, вероятно теперь станет понятно откуда взялись такие тезисы :)
P.S. Похоже всё это не является проблемой только потому, что получается микро-коммунизм в наеме. Одна компания обучает соискателей для другой, и та в свою очередь делает так же. По сути работает не система наема в компании, а побочные эффекты от нескольких систем наема разных компаний.
Откуда такие мысли:
Я уже 12 лет как в теме, и устраивался на работу сам, и искал людей на свои проекты, и собеседовал как технарь(в Android разработке) на проекты бизнеса. В общем я пощупал этого слона с разных сторон, и теперь когда я снова в активном поиске - это сложилось в объемную картину.
WhatsApp сканирует сеть?
Совершенно случайно наткнулся на интересное:
У меня дома стриггерился алерт: мой домашний сервачок (он же роутер) помимо всего прочего отслеживает количество уникальных от-forward'енных $src_ip + $dst_ip + $dst_port – и алертит, когда их количество превышает некоторый порог.
И вот за последние сутки с моего телефона + телефона жены 2560 + 4082 уникальных пар $dst_ip + $dst_port (где 602x22 ниже – это соединения на 22 порт на 602 разных IP-адреса):
kate-mobile.lan (4082 IP+port pairs): 3117 TCP (1534x443, 602x22, 261x80, 237x554, 220x53, 29x23, 28x983, 21x553, 20x179, 12x1443, 9x5222, 6x5228, 4x4460, 4x21, 2x571, 2x9243, 2x240, 2x383, 2x185, 2x260, 2x299, 2x237, 2x336, 2x131, 2x512, 1x464, 1x734, 1x4416, 1x371, 1x10, 1x863, 1x895, 1x759, 1x815, 1x178, 1x830, 1x271, 1x838, 1x707, 1x629, 1x174, 1x1003, 1x894, 1x3237, 1x887, 1x962, 1x603, 1x855, 1x241, 1x494, 1x540, 1x181, 1x352, 1x454, 1x373, 1x654, 1x56, 1x646, 1x175, 1x876, 1x810, 1x556, 1x395, 1x483, 1x697, 1x212, 1x34, 1x588, 1x348, 1x605, 1x680, 1x460, 1x401, 1x224, 1x143, 1x161, 1x104, 1x655, 1x872, 1x521, 1x459, 1x911, 1x705, 1x317, 1x377, 1x807, 1x323, 1x893, 1x866, 1x142, 1x1001, 1x170, 1x920, 1x843, 1x209, 1x463, 1x156, 1x569, 1x952, 1x701, 1x184, 1x597, 1x389, 1x647, 1x8543, 1x487, 1x624, 1x537, 1x814, 1x259, 1x578, 1x26, 1x904, 1x751, 1x652, 1x795, 1x234, 1x671, 1x45, 1x4477, 1x307, 1x635, 1x651, 1x227, 1x806, 1x752, 1x203, 1x220, 1x582, 1x568, 1x153, 1x844, 1x402), 965 UDP (379x443, 278x53, 116x554, 83x123, 38x22, 22x23, 7x2002, 6x983, 4x179, 2x4123, 2x512, 2x21, 2x553, 1x363, 1x652, 1x654, 1x1003, 1x299, 1x307, 1x377, 1x680, 1x807, 1x804, 1x966, 1x685, 1x240, 1x463, 1x655, 1x806, 1x45, 1x383, 1x336, 1x153, 1x260, 1x28, 1x241, 1x603)
mobile.lan (2560 IP+port pairs): 1899 TCP (814x443, 405x22, 171x554, 168x80, 160x53, 23x983, 18x179, 18x1443, 15x23, 10x553, 9x5222, 6x21, 5x7275, 3x5228, 2x19302, 1x759, 1x37, 1x629, 1x685, 1x581, 1x582, 1x10000, 1x142, 1x250, 1x846, 1x125, 1x872, 1x657, 1x8543, 1x604, 1x90, 1x727, 1x567, 1x911, 1x739, 1x810, 1x4477, 1x866, 1x26, 1x491, 1x10, 1x156, 1x626, 1x178, 1x422, 1x977, 1x155, 1x12, 1x402, 1x683, 1x21007, 1x306, 1x595, 1x184, 1x4416, 1x472, 1x14, 1x904, 1x166, 1x165, 1x753, 1x988, 1x4434, 1x11, 1x28, 1x317, 1x622, 1x535, 1x718, 1x686, 1x637, 1x207, 1x244, 1x153, 1x7000, 1x8443, 1x966, 1x383, 1x5223, 1x985, 1x161, 1x994, 1x395, 1x898, 1x39, 1x592, 1x6447), 661 UDP (307x443, 168x53, 81x554, 36x123, 24x22, 10x23, 6x983, 6x179, 4x19302, 3x553, 3x21, 1x153, 1x685, 1x626, 1x155, 1x592, 1x19000, 1x491, 1x306, 1x472, 1x125, 1x8443, 1x28, 1x966)
Поставил себе PCAPdroid на телефон, и выяснилось, что WhatsApp (я им совсем не пользуюсь – установлен по необходимости):
За последний месяц съел 23 MB Wi-Fi трафика.
За сегодняшний день съел 92 MB Wi-Fi трафика.
Постоянно открывает соединения на разные IP и всякие мутные порты (ssh, ntp, ftp).
Хотелось бы верить, что это какая-то очередная защита от блокировок или вроде того, но, учитывая недавние истории про слежку за пользователями на Android, что-то не очень верится. :)
Как-то более глубоко исследовать эту ситуацию, честно говоря, нет желания (да и наверняка он подобными сканированиями занимается только изредка, чтобы не привлекать к себе лишнего внимания) – поэтому всё выше написанное исключительно JFYI, без каких-либо интересных подробностей.
P.S.: Большая просьба не воспринимать это как очередную рекламу в пользу всем известного мессенджера, который сейчас активно продвигается – все совпадения случайны.
Кормак Хэйден — владелец Oasis, приложения для iPhone и смартфонов на Android, которое публикует якобы научно обоснованные рейтинги воды и фильтров, опираясь на результаты лабораторных тестов и открытые данные. Плату берут за, как утверждается, доступ к части функций, чтобы финансировать независимые (без рекламы) анализы. На сайте проекта ведётся раздел с рейтингами бутилированной воды и фильтров, поиск по водопроводной воде по городам США, а также возможность заказать домашние тест-наборы для отправки проб в лабораторию.
В личном микроблоге Хэйден опубликовал лаконичный пост. В нём он в три слова и две кавычки пожаловался, что его давно просили сделать приложение для Android, но финансовый результат Кормака разочаровал.

В комментариях Хэйдену указали, что кнопка покупки на Android попросту была сломана. Кормак ответил, что локально на его машине всё работает. На самом деле ситуация ещё более смешная.
Оплата на Android в Oasis действительно сломана, это так. Однако в регионе США всё работает, указывает Хэйден. Это будет относительно легко пофиксить. Забавно именно то, что поправить уже нельзя: база данных данных Oasis крайне похожа на открытую закраудсорсенную базу данных OpenFoodFacts, а схожие же функции даёт бесплатное приложение Yuka. Кстати, Oasis по дизайну UI сильно напоминает Yuka.
Один из комментаторов даже назвал Oasis всего лишь фронтендом OpenFoodFacts. Кормак парировал, что в данных последней тяжёлых металлов и ПФАС нету и что Oasis собирает и публикует лабораторные данные, а Yuka якобы устарела, часто ошибается и не включает лабораторные измерения. Впрочем, в комментариях спросили, не заполняет ли Oasis эти значения случайными числами. Один из микроблогеров заметил, что на двух скриншотах у бренда Fiji стоит разная оценка.
На самом деле часто данные Oasis вводят в заблуждение. В комментариях к твиту нашли ошибки в выставленных предельно допустимых концентрациях: в приложении часто занижены ПДК относительно рекомендуемых властями США, и в реальности представленные количества вредных веществ представлять угрозу не могут. Зато эта дополнительная строгость к чистоте на три–четыре порядка ниже ПДК позволяет резко критиковать разные бренды за наличие в них мышьяка и тяжёлых металлов.
Хэйден резок в суждениях. В ответ на критику он заявил, что стандарты собеседника в отношении еды и здоровья устарели. Остряки на это заметили, что его Oasis допускает грубые грамматические ошибки уже в описании в каталоге приложений.
Наконец, секретом финансового успеха может быть банальный обман пользователей. Один из комментаторов указывает на тестовый период, который может запутать. Триал длится три дня, а затем начинают списывать по $4,99 в неделю. Возможно, что часть пользователей удаляет приложение и просто забывает отключить эту подписку.
Вызывает вопросы сама цена. Есть ли смысл платить по $30 ежегодной подписки за привилегию сравнивать разные бренды бутилированной воды? И вообще, заслуживает ли статуса отдельного приложения то, что может быть страницей в Интернете?
Разработчик (Mobile Developer) и программист Алексей Гладков попытался независимо изучить компоненты мессенджера Max.
Почему разработчикам опенсорсных приложений для Android может не потребоваться подтверждать свою личность
Недавно Google анонсировала, что скоро смартфоны на базе Android будут работать только с приложениями, чьи разработчики подтвердили свою личность непосредственно Google. Но как это будут проверять? Напрашивается проверка по ключам подписи, но погодите-ка…
Если вы более-менее интересуетесь опенсорсом, наверняка вы слышали про “магазин” F-Droid. Что примечательно в нём — все приложения в его главном (единственном по умолчанию) репозитории собираются из исходников и подписываются одной сущностью — F-Droid. Эта особенность делает данный источник приложений уникальным в своём роде — в Google Play или RuStore каждый разработчик собирает и подписывает приложение сам.

Если Google не передумает и действительно введёт блокировку на “анонимных” разработчиков, вполне возможно, что F-Droid просто создаст единый аккаунт для своего ключа подписи, и продолжит спокойно предоставлять приложения даже на “сертифицированных” Android-девайсах.
Но наверняка вы скажете, что там распространяются приложения, неугодные Google, и будете правы. Однако они и так ломаются каждый месяц самой же корпорацией ввиду открытых исходников этих приложений и способов парсинга контента без официального API. Так что, думаю, обойдётся.
Что думаете?
🎲 Retrofit 3.0 — что изменилось?

При поддержке блога @dolgo_polo_dev
Вышел Retrofit 3.0. А точнее в один день вышло 2 версии — Retrofit 3.0 и 2.12
Библиотека важная, поэтому попробовал разобраться, что изменилось
Самое интересное случилось в 2.12 — добавили стриминговую сериализацию из Kotlin/Java-классов в Json/Protobuf
Зачем это нужно было?
➡ чтобы большие классы не сериализовывались целиком перед отправкой запроса, а начинали это делать во время передачи данных на бэк
Это позволит чутка снизить нагрузку на процессор и оперативку, если
передаете объемные данные в теле запроса (1 мегабайт+)
где-то вызываете Retrofit.Call.enqueue() с главного потока — стриминг перенесет сериализацию с главного UI-потока в бэкграунд
Чтобы изменения заработали, нужно создавать конвертер с помощью функции withStreaming()
MoshiConverterFactory.create(moshi).withStreaming() // пример
В Retrofit 3.0 просто апнули версию OkHttp (3.14.9 -> 4.12.0). И немного поправили внутреннего кода, пару строк для совместимости с 4.12.0
Так что если обновите версию Retrofit, у вас транзитивно апнется OkHttp — будьте внимательны, берегите себя и своих тестировщиков
Из хороших новостей — Retrofit 3.0 формальный мажор, то есть бинарно совместим с предыдущими версиями (по словам разработчиков). Мажорное версию апнули для хайпа, чтобы подчеркнуть обновление OkHttp
Пруфы:
compare 3.0 with previous (https://github.com/square/retrofit/compare/2.12.0...3.0.0#diff-ee4546957dd484579c54b92114186cb4b3181a8906d98fbf94fbc526bf755943)(там много ченжей, но 90% из них — это обновление их website с документацией)
compare 2.12 with previous (https://github.com/square/retrofit/compare/2.11.0...2.12.0) (тут можно посмотреть, за счет чего сериализация стала асинхронной)
остальные посты про Android публикую в https://t.me/dolgo_polo_dev
Нужно было быстренько перехватить вызов нативной функции и посмотреть значение переменной в Android приложении. Как то давно использовал для этого замечательную утилиту Frida. Установил на планшет frida-server (root уже был) и клиент на PC, написал классический JS хук, но он не работал:
defineHandler({
onEnter(log, args, state) {
conslole.log('decoder_CRC_check()');
},
onLeave(log, retval, state) {
const libc = Module.findBaseAddress('libc.so');
console.log(hexdump(libc, {
/* address: ptr('0x1000'), -- to override the base address */
offset: 0,
length: 64,
header: true,
ansi: true
}));
}
});В консоли лишь получал TypeError: not a function at onEnter (D:\Distrib\Android TV\frida\hook.js:7)
Убил полдня в поисках "чего я делаю не так", при том что и официальная дока и нейронки твердят, что именно так и нужно получать базовый адрес модуля и, при необходимости, функций. Оказалось, во Frida 17 автор полностью удалил некоторые функции (а дока и нейронки еще не обновились):
Module.ensureInitialized()
Module.findBaseAddress()
Module.getBaseAddress()
Module.findExportByName()
Module.getExportByName()
Module.findSymbolByName()
Module.getSymbolByName()
Туда же статические функции Memory
И теперь надо писать цепочку вызовов:
const lib = Process.findModuleByName("libc.so");
console.log("[*] libc.so loaded at base: " + lib.base);
const funcAddr = lib.findExportByName("decoder_CRC_t_init");Итог получился такой универсальный скрипт:
Java.perform(function () {
const System = Java.use("java.lang.System");
const Runtime = Java.use('java.lang.Runtime');
const SystemLoadLibrary = System.loadLibrary.overload('java.lang.String');
const VMStack = Java.use('dalvik.system.VMStack');
// "ожидание"\перехват динамической загрузки нативных библиотек
SystemLoadLibrary.implementation = function(library) {
console.log("Loading dynamic library => " + library);
const loaded = Runtime.getRuntime().loadLibrary0(
VMStack.getCallingClassLoader(), library
);
if (library.includes("mylibname")) {
console.log("\n[+] Hooked mylibname");
// перехватываем только нужную нам
hookNativeFunc();
}
return loaded;
}
});
function hookNativeFunc() {
//тут имя полностью как называется сам файл в ресурсах
const lib = Process.findModuleByName("libmylibname.so");
console.log("[*] mylibname.so loaded at base: " + lib.base);
const funcAddr = lib.findExportByName("decoder_CRC_t_init");
if (!funcAddr) {
console.log("[-] Function not found!");
return;
}
console.log("[+] Found decoder_CRC_t_init at: " + funcAddr);
Interceptor.attach(funcAddr, {
onEnter: function (args) {
var result = args[0];
var inputPtr = args[1];
var len = args[2].toInt32();
console.log("\n[+] decoder_CRC_t_init called");
console.log(" result: " + result);
console.log(" inputPtr: " + inputPtr);
console.log(" len: " + len);
},
onLeave: function (retval) {
//нужный адрес массива, например из IDA PRO
const wordArrayOffset = 0x5B2C04;
const wordArray = lib.base.add(wordArrayOffset);
var ptr = new NativePointer(wordArray); // современный вариант чтения
console.log("[*] 5B2C04 contents:");
try {
console.log(hexdump(ptr, {
offset: 0,
length: 512,
header: true,
ansi: true
}));
} catch (e) {
console.log("[!] Error reading 5B2C04:", e);
}
console.log("Return value:", retval);
}
});
}
Запускается так frida -U -f com.android.app -l hook.js