Feature flags меню разработчика что это

Feature Flags в новом Android P: где найти, и что они означают

В приложении «Настройки» нового Android P изменения/обновления тоже есть, и их снова много. Одна из таких новых фич появилась в разделе «Для разработчиков». Называется она Feature Flags…

…и точно так же, как Flags в браузере Chrome открывает юзеру доступ к некоторым новым функциям системы, которые еще находятся в разработке.

После активации режима «Для разработчиков» находим его в «Настройках», заходим, прокручиваем экран вниз и тапаем «Feature flags«.

В разделе «Feature flags» первой версии Android P (а их до релиза обещают еще с десяток) предусмотрено всего восемь «флажков«. Некоторые из них уже включены, но выключать/включать можно любой или все сразу:

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

Если снова вернуться к аналоги с flags в Chrome, то положение слайдер-выключателя «Вкл«соответствует значению «true«, а «Выкл» — значению «false«. При этом у Android P изменения значения для каждого из «флажков» вступает в силу сразу, то есть перезагрузка системы не требуется (в отличие от flags в браузере Chrome).

На момент публикации данной статьи ничего сенсационного в Feature flags мы не нашли. Да, функция удобная, если надо быстро отключить какое-то из новшеств Android P, но не более.

С другой стороны, есть подозрение, что Feature flags в нынешнем их виде представлены сугубо для первичного ознакомления с ними юзеров, и в финальной версии системы список из будет выглядеть несколько иначе и гораздо более интереснее.

Значит, Feature flags в Android P (DP1):

Их на данный момент всего 8, как мы уже сказали. Вероятно, в следующих версиях системы появятся новые flags, но пока давайте разберемся с теми, которые уже есть.

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

В общем, пока так. Повторимся, в следующих версиях Android P список «Feature flags«, наверняка, будет корректироваться и до релиза, вероятнее, всего дойдет в гораздо более интересном виде. Но посмотрим… А пока следите за обновлением. Постараемся дополняться этот пост по мере поступления новых данных.

Источник

Как ускорить телефон Android с помощью секретных настроек

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

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

↑ Настройки разработчика и как их разблокировать

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

↑ Возможности меню Для разработчиков

Ниже представлены функции, содержащиеся в меню Для разработчиков.

↑ Настройка анимации

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

Также есть еще один метод. Сначала необходимо зайти в пункт «Отрисовка». Все показатели выбраны по умолчанию. Значение для шкалы анимации окна, переходного масштаба и длительности устанавливается на 1X. Рекомендуется поставить 0,5Х.

↑ Улучшение графики

Спецификации современных смартфонов быстро масштабируются за счет интеграции большого объема оперативной памяти и SoC, достойных настольных систем среднего и высокого уровня. В Playstore много новых игр и приложений, в том числе бесплатных, позволяющих раскрыть технический потенциал смартфона.

Чтобы улучшить графическую производительность игр и время отклика можно активировать опцию MSAA 4X. Это простой фильтр сглаживания, который обрезает изображения, улучшая качество. Он подойдет не для всех игр, а только для тех, которые используют OpenGL ES 2.0 API.

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

↑ Дополнительные хитрости

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

↑ Очистить лишние файлы

Внутренняя память должна быть свободной. Для этого подойдет файловый менеджер, который анализирует хранилище и показывает неиспользуемые файлы, а также маловажные программы.

↑ Удалить или отключить приложения вручную

Чтобы сделать это необходимо посетить «Настройки», а затем «Приложения». Некоторое ПО, установленное по умолчанию, нельзя стереть, но разрешено выключить.

↑ Сменить лаунчер

↑ Разгон на 100%

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

Являюсь руководителем проекта, люблю андроид. Слежу за новинками, новостями, а так же помогаю решить проблему возникшую у пользователей в андроид смартфонах!

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

Если у вас возникли вопросы с решением проблемы на вашем смартфоне. Оставьте комментарий подробно описав проблему, и обязательно укажите модель вашего смартфона. Мы постараемся помочь вам!

Информация

