# Технические вопросы Почему Riff хранит данные в FlatBuffers, разделён на два процесса и выглядит так просто? Это более технический FAQ о решениях, продиктованных работой на недорогих автомобильных магнитолах. В **1.1.0** Indexer сохраняет состояние профиля и историю в отдельных файлах FlatBuffers. На сайте опубликован этот релиз; прежняя пользовательская база SQLite/WAL больше не используется. ## Почему FlatBuffers, если для Android есть Room и SQLite? Room и SQLite — обычный и разумный выбор. В Riff тоже была база SQLite с WAL. Но магнитола может получать питание через автомобильную проводку, а её накопитель и прошивка не всегда ведут себя так, как ожидает приложение. Надёжность SQLite, включая WAL с `synchronous=FULL`, опирается на выполнение накопителем запросов синхронизации. Если контроллер сообщает об успешном сохранении раньше времени или нарушает порядок записи, внезапное отключение питания может повредить данные. SQLite прямо описывает [такие ограничения накопителей](https://www.sqlite.org/howtocorrupt.html#_failure_to_sync). В 1.1.0 Riff сохраняет состояние неизменяемыми снимками FlatBuffers: сначала готовит и проверяет новую версию, затем переключает указатель на неё. Предыдущая принятая версия сохраняется для восстановления. Так у приложения есть явно определённые правила публикации, проверки и отката. Сам FlatBuffers не исправляет неисправный контроллер и не гарантирует сохранность при любом обрыве питания. Убийство процесса и искусственные ошибки записи проверяются программно; поведение самого накопителя требует реального отключения питания или специального стенда. Такая проверка Riff пока остаётся отдельной задачей. Есть и вопрос накладных расходов. На старом мобильном процессоре лишние промежуточные объекты и работа сборщика мусора могут заметно мешать воспроизведению. FlatBuffers позволяет [обращаться к данным прямо в бинарном буфере](https://flatbuffers.dev/), без обязательной распаковки всей структуры. Адаптеры и интерфейс всё равно создают объекты: выбор формата не означает полного отсутствия аллокаций или GC. ## Зачем два процесса и такой сложный обмен между ними? Декодирование и ML-инференс требуют памяти и процессорного времени. На старой магнитоле запас этих ресурсов невелик. Android может завершить процесс при нехватке памяти; отдельный риск — сбой нативного декодера на конкретном файле. Высокая загрузка CPU сама по себе не означает, что Android обязательно убьёт процесс. Правила его времени жизни описаны в [документации Android](https://developer.android.com/guide/components/activities/process-lifecycle). Поэтому Riff разделяет воспроизведение и подготовку библиотеки. Player держит интерфейс и звук, Indexer разбирает файлы и выполняет анализ. Гибель Indexer — предусмотренный сценарий восстановления: после перезапуска он должен продолжить работу, а Player — сохранить принятую библиотеку и текущую очередь. Отсюда файловые дескрипторы, подтверждения, повторы и номера сеансов. Нужно отличить потерянный ответ от невыполненной команды, повтор от нового действия, старый процесс от нового. В 1.1.0 отдельно различаются «принято в память» и «сохранено на накопитель». Подробности — в статье [о взаимодействии Player и Indexer](https://roadio.link/tldr/ru/player-indexer/). ## Почему интерфейс такой простой, если есть Material Design? Автору известны и Material Design, и Compose. Интерфейс Riff собран на Compose, но ориентирован на экран, у которого могут быть слабая контрастность, блики на солнце и неточное распознавание касаний. Крупная контрастная кнопка на такой матрице остаётся различимой и даёт большую область попадания. Тонкие подписи, небольшие элементы и деликатные градиенты в тех же условиях могут потеряться. Поэтому в Riff много свободного места, большие зоны нажатия и отчётливое разделение переднего плана и фона. Вид намеренно утилитарный: важнее прочитать и нажать нужное. Крупные кнопки не отменяют ограничений на действия во время движения. Эти ограничения зависят от режима работы и автомобильной системы. ## Для чего экран с банковской картой? Это физическая линейка. Размер обычной банковской карты известен: совмещая её с контуром на экране, можно измерить реальный масштаб изображения. Android переводит `dp` в пиксели, используя сообщённую устройством плотность. Если одна прошивка со значением 160 dpi используется на панелях с разной физической плотностью, одинаковые элементы интерфейса получаются разного размера. Само наличие `dp` эту ошибку не исправляет. [Как Android учитывает плотность](https://developer.android.com/training/multiscreen/screendensities). Калибровка даёт Riff измерение, с которым он может выбрать подходящий масштаб. Если на вашем устройстве размеры уже удобны, проходить её необязательно. Экран нужен прежде всего там, где данным прошивки о дисплее доверять нельзя. ## Можно ли сделать AI-подбор лучше? Модель можно улучшать: точнее определять свойства музыки, оценивать уверенность и реже ошибаться. Но точность распознавания — только часть задачи выбора следующего трека. У автономной магнитолы нет истории прослушивания миллионов людей и данных о том, какие записи они обычно слушают вместе. Даже точное определение EDM ещё не объясняет, какую именно композицию вы захотите услышать сейчас. А постоянный выбор ближайшего совпадения может снова и снова приводить в один и тот же угол большой библиотеки. Поэтому Riff использует несколько слоёв: модель описывает музыку, оценки пользователя меняют предпочтения, история уменьшает вероятность недавних повторов, а взвешенный случайный выбор даёт шанс другим подходящим трекам. Более точная модель может помочь, но разнообразие всё равно требует отдельного механизма. Случайность не исправляет ошибки распознавания; она помогает не превращать подбор в бесконечное повторение самых близких кандидатов. Текущая модель остаётся исследовательской бета-моделью. Как из её результатов, оценок и истории получается очередь, объясняет [статья о вероятностном подборе](https://roadio.link/tldr/ru/probabilistic-selection/).