Обновить
128K+

Android *

Пишем под самую популярную мобильную ОС

172,87
Рейтинг
Сначала показывать
Порог рейтинга

Собеседование. Часть 4: Корутины — от иллюзии потоков до Structured Concurrency и обработки ошибок

Многопоточность вызывает мандраж. В Android на смену RxJava пришли Kotlin Coroutines. За их лаконичным синтаксисом скрывается жесткая архитектура. Мой следующий блок вопросов проверяет, понимает ли кандидат, что именно происходит под капотом привычного launch.

Уровень 1: Иллюзия потоков (База)

Начинаю с провокации: «Корутина — это просто легковесный поток?»

Многие кивают, и это ловушка. Правильный ответ: Нет, это блок кода, умеющий приостанавливать работу (suspend) и возобновляться позже. На одном Main Thread могут поочередно работать тысячи корутин, не блокируя его. Это кооперативная многозадачность. Если кандидат отличает блокировку Thread.sleep от delay, базовый тест пройден.

Уровень 2: Кто за рулем? (Scope и Job)

Усложняем: «Мы запустили сетевой запрос. Юзер закрыл экран. Как избежать утечки памяти?»

Продвинутый кандидат назовет CoroutineScope и Job. Job управляет жизненным циклом задачи, а Scope — ее область видимости. Обязательный вопрос: «В чем фишка viewModelScope?» Ожидаемый ответ: он жестко привязан к ViewModel и автоматически вызывает cancel() дочерним корутинам в onCleared().

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch { 
            val data = api.fetchData() // Отменится сам при смерти ViewModel
        }
    }
}

Уровень 3: Эффект домино (Structured Concurrency)

Меняем условия: «Запущено несколько задач в viewModelScope. Одна упала с Exception. Что будет?»

Джуны часто пишут try-catch внутри launch. Но истинный эксперт знает про Structured Concurrency. Корутины связаны иерархией. Если "ребенок" падает, он отменяет родителя (наш launch). Родитель умирает и отменяет остальных детей. В итоге молча упадет весь viewModelScope. Блок try-catch внутри вложенного launch здесь абсолютно бесполезен — исключение просто пробивает иерархию Job.

Уровень 4: Изоляция катастрофы (SupervisorJob)

Финальный босс: «Как защитить родительский scope от падения одной дочерней корутины?»

Здесь эксперт должен использовать supervisorScope. Его суперспособность — изоляция ошибок: смерть одной корутины не убивает соседей по scope.

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch { 
            val data = api.fetchData() // Отменится сам при смерти ViewModel
        }
    }
}

А для глобальной обработки непойманных исключений (чтобы не крашнулось всё приложение) кандидат должен упомянуть CoroutineExceptionHandler, который передается в контекст корневой корутины.

Резюме: Суровая реальность против тепличного кода

Корутины расслабляют своим красивым синтаксисом. Для меня на интервью важно увидеть, что человек понимает последствия своих архитектурных решений.

В мобильной (и особенно Automotive) разработке сеть нестабильна. Машина заезжает в альпийский тоннель, связь обрывается, и один упавший сетевой запрос погоды не должен утянуть за собой весь модуль навигации из-за неправильно настроенной иерархии Job. Именно умение строить отказоустойчивые системы отличает кодера от инженера, которому можно смело доверить продакшен.

Теги:
+1
Комментарии0

Делюсь опытом вайбкодинга Работа в Antigravity, Open Code, Windsurf

Сразу небольшая ремарка -

Всем ребятам которые когда-либо создавали сайт или приложение с помощью ИИ или агента, хочу сказать что опыт у всех разный , разные модели, взаимодействие, а также склад ума, мышления, понимая конечной цели и желаний, также различен, поэтому прошу не судить строго :)

Моё знакомство с искусственным интеллектом началось примерно в 2022 году, во время пандемии, когда много предприятий закрывалось и было скучно, особо не куда не выйдешь погулять или на природу, появился ChatGPT , я кинулся регаться в OpenAi , используя сервис виртуальных номеров той страны , какой включаешь ВПН, это получилось у меня только на 3 Гугл аккаунте Первые вопросы которые я ему задавал были простыми, но его ответы уже на тот момент мне казались пугающими. Тогда ещё можно было настроить прямо в чате температуру, где больше это значило модель может лгать, меньше или 0 модель всегда отвечала как есть.