На данный момент комментариев нет 🙁

Источник

Feature Flags и фабрика ПО

Наши команды практикуют подход Trunk Based Development – новый код сразу добавляется в мастер-ветку, сторонние ветки живут максимум несколько дней. А чтобы коммиты не мешали друг другу, разработчики используют фича-флаги (Feature Flags) – переключатели в коде, которые запускают и останавливают работу его компонентов.

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

Если вы стремитесь сокращать Time-to-Market, это недопустимо долго. Чем раньше вы получите обратную связь от пользователей, тем скорее вы исправите ошибки, тем меньше времени вы тратите на неудачные идеи, тем больше ресурсов можете уделить идеям удачным.

Чтобы обновления быстрее доезжали до прода, одна итерация должна включать одну фичу. Именно поэтому нужно сокращать срок жизни веток.

Проблемы долгоживущих веток

Конфликты между коммитами (Merge hell). Откладывание релиза, интеграция с внешней системой, прочие внешние факторы могут привести код в нерабочее состояние.

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

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

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

Как Trunk Based Development решает эти проблемы

Trunk Based Development (от англ. trunk – «ствол дерева») – метод разработки кода на основе одной главной ветки. В отличие от подхода Gitflow, TBD позволяет разработчикам добавлять новые модули сразу в master. Второстепенные feature-ветки также могут создаваться, но они имеют короткий срок жизни.

Trunk Based Development предполагает только одну ветку для разработки, которая называется trunk. В любой момент эту ветку можно развернуть её на проде, а разработка, как и прежде, идёт в отдельных фича-ветках. Только теперь эти ветки живут не более двух дней.

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

Но как вести разработку в одной ветке, если какие-то фичи ещё не готовы, а релиз завтра? Тут нам на помощь приходят Feature Flags.

Как работают Feature Flags

По сути своей, это IF-блок, который запускает кусок кода при выполнении некого условия. Самое простое – разработчик сам прописывает, включать или выключать код. Могут быть параметры посложнее: например, по расписанию или только для пользователей с такими-то уровнем доступа. Или наоборот – фича отключается, если нагрузка на систему превышает заданный порог.

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

В точке переключения мы обращаемся к переключателю (toggle router), который определяет состояние фичи. Чтобы понять, какой нужен флаг, роутер обращается к его конфигурации и контексту. Первая определяет общую стратегию включения флага (основные условия для его работы), второй включает любую дополнительную информацию (например, имя пользователя, который отправил запрос, текущее время и т.д.).

Как использовать Feature Flags

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

Релизные (release toggles): скрывают неготовые фичи, уменьшают количество веток, открепляют запуск фичи от даты деплоя. Основной тип флагов.

Экспериментальные (experiment toggles): используются для A/B тестирования, позволяют таргетировать функции на разные группы пользователей. Таким образом вы можете развернуть новый сервис на Х% аудитории, чтобы оценить нагрузку или собрать первые отзывы.

Разрешающие (permission toggles): открывают доступ к платным фичам или закрытым функциям администратора. Такие флаги могут жить очень долго, быть очень динамичными и менять своё состояние при каждом запросе.

Операционные (ops toggles): отключают ресурсоёмкие функции. Например, так можно регулировать работу приложения на слабых смартфонах или застраховаться от падения производительности при запуске новой функциональности – флаг отключит модуль до того, как тот вызовет критический сбой.

### Что дают Feature Flags

Непрерывная доставка фич со стабильным качеством – возможность отключить нерабочий код снижает риски в релизной версии.

Тестирование новых фич в боевых условиях – фиче-флаги позволяют постепенно внедрять сервисы, контролируя риски при релизе на реальную аудиторию.

Возможность развивать несколько версий ПО параллельно – TBD и фичефлаги позволяют предлагать разные функции разным группам пользователей, при этом поддерживать все эти версии ПО может всё та же одна команда.

Как управлять флагами

Проприетарные решения: LaunchDarkly, Bullet-Train, Unleash. Каждый продукт предлагает какие-то свои преимущества, каждый по-своему удобный. Но за лицензию придётся платить, а гибкость настройки зависит от разработчика системы.

Open source решения: Moggles, Esquilo. Это бесплатные решения, но чтобы они заработали у вас, потребуется над ними поколдовать. Кроме того, придётся подбирать продукт с таким набором функций, который вас устроит.

Собственная система управления: вариант, которым пользуемся мы. Это единственное в своём роде решение, которое целиком нас устраивает. В будущих постах расскажем подробнее.

Как флаги работают у нас

Feature Flags Portal (FF-Portal): Web + REST API приложение для манипулирования состоянием флагов. Напрямую работает с хранилищем флагов.

Feature Flags Storage (FF-Storage): персистентное хранилище с настройками флагов и их статусами.

Kubernetes ConfigMap (FF-configmap): k8s ConfigMap ресурс, который собирается на основе данных, которые хранятся в FF-Storage в удобном формате для конечного приложения. Изменение данных флагов через FF-Portal также влечёт к изменению FF-configmap.

Microservice (MS): Микросервис, который использует FF-configmap как источник конфигурации при старте приложения. При изменений FF-configmap, микросервис делает перезагрузку своей конфигурации.

Приложение считывает конфигурацию флагов с FF-ConfigMap, который монтируется к Pod-у как файл. При изменении ConfigMap, k8s обновит и файл, далее приложение среагирует на изменение файла и перегрузит конфигурацию флагов.

Изменение флагов происходит через портал, который отправляет интеграционное сообщение в шину при обновлении статуса. Компонент Config Updater обновляет значения флагов в FF-ConfigMap через K8S API.

Напоследок о тестировании

Возникает вопрос, как тестировать продукт с фиче-флагами? На первый взгляд, флаги усложняют в этот процесс – если переключателей становится много, то и количество всевозможных состояний резко растёт.

Но не всегда флаги зависят друг от друга. Поэтому мы для релиза тестируем два предельных случая: 1) все новые флаги выключены и 2) все флаги включены.

Практика показывает, что обычно этого достаточно.

Источник

Укрощение feature-флагов

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что это

Всем привет! Меня зовут Паша Стрельченко, и я — Android-разработчик в hh.ru. В этой статье расскажу об укрощении feature-флагов. Если больше нравится аудиовизуальный формат, его можно найти на нашем youtube-канале. В статье я расскажу чуть больше технических подробностей, чем в видео, так что должно получиться интересно.

Что такое feature-флаги? Это обычные булевские флажочки, которыми внутри приложения вы заменяете или закрываете какую-либо функциональность. Например, с помощью одного флажка можно изменить цвет кнопки, с помощью другого – включить или выключить мессенджер внутри приложения.

Если у вас маленькая команда и не очень много feature-флагов, то, скорее всего, вы вообще не сталкивались с проблемами. Однако, если команда растёт, и с каждым днем становится всё больше фичетоглов, то неизбежно возникнут определенные сложности. О них мы и поговорим.

Содержание

Способы сборки feature-флагов по всей кодовой базе

Java Reflections API

Немного специфики hh.ru

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

Во-первых, мы называем feature-флаги «экспериментами», потому что постоянно «экспериментируем» над нашими пользователями, пока проводим огромное количество A/B тестов. Таким образом мы связали эти два понятия. То есть, включаем какой-то эксперимент = включаем feature-флаг. На протяжении всей статьи я буду оперировать именно понятием «эксперимент».

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

И вот тут мы столкнулись с определенными проблемами.

Проблемы с feature-флагами

Нашу первую реализацию конфига экспериментов можно было охарактеризовать фразой «Абстракция на абстракции и абстракцией погоняет».

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что этоАбстракция на абстракции, абстракцией погоняет!

Всё было очень сложно. У нас был базовый модуль экспериментов, в котором мы описывали логику походов в сеть за списком экспериментов и его кэшированием. Было два отдельных модуля, которые содержали все эксперименты двух наших приложений: соискательского и работодательского. И уже от этих двух модулей зависело огромное количество feature-модулей.