В то время мне был очень интересен язык программирования javascript , и я кинулся его изучать, прошёл один бесплатный курс на стёпике и один платный, небольшой, на одной из известных площадок, по видео с Ютуба попробовал сделать мини-апп, но до конца это у меня не вышло, потому что замах был слишком большим, тогда я ещё не понимал что мини-апп этот - целая CRM, работающая на Sanity а не просто приложение. Всё что у меня получилось это 4 работающих страницы и возможность через crm добавлять блоки контента, понимая что наполнять их в одиночку, у меня не будет ни сил не времени, а также не было материальной мотивации, я это дело забросил на три года. У меня родился старший сын, были обычные бытовые заботы, в это время я мало следил за развитием ИИ , так как на тот момент мне казалось что нужно всё таки нужно больше гуглить, серфить stackoverflow, а хотелось максимум автоматизации из-за чувства что не хочу проводить свою жизнь за компьютером.

На тот момент я работал менеджером на ПВЗ Wildberries, по деньгам у меня выходило 5-6 тыс р. а в хорошие дни и 8-9, для моего небольшого города это отлично, с учётом графика 2/2

Но, я как любой другой отец задумался о будущем своего, тогда ещё одного ребёнка, хотел увеличить свой доход

А так как ИИ модели очень продвинулись, я недолго думаю кинулся создавать своё приложение 🐱.

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

Вообще ИИ уже очень сильно продвинулся, он мог предложить модули регистрации/авторизации через Гугл/firebase , Яндекс или ВК, или простую почту, также сделать простую метрику кто откуда зашёл, какой тариф купил и так далее, это было всё организованно через ТГ айди пользователя, которое синхронизировалось и при обычном создании аккаунта при регистрации через почту. Что я тогда понял при создании этого приложения?

А проблем была куча :

  • он мог постоянно генерить новые или менять куски функций, или блоки

  • если ему не объяснить про безопасность , sql, аудит и gitignore, а также МД файлы, он тупо пушил их кучей в гитхаб те инструкции которые ты ему писал

  • он хардкодил css

  • не правильно поставленная задача или промт часто приводила к переделке какого-либо участка кода

  • переключение на другую модель могло усугубить и добавить абракадабры в код

Итого: ИИ до сих пор не совершенен, и если даже даже мастера сеньоры ошибаются, то что говорить об ИИ?

Кому интересно - Продолжение будет в ч.2

Теги:
-2
Комментарии9

Вышел открытый проект DroidDeck, который позволяет запускать PC-игры с платформы Steam на Android-устройствах. Решение использует Proton для обеспечения совместимости игр на мобильных устройствах.

DroidDeck предлагает интерфейс, похожий на SteamOS, для удобного управления играми. Поддерживаются технологии AMD FSR и Snapdragon GSR для улучшения графики и производительности. С помощью технологии генерации кадров можно повысить плавность игры на мобильных устройствах. Для оптимальной работы требуется мощное аппаратное обеспечение смартфона. Например, на RedMagic 11 Pro с Snapdragon 8 Elite Gen 5 Cyberpunk 2077 работает на 720P при 60 FPS. Старые игры, такие как Left 4 Dead 2, могут достигать 120 FPS на том же устройстве. Также игры, разработанные для больших экранов, могут требовать подключения клавиатуры и мыши. Некоторые игры могут не поддерживать экраны с соотношением сторон 20:9 и 19.5:9, что приводит к проблемам с отображением контента.

«DroidDeck переносит опыт использования SteamOS на Android: клиент Steam от Valve в режиме Big Picture на вашем портативном устройстве с графикой Adreno и запуск игр для Windows через ARM64-версию Proton от Valve», — пояснили в команде проекта.

Теги:
0
Комментарии0

😎 Привет Халчане!

Давно не виделись, много чего произошло)

Сегодня вышло обновление HalChat for Android v1.0.4 (AI in every phone!)
Благодаря новым технологиям, стало возможно запускать даже 8 млрд. модели на бюджетных телефонах
Среднее время ответа ИИ на Honor X7b составляет от 3 до 4 минут
Можно почти одновременно общаться в одном чате как на ПК, так и на мобильном устройстве

Самое трудное было это перенести систему плагинов на Android.
Она может работать в оффлайн режиме, что позволяет общаться с ИИ без интернета (но пока отключено)

Кроме этого были добавлены статусы сообщениям (доставлено или отправлено)
Добавлены статусы пользователя в личном чате

Было проведено много работы над багами, оптимизацией, улучшением синхронизации и обновлению системы файлов.
На этой неделе я расскажу как я смог запустить в браузере 27b модель с минимальными потерями. И как я дообучил модель для HalChat.

Бесплатно попробовать 3 новых ИИ можете в:
Google Play: https://play.google.com/store/apps/details?id=halwarsing.net.halchatandroid
RuStore
: https://www.rustore.ru/catalog/app/halwarsing.net.halchatandroid

🤝 До новых встреч!

Теги:
0
Комментарии0

Фирменную анимацию раскрытия интерфейса iPhone Duo повторили на Samsung Galaxy Z Fold 8. Разработчик с Reddit использовал штатный датчик шарнира и шейдеры, доказав, что Samsung способна добавить такую же фишку в обычном обновлении One UI.

Данные с сенсора в реальном времени управляют шейдером AGSL через Android Presentation API. Шейдер интерполирует изображение между скриншотами внешнего и внутреннего экранов в зависимости от того, под каким углом сейчас раскрыт корпус.

Теги:
Всего голосов 5: ↑5 и ↓0+5
Комментарии0

Всем привет! Меня зовут Роман, я фронтенд разработчик. Недавно обзавелся участком и столкнулся с большим объемом данных/документов.

Пример проблем:

  • Выписка ЕГРН, ГПЗУ, данные по скважине/септику/щитку, планы, договора, все данные были разбросаны по телефону: фото, файлы, заметки и т.д.

  • Какой фильтр стоит на скважине (дата установки, инструкция по обслуживанию, параметры)

  • Кто и когда ставил ВРУ/трубостойку (контакты)

Решив заняться сортировкой понял: заметки/папки в телефоне это неудобно, а приложения, которое бы точечно решало проблему - не нашел. Поэтому решил создать свое.

Критерии приложения:

  • для мобильных устройств

  • не требует подключения к интернету

  • закрывает все разделы по хранению данных касательно дома/участка.

Критерии по разработке - простота и скорость, отсюда следующее:

  • Стек - ts, zustand, expo.

  • Архитектура - feature first.

  • БД - sqlite + drizzle (indexedDB для web)

  • Бэк не делаю, все данные храню локально.

Релиз

После создания MVP и всех своих «хотелок» решаю открыть приложение для всех. Для этого добавляю аналитику Aptabase, формы обратной связи, создаю фотокарточки тут (много шаблонов, бесплатно 1 или 3 экспорта в месяц), придумываю название/лого. Иду публиковать в Google Play и App Store и сталкиваюсь с ограничениями, которые сильно усложняют публикацию при регистрации в Крыму. Расстраиваюсь. Изучаю проблему и ищу обходные пути. Понимаю, что безопаснее и проще будет перейти на PWA. После рефактора под PWA, публикую web версию, публикую Android версию в RuStore, а для iPhone..., инструкцию по созданию ярлыка.

Бизнес-план

Рассматривал два варианта монетизации: подписка на облачное хранение и платное расширение функциональности. Оба варианта требуют бэк, платежной инфраструктуры и подтвержденного спроса, поэтому пока это отложил.

Вложений в рекламу нет и не планирую поскольку изначально целью создания приложения было решение моей проблемы, с чем пока отлично справляется.

Состояние приложения на данный момент

На текущем этапе закрывает мой основной сценарий: хранение данных, напоминания и контакты подрядчиков. Дальше интересно будет понять, какие сценарии окажутся важны другим пользователям. Если будут предложения - пишите в комментарии, с удовольствием прочитаю. Делал для себя, но оно должно отлично подойти всем владельцам домов, участков, дач.

Для себя итоги такие: приложение полностью решает мои проблемы и полностью устраивает, а если решит похожие проблемы другим людям - вообще замечательно.

Спасибо за прочтение!

Теги:
Всего голосов 2: ↑2 и ↓0+4
Комментарии3

Wildberries выпустила собственный мессенджер WB Chat

У Wildberries появился собственный мессенджер WB Chat. Приложение уже доступно пользователям на Android и iOS, а авторизация проходит через WB ID.

Интерфейс построен по знакомой схеме: диалоги разделены на чаты, группы и каналы, причём создать собственную группу или канал можно непосредственно из приложения. Есть отдельный раздел профиля с аватаром, именем, статусом и возможностью выбрать юзернейм, а для организации переписок предусмотрены папки.

Набор функций тоже постепенно расширяется. Сейчас WB Chat позволяет:

  • отправлять сообщения, фото, видео и файлы;

  • пересылать сообщения и отвечать на них;

  • использовать реакции, эмодзи и стикеры;

  • создавать публичные и приватные группы и каналы;

  • закреплять важные сообщения;

  • искать людей, чаты и сообщения;

  • совершать аудиозвонки;

  • расшифровывать голосовые сообщения в текст.

Последняя функция особенно интересна для повседневного использования: рядом с кнопкой воспроизведения голосового сообщения появляется возможность получить его текстовую расшифровку. То есть длинное голосовое необязательно прослушивать целиком.

При этом проект пока активно развивается. Например, в последних версиях разработчики отдельно сообщают об исправлениях синхронизации, работе контактов, медиафайлов, звонков и повышении стабильности приложения.

В Google Play приложение опубликовано компанией WB FZE, зарегистрированной в Hamriyah Free Zone в эмирате Шарджа, ОАЭ. На момент проверки там указано 1 тыс.+ скачиваний.

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

Скачать WB Chat для Android можно через Google Play, для iPhone и iPad — через App Store. Также заявлена веб-версия WB Chat.

Пока это выглядит скорее как новый игрок на рынке мессенджеров, чем полностью сформировавшаяся альтернатива привычным сервисам. Но наличие чатов, групп, каналов, звонков, поиска, папок и расшифровки голосовых показывает, что Wildberries постепенно собирает полноценную коммуникационную платформу.

✔ Код — журнал о технологиях https://t.me/kodjournal подпишитесь на наш Telegram-канал! 😎

Теги:
Всего голосов 2: ↑1 и ↓10
Комментарии2

Jetpack Room 3.0 теперь stable с кучей преимуществ для мультиплатформы и не только. Запланируйте интеграцию нового мажора.

У меня как раз есть статья

Теги:
Всего голосов 1: ↑1 и ↓0+3
Комментарии0

[пожалуйста, не злоупотребляйте эмодзи]

😻 Привет Халчане! Новое обновление 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

Теги:
Всего голосов 4: ↑1 и ↓30
Комментарии1

Новинки синтеза речи для Андроид. В Гугл Плэй появилось приложение для офлайн синтеза речи высокого качества VoxSherpa TTS.

VoxSherpa имеет настройки “умные паузы”, тэги эмоций, и т.д. Позволяет установить в качестве системных 3 нейро-движка синтеза речи:

  1. Kokoro. Мультиязычный, но русский язык не поддерживает.

  2. Piper. С ним нужно дополнительно загрузить отдельные голоса для разных языков. Есть довольно хороший русский голос Irina. Среди английских голосов есть несколько очень высокого уровня.

  3. Supertonic 3 TTS. Очень интересный новый движок. Полностью мультиязычный (русский поддерживает), хорошо переключается между английскими и русскими словами даже внутри одного предложения. Очень высокое качество (настраиваемо), но есть лёгкий английский акцент. Сейчас плохо работает с изменением скорости (лучше оставить на 1.0). Есть в Гугл Плэй отдельным приложением. Есть также на Гитхаб:
    https://github.com/davnozdu/supertonic-android

Теги:
Всего голосов 2: ↑2 и ↓0+4
Комментарии1

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

Три вещи которые горели чаще всего.

Разные версии Android. На эмуляторе всё красиво. На реальном устройстве со старой версией что-то обязательно едет. Держу под рукой старый телефон с Android 10, туда ставлю перед каждым релизом.

Разрешения. Забываешь добавить в манифест, на новых версиях система спрашивает пользователя, пользователь жмёт «запретить» и половина функций молча перестаёт работать. Без каких-либо ошибок в логах.

ProGuard и минификация. Локально всё работает. В release сборке падает что-то что ты вообще не трогал. Потому что минификатор убрал класс который использовался через рефлексию.

Список не длинный но каждый раз спасает от как минимум одного стыдного бага в продакшене.

Что у вас в чеклисте перед релизом чего нет у большинства?

Теги:
Всего голосов 2: ↑2 и ↓0+4
Комментарии0

Открытый проект RepoStore для смартфонов на Android превращает GitHub в Google Play и помогает найти любые Android‑приложения среди репозиториев. С помощью RepoStore можно быстро найти и открыть APK‑файл — у него будут описание и даже рейтинг. Всё разделено по категориям.


Теги:
Всего голосов 9: ↑9 и ↓0+13
Комментарии0

Прямая 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

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

  1. артист отправляет фото профиля

  2. артист отправляет описание профиля

  3. артист загружает трек

  4. артист отправляет описание трека

  5. артист выполняет оплату сервиса

  6. артист финализирует трек

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

МОНЕТИЗАЦИЯ

Отправка материала артистом требует платы за сервис. Когда артист подтверждает оплату, бот списывает с его кошелька требуемую сумму и зачисляет её на кошелек владельца радиосервиса. Это значительно упрощает обмен активами между плательщиком и получателем.

Таким образом, технология Web3 с использованием блокчейна позволяет оперативно выполнять оплату за предоставленный сервис без лишних трат и банковских процедур. При этом все транзакции становятся видны в блокчейне уже через несколько минут.

схема работы блокчейна
схема работы блокчейна

Следующим этапом разработки я планирую реализовать возможность начисления роялти каждому автору музыкального материала. Для этого потребуется создать публичную таблицу рейтинга на сайте радио, где слушатели смогут голосовать за понравившиеся треки, продвигая артиста на верх списка. Исходя из количества прослушиваний конкретного трека, можно будет рассчитать сумму роялти и автоматически выплачивать её на личный кошелек автора в мессенджере.

Таким образом, объединение двух разных сущностей, а именно интернет-радио с приложением для обмена сообщениями, является неким ноу-хау для оказания помощи в развитии молодых дарований. Лично для меня как для разработчика это отличный вызов и прекрасная возможность "пошевелить мозгами".

Если у вас появятся предложения, буду рад подискуссировать.

Теги:
Всего голосов 6: ↑2 и ↓40
Комментарии6

Ближайшие события

На Android вышло новое приложение CatchCat для любителей кошек. Это аналог Pokémon Go, но бесплатный и без встроенных покупок. Нужно ходить по улице и городам и фотографировать различных котов. Нейросеть сама определяет породу и выдаёт пользователю карточку питомца. Есть здесь и карта, на которой можно отслеживать найденных другими игроками котов. Чем больше снимаете, тем выше уровень. Версия для iOS у проекта появится позднее.

Теги:
Всего голосов 4: ↑3 и ↓1+4
Комментарии3

Как я научил свою читалку различать примечания и комментарии в «Войне и мире»

Я не профессиональный программист. Пишу Android-читалку MRead для себя и для тех, кому тоже надоели комбайны. Код закрытый, всё работает локально, без серверов и трекинга. Раздаю через RuStore, 4PDA и GitHub.

В версии 1.4.0 я наконец победил баг, который меня лично бесил как читателя. Расскажу именно про него, потому что задача оказалась интереснее, чем выглядела.

Проблема

Открываю «Войну и мир». В одном предложении сразу два маркера сноски: примечание (перевод французского) и научный комментарий. В разных изданиях они нумеруются независимо, поэтому в тексте легко встретить два маркера с одинаковой цифрой. Старая логика по тапу вытаскивала из текста ближайшую цифру и искала абзац, начинающийся с этого числа, по всем главам, и показывала первое совпадение. Итог: тап по «6» открывал не ту сноску, а тап по числу вроде «1805» вообще пытался найти несуществующую сноску.

Почему наивный подход не работает

Цифра это не идентификатор. Она повторяется и в каждой главе, и между разделами «Примечания» и «Комментарии». А вот в исходнике всё однозначно: каждый маркер это ссылка с уникальным адресом.

EPUB: 1 FB2: [6] и {4}

То есть книга всегда знает правильный ответ. Беда была в том, что мой парсер при импорте выбрасывал href и оставлял только видимый текст маркера.

Что я поменял

  1. При разборе HTML сохраняю не только текст абзаца, но и ссылки-сноски с точной позицией маркера. Позицию считаю надёжно: оборачиваю маркер невидимыми символами из Private Use Area до очистки текста, а после очистки нахожу их и вычисляю смещение. Так цифры в обычном тексте (годы, числа) не путаются с маркерами.

  2. Позиции храню в абсолютных координатах главы. Это важно, потому что мой пагинатор режет абзацы на куски по строкам. Абсолютные смещения переживают нарезку без отдельной возни в пагинаторе.

  3. По тапу беру не цифру, а ссылку под пальцем, и резолвлю сноску по точному id из нужного файла. Для FB2 пришлось ещё и сохранить id секций сносок при конвертации в HTML, потому что примечания и комментарии лежат в отдельных главах.

  4. Старый поиск по цифре оставил как запасной вариант для книг без нормальной разметки, но убрал срабатывание на голые числа.

Результат: тап по примечанию открывает перевод, тап по комментарию открывает комментарий, а год «1805» больше никого не трогает.

Контекст для тех, кому интересно

Рендер текста у меня не WebView, а нативный движок на Canvas с собственной пагинацией (Jetpack Compose, Kotlin). Это даёт контроль над переносами, выравниванием по ширине и стабильной привязкой цитат и закладок, но за каждую такую фичу приходится платить ручной работой вроде этой истории со сносками.

Что ещё в 1.4.0, кратко

• Полнотекстовый поиск по PDF (для PDF с текстовым слоем)
• Озвучивание (TTS): голоса, скорость, таймер сна, автопереход, пауза с памятью места
• Передача слова во внешний словарь и контекст предложения в экспорт Anki
• Настраиваемые блоки настроек чтения, межабзацный интервал, Bold/Italic
• Полки, фоновый импорт из папок, оценки и заметки, Material You

Ссылки

• RuStore

• GitHub

• 4PDA

Если у вас была книга FB2, переоткройте её, чтобы заработали точные сноски (HTML генерится при импорте). EPUB подхватит сам.

Буду рад замечаниям по подходу. Если делали резолв сносок иначе, расскажите как, мне правда интересно.

Теги:
Всего голосов 3: ↑3 и ↓0+6
Комментарии0

Как я ускорил бэкапы в 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 верхнего меню: закладки теперь можно переименовывать, а тап по номеру страницы открывает быстрый переход.

  • Добавлен множественный выбор в библиотеке (массовое добавление на полки/удаление/скрытие).

Ссылки:

Теги:
Всего голосов 1: ↑1 и ↓0+3
Комментарии1

🤗 Привет всем!

😻 Обновление 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

Теги:
Всего голосов 1: ↑1 и ↓0+3
Комментарии2

Собеседование. Часть 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 невероятно удобны. Но за синтаксический сахар всегда нужно платить. Важно видеть, как человек переходит от слепого восторга красивым кодом к прагматичному пониманию

Теги:
Всего голосов 5: ↑5 и ↓0+7
Комментарии0

Помогите, кто чем может. Яндекс пробил дно

У меня айфон с маленьким экраном. Предпочитаю компактные модели, чтобы умещались в любом кармане. Это доставляет и некоторые неудобства. Например, часть приложений плохо работают, элементы интерфейса перекрывают друг друга, и из-за этого некоторые функции становятся недоступны. Иногда с такой проблемой я сталкивался в приложении Яндекс.Такси.

В связи с тем, что с продукцией Apple в России в последнее время ситуация постоянно ухудшается, планирую перейти на Android. Нашел подходящую модель на Яндекс.Маркете смартфон Conquest F3 Plus. Одна проблема — в этой модели экран еще меньше, чем у меня сейчас. Значит, есть риск, что приложения, которые глючили на старом смартфоне, вообще не смогут работать на новом.

С данным вопросом я обратился в поддержку Яндекса. Я был уверен, что получу точный ответ, будет работать приложение Яндекс.Go на интересующем меня устройстве или не будет. Ведь что может быть проще? Любой разработчик может, даже если не знает точно, в эмуляторе задать указанное разрешение экрана и прогнать тесты.

Ответом поддержки я был, мягко говоря, ошарашен. Ниже привожу текст нашего диалога со скринами.

Здравствуйте! Будет ли работать ваше приложение на вот такой модели смартфона?
https://market.yandex.ru/cc/9aPY2a
Разрешение экрана 540x1200.

Данную информацию вы можете уточнить в магазине приложений, из которого вы хотите скачать наше приложение

Я хочу купить новый смартфон, но мне нужно знать, будут ли работать нужные приложения. Ваше приложение очень плохо работает на экране с разрешением 1344x750. Хоть и работает. Какое разрешение поддерживает yandex.go? Будет ли оно работать на экране 540x200?

Данную информацию уточнить не получится, подсказать смогут в магазине приложений

Что значит, не получится? Напишите разработчикам, и они вам скажут.

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

Какой магазин?

Магазин приложений вашего устройства

Итак, поддержка Яндекс.Go не смогла ответить на вопрос о системных требованиях собственного приложения. Неожиданно. У меня нет ни малейших идей, как такое стало возможно.

В связи с этим я решил обратиться за помощью к товарищам по отрасли. Может, кто-нибудь из читателей Хабра пробовал ставить Яндекс.Go на Android 12 с разрешением экрана 540x1200. Нормально работает?

Теги:
Всего голосов 8: ↑4 и ↓4+2
Комментарии29

Собеседование. Часть 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: ↑1 и ↓0+3
Комментарии0
1
23 ...