И мы выделили для себя три основных проблемы.

Merge-конфликты в общем наборе экспериментов

Как я уже сказал, у нас были модули, в которых мы описывали все наши эксперименты для разных приложений. И выглядело это как гигантский enum — один файл, в котором мы строчка за строчкой описывали эксперименты:

И, серьёзно, почти каждый день мы сталкивались с merge-конфликтами: кто-то смёрджил очередную фичу в develop, кто-то разрабатывает следующую и подмёрдживает к себе develop, и вынужден решать конфликты в одних и тех же файлах.

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что этоБесконечные merge-конфликты в одном файле

Когда merge-конфликты раз за разом происходят в одних и тех же файлах, неудивительно, что появляется желание как-то это исправить.

Пересборка приложения при добавлении эксперимента

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

Когда вы меняете какой-либо публичный интерфейс, доступный другим модулям, вы изменяете ABI модуля, и при изменении ABI будут пересобраны все зависимые от него модули. И поскольку у нас было много feature-модулей, которые зависели от модуля со списком экспериментов, у нас пересобиралось почти всё приложение при добавлении очередного элемента enum-а.

Так быть не должно.

Много кода ради проверки одного флага

Это проблема уже специфичная для android-проекта hh.ru — мы писали очень много кода для проверки одного-единственного эксперимента. Почему так получилось — мы долгое время считали модуль с экспериментами отдельным feature-модулем, который, по правилам нашей кодовой базы, не мог быть напрямую подключен к другому feature-модулю.

Как вы знаете, театр начинается с вешалки (ну или с парковки), в нашем случае, театр начинался с feature-модуля. В нём мы создавали интерфейс, который описывал зависимости этого feature-модуля:

Затем мы шли в application-модуль, там описывали класс-медиатор, в котором реализовывали зависимости нужного feature-модуля. Там обращались к DI и доставали значение флага:

В модуле экспериментов у нас был собственный публичный интерфейс (о нём я упоминал выше), к которому мы и обращались из application-модуля:

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

Ну и, наконец, изменялся enum с экспериментами, туда добавлялись новые элементы с нужными ключами:

Итого — у нас получилась длинная-предлинная церемония добавления и проверки очередного эксперимента. Разумеется, нас это не устраивало.

Какие выводы мы в итоге сделали:

Нам нужны эксперименты в кодовой базе, просто выбросить их мы не можем;

Мы тратим время на merge-конфликты, от них однозначно хотим уйти;

И мы пишем слишком много кода ради одного эксперимента.

Давайте решать эти проблемы!

Решаем проблемы

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

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

Для этого мы ввели интерфейс эксперимента, и разделили элементы enum-а на отдельные классы, реализующие этот интерфейс:

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

Затем мы решили упростить процесс проверки эксперимента: для этого создали специальный объект Experiments, куда добавили метод для проверки наличия эксперимента в кэше:

Для большего удобства можно сделать extension-метод на интерфейсе Experiment, тогда код проверки будет ещё короче:

Ах, да, чтобы не тащить код проверок через медиаторы-application-модули и т.п., пусть теперь модуль с базовой логикой экспериментов считается core-модулем, значит его можно подключать напрямую.

Проблему длинных церемоний в коде — тоже решили.

Ну и, наконец, решаем проблему пересборок. Так как теперь каждый эксперимент — это отдельный класс, их можно размещать в РАЗНЫХ модулях и отмечать класс эксперимента модификатором internal.

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

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

Ура, все три проблемы решены!

Собираем feature-флаги

Но перед нами встала новая интересная задача: а как нам теперь собрать разбросанные по нашей кодовой базе классы экспериментов в единый список?

Зачем нам вообще это нужно: у нас в hh для облегчения тестирования приложений есть специальная debug-панель, которая доступна на debug-сборке и на минифицированной debuggable-сборке (мы называем это preRelease-ом).

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что этоDebug-панель и открытая секция экспериментов

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

Мы ушли от enum-чика, а значит у нас больше нет встроенной в язык возможности получить разом список экспериментов (раньше это делали через ApplicantExperiment.values()). Плюс к этому, наш сервер не присылает нам ВЕСЬ список возможных ключей экспериментов, он нам присылает только тот список, который активен для того или иного пользователя. А значит в debug-панели нельзя просто взять и отобразить ответ сервера.

Что же делать?

И вот тут начинается магия. Способов оказалось довольно много, и я хочу вам о них рассказать. Я разбил эти способы на несколько групп:

Собрать список вручную

Вспомнить про Java Reflections API

Сгенерировать нужный код

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

Сборка вручную

Этот способ выглядит сомнительно. Мы бы столкнулись всё с той же проблемой merge-конфликтов: просто на этот раз не в enum-е, а в build.gradle-файлах или же при описании списка, в котором мы бы инстанцировали модели экспериментов:

От чего ушли, к тому и пришли. Непорядок.

С другой стороны, у этого способа тоже есть плюсы:

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

И реализация очень простая.

Возможности DI-фреймворка

Оказывается, некоторые DI-фреймворки могут собирать объекты, которые объединены каким-то признаком (реализация интерфейса, наследники абстрактного класса и т.п.) в списки.

В частности, у Dagger-а 2 и у Hilt-а есть такая фича, которая называется Mutlibindings. С её помощью вы можете собирать объекты либо в Set, либо в Map с любыми ключами.

Как это делается?

Во-первых, вы создаёте dagger-модули в нужных feature-модулях, в которых описываете метод, отмеченный аннотациями @Provides и @IntoSet, и в реализации метода описываете создание объекта:

После этого вы готовы заинжектить собранный список в нужное вам место:

Ок, я сказал, что DI-фреймворки умеют собирать объекты в списки. Но что там у Toothpick/Koin? К сожалению, ничего.

Единственное, что есть у этих фреймворков — это issue на гитхабе, в которых разработчики просят добавить эту возможность (issue для Koin-а, issue для Toothpick-а).

Таким образом, если у вас в проекте уже используется Dagger — вам повезло, вы можете пользоваться встроенной фичей для сбора списка экспериментов. Если же используете другой фреймворк — вам подойдут другие способы. У нас в hh используется Toothpick, так что мы продолжили ресёрч.

Java Reflections API

Java Reflections API — это возможность языка Java в рантайме узнавать или изменять поведение приложений. И в этом разделе есть несколько интересных способов сбора множества разрозненных кусочков кода в единый список, о которых я хочу вам рассказать.

ClassLoader + DexFile

Первая связка, о которой я хочу рассказать — это использование ClassLoader-а и android-ного DexFile-а.

В Java, прежде чем использовать какой-то класс, нужно его загрузить, этим и занимается ClassLoader. Dex-файлы — это специальные файлы внутри APK, которые содержат в себе скомпилированный код ваших приложений. Фишка в том, что формат байт-кода, используемый в Android, отличается от стандартного формата в Java, и этот формат называется DEX.

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

Абстрактный сканер кодовой базы

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

Мы получили ClassLoader из объекта текущего потока, Затем открываем DexFile, передав туда package name нашего приложения. Так мы получим доступ к списку всех имён классов, доступных в DEX-файле.

Теперь нам надо как-то отфильтровать полученный список имён. Современные Android-приложения — это довольно сложные системы, которые подключают к себе тонну различных библиотек и работают на основе объёмной кодовой базы. Поэтому список имён классов может быть очень длинным. Если пытаться загружать каждый класс внутри DEX-а и анализировать, является ли он реализацией нужного нам интерфейса, это может быть довольно долгим процессом.

Поэтому сначала мы фильтруем классы по имени с помощью метода isAcceptableClassName и только после этого загружаем класс и проверяем, подходит ли он нам — проверка будет описана в методе isAcceptableClass.

Если класс нам подходит, вызываем метод-аккумулятор — onScanResult.

Реализация конкретного сканера

Опишем конкретный сканер для наших классов-экспериментов, реализовав наследника ClassScanner:

Чтобы быстро отфильтровать эксперименты по имени, мы вводим соглашение по их именованию — каждый класс эксперимента должен иметь суффикс Experiment. В методе isAcceptableClass мы проверяем, что класс является реализацией интерфейса Experiment, и что проверяемый класс не является самим интерфейсом — это нужно для того, чтобы мы могли создать инстанс класса через clazz.newInstance.

Метод-аккумулятор просто складывает инстанцированные эксперименты в список.

Использование сканера

Запускаем сканер, получаем из него список:

Но возникает вопрос: что отобразится в debug-панели при минифицированной сборке?

Включаем Proguard и обнаруживаем, что в списке экспериментов пусто.

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что этоВключили Proguard и теперь грустим

После этого всё заработает.

Выводы по ClassLoader-у и DexFile-у

Это рабочий способ, несмотря на то, что класс DexFile стал deprecated с API 26.

Но минусов вагон и маленькая тележка:

Способ требует соглашения по неймингу классов-экспериментов. Ко мне часто обращались коллеги, которые не находили свой эксперимент в debug-панели, потому что забывали о необходимом суффиксе Experiment;

Если вы хотите в minified-сборках получить доступ к списку экспериментов, придётся включать keep имён классов-экспериментов. Чем это плохо: потенциальные конкуренты могут посмотреть содержимое вашего APK-файла, увидеть там все названия классов-экспериментов, по именам понять, о чём этот эксперимент, и скопировать логику к себе;

Год спустя я уже не понимаю, почему мы выбрали именно этот способ работы с нашими экспериментами, ведь есть способы проще и лучше =)

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

ServiceLoader + META-INF/services

ServiceLoader — это специальный утилитный класс в Java, который существует там с незапамятных времён. Этот класс позволяет загружать список нужных объектов (сервисов) с помощью service provider-ов.

Service provider обычно представляет собой текстовый файл, который лежит по адресу/src/resources/META-INF/services/fqn.YourClass. Здесь fqn.YourClass — это полное имя с пакетом вашего общего интерфейса/абстрактного класса. В файле на каждой строчке описывается provider — полное имя класса, который реализует нужный вам интерфейс:

Кстати, совершенно неважно, в каком именно модуле вы создадите такой файлик, Gradle подхватит его, и добавит в итоговый APK.

Описав такой файлик, вы сможете воспользоваться методом ServiceLoader.load(YourClass::class.java), который отдаст вам Iterable с объектами. Главное, чтобы у ваших классов был дефолтный конструктор без параметров:

И вроде бы всё хорошо, но камнем преткновения становится как раз этот конфигурационный файл с описанием provider-ов, ведь мы снова приходим к merge-конфликтам, на этот раз — в файле META-INF/services.

Ещё один минус этого файла — он не поддерживает авторефакторинг. Если вы захотите изменить имя класса-эксперимента, перенести в другой пакет, то нужно будет не забыть подправить его имя в META-INF.

Делаем выводы:

Из плюсов: никаких внешних дополнительных зависимостей и простая реализация

Из минусов: проблема ручного заполнения файла META-INF.

Генерация META-INF/services файла

Довольно логичным кажется попробовать автоматически сгенерировать файл META-INF/services, чтобы не страдать от merge-конфликтов. Для этого, конечно же, уже есть несколько готовых библиотек, в частности:

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

Таких warning-ов нет, если воспользоваться библиотечкой ClassIndex. Как с ней работать:

Подключаем её через compileOnly + kapt;

Навешиваем аннотацию @IndexSubclasses на нужный базовый интерфейс:

Подключаем библиотеку к каждому feature-модулю, где есть модельки экспериментов, для их индексации:

После этого мы спокойно используем метод ServiceLoader.load, получаем список экспериментов, радуемся.

Но есть, как говорится, нюансы.

Приключение с ClassIndex

В ролике я говорил, что из рассмотренных вариантов, использование библиотеки ClassIndex для сбора классов по кодовой базе, пожалуй, является самым привлекательным способом. Импакта на скорость сборки практически нет, требований к именованию классов-экспериментов — нет, специфических правил для proguard-а тоже не нужно указывать. Недолго думая, я решил мигрировать наш проект с выбранного когда-то ClassLoader-а на ClassIndex.

Однако при использовании этого способа есть интересный момент.

Одним из недостатков способа с ClassLoader-ом я назвал необходимость сохранять имена классов-экспериментов в минифицированном APK. К чему это может привести? К тому, что злоумышленник-конкурент может легко обнаружить все классы-тоглы внутри вашего приложения, по названиям классов понять логику экспериментов, скопировать её, ну и так далее.

Разумеется, хотелось бы избежать аналогичной проблемы при использовании ClassIndex-а. Но вот незадача — ClassIndex по итогу своей работы генерирует файл META-INF/services/fqn.of.your.class.ClassName, который попадает в конечный APK. В этом файле аккуратно перечислены все имена классов-экспериментов, причём при обработке кода R8 будут перечислены уже минифицированные имена классов, что сильно упростит работу злоумышленникам. Что же делать?

Я отыскал только один надёжный способ выключения генерации META-INF-файла: не добавлять в library-модули, в которых, в основном, и объявляют классы-эксперименты, зависимости для ClassIndex-а. Из-за этого annotation processor не видит, что какой-то класс является наследником индексируемого интерфейса — и не добавляет его описание в итоговый META-INF-сервис.

Для этого я добавил простой Gradle-параметр, в зависимости от которого либо добавляем ClassIndex к library-модулю, либо нет:

В каждом модуле такое писать неудобно, поэтому это можно вынести в convention-плагин и подключать уже его.

На локальную разработку это никак не влияет — продуктовые разработчики не должны добавлять никаких дополнительных флагов или вообще как-то менять свой привычный флоу работы. А для сборки release APK на CI мы можем легко пробросить параметр:

Таким образом, мы избавились от двух проблем способа с ClassLoader-ом:

Нам не нужно иметь какое-то специальное соглашение по именованию классов-экспериментов;

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

Profit.

А где приключения-то?

А приключения были, пока я искал рабочий способ отключения генерации META-INF.

Первым делом я решил попробовать воспользоваться возможностью AGP убирать какие-либо файлы из итогового APK. Это можно сделать при помощи объекта PackagingOptions, к которому можно получить доступ через метод DSL-а packagingOptions. Мы настраиваем наши application-модули через convention-плагины, так что я дописал небольшой блок кода туда:

Но даже если написать такой exclude, то в конечном APK указанного файл всё равно будет присутствовать. Я до конца не понимаю почему так происходит, ведь annotation processor должен отрабатывать раньше, чем начинается запаковка файлов в итоговый APK.

Объект packagingOptions почему-то не помог нам в решении ситуации. Я подумал, что может быть это из-за того, что я указываю не минифицированное имя интерфейса-эксперимента. Добавил proguard-правило, которое сохраняло имя интерфейса Experiment, ничего не изменилось;

Пробовал удалять временные файлы META-INF, которые появлялись в разных модулях, в течение работы annotation processor-а. Я заметил, что в каждом модуле, куда я подключал kapt и зависимость для ClassIndex-а, в папке build/tmp/kapt3/classes/release/META-INF/services появлялся файл для ServiceLoader-а, в котором был записан FQN класса-эксперимента. Я пробовал удалять эти временные файлы до сборки итогового APK (через TaskProvider-ы, доступные в android.applicationVariants), но это не помогало;

Гипотетически, форкнув ClassIndex, и добавив в него флаг для annotation processor-а, можно было бы выключать создание META-INF файла для релизного APK, но это уже немного другая история.

Выводы по ServiceLoader-у и META-INF

Это самый простой способ из найденных мною;

Не требуется никаких зависимостей в runtime.

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

Reflections / Scannoation / Classgraph / etc

Третий способ внутри направления Java Reflections API — это использование различных библиотек. Когда вы попробуете загуглить что-то вроде «java Collect classes with annotation across the codebase», вы обнаружите огромное количество библиотек, связанных с Reflections API.

Я не стал рассматривать абсолютно все, а посмотрел на те, по которым была хорошая документация и много информации на StackOverflow — это Reflections и ClassGraph.

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

С другой стороны, в репозиториях на Github-е этих библиотек можно отыскать issues, где разработчики пытаются найти способ завести их под Android. Чётких инструкций вы там не найдёте, но там описывается способ, которым, теоретически, можно воспользоваться — список нужных вам классов можно собрать на момент компиляции ваших приложений. Когда компиляция полностью пройдет, вы в build-скриптах можете воспользоваться библиотекой, сгенерируете промежуточный файл, а потом в рантайме его прочитаете и всё будет офигенно!

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

Codegen / Bytecode patching

Это последнее направление, о котором я хотел рассказать — возможности кодогенерации.

В решении нашей задачи могут помочь два способа:

Написать свой собственный annotation processor;

Писать свой annotation processor можно, но зачем, если уже есть тот же ClassIndex, который сделает ровно то, что нужно. А вот вторая возможность выглядит интересно.

Для модификации байт-кода существует фреймворк ASM. После того, как ваше приложение уже скомпилировано, итоговый байт-код получен, вы можете его модифицировать как вам нужно.

Коллеги из компании Joom написали библиотеку Colonist, которая идеально подходила под наши нужды. Забавный факт: статья про Colonist вышла буквально на следующий день после начала моего ресёрча.

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

Во-первых, нужно будет подключить библиотеку к вашему приложению. Подробнее про это можно почитать на Github-е:

Во-вторых, мы объявляем аннотацию-«колонию». Колония — это место, куда мы будем направлять «поселенцев», то есть, некоторый аккумулятор, который будет собирать нужные нам классы по правилам, которые мы задаём при помощи тонны аннотаций:

Мы описали аннотацию-колонию ExperimentsColony, в которую будем пускать определённых поселенцев — наследников класса Experiment (не оговорился, именно абстрактного класса Experiment). Также мы говорим Colonist-у, что поселенцев мы будем обрабатывать через специальный callback.

Остаётся описать класс-колонию / наш коллектор:

Отмечаем его аннотацией колонии (@ExperimentsColony), добавляем метод для приёма поселенцев (отмечен аннотацией @OnAcceptSettler), готово!

Что происходит под капотом: если компиляции APK посмотреть на него с помощью утилиты jadx-gui, то можно увидеть, что код коллектора отличается от того, что вы написали. Произошла магия изменения байт-кода, и теперь есть специальный кусочек кода, который собирает наследников нужного класса и складывает их в определенный метод.

Feature flags меню разработчика что это. Смотреть фото Feature flags меню разработчика что это. Смотреть картинку Feature flags меню разработчика что это. Картинка про Feature flags меню разработчика что это. Фото Feature flags меню разработчика что этоСмотрим итоговый байт-код через jadx-gui

    Остаётся только использовать колонию

    Выводы по Colonist

    Это рабочее решение, хоть и используется альфа-версия библиотеки;

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

    У библиотеки магия под капотом. Если обычный annotation processor — это ещё более-менее понятная вещь, вы можете пощупать сгенерированный код, то здесь происходит гораздо больше волшебства (если захотите разобраться с ASM — вот томик документации);

    Подведём итоги

    Во-первых, необязательно страдать от merge-конфликтов, ведь можно от них уйти;

    Есть множество способов собрать разбросанные по кодовой базе модели в единый список;

    Если бы у нас был Dagger, этой статьи, возможно, не было =)

    Совет для вас — не создавайте лишних абстракций, упрощайте жизнь себе и коллегам.

    Буду рад вопросам и дополнениям в комментариях.

    Маленький опрос для большого исследования

    Мы проводим ежегодное исследование технобренда крупнейших IT-компаний России. Нам очень нужно знать, что вы думаете про популярных (и не очень) работодателей. Опрос займет всего 10 минут.

    Источник

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *