Pull to refresh
56
Данил Перевалов@princeparadoxes

Android developer

80
Subscribers
Send message

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

А firebender не пробовали? 

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

И как вообще оплачиваете это?

Тут не подскажу, я непосредственно в закупках не учавствую

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

Спасибо за статью!

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

У нас есть похожая система для инициализации на старте. Для её реализации рассматривали и ServiceLoader, но в итоге сделали через метаданные в AndroidManifest. Причиной тому была нестабильность работы ServiceLoader в Android. В этом Pull Request, в репозитории корутин, как раз уходят от ServiceLoader в пользу Class.forName. Там в комментариях к коду и Pull Request достаточно подробно расписано. Ну или вот, у меня похожая ситуация возникла в Ktorfit.

Как я понимаю, в идеальной ситуации плохая работа ClassLoader в Android должна компенсироваться R8. Он должен заменить ServiceLoader.load(X.class, X.class.getClassLoader()).iterator() на Arrays.asList(new X[] { new Y(), ..., new Z() }).iterator(). Но в тех же корутинах это почему-то не всегда работало, хотя код у них как раз подходящий:

ServiceLoader.load(
MainDispatcherFactory::class.java,
MainDispatcherFactory::class.java.classLoader
).iterator()

Мы у себя тоже используем ServiceLoader для демоприложений, но только для сущностей которые находятся в debug папке и на прод никогда не попадут.

Поэтому у меня возникли такие вопросы:

1) У вас не возникало проблем на проде из-за ServiceLoader?

2) Вы у себя в Gradle-таске получаете все META-INF файлы. Не думали достать из них все имена наследников BaseInitializer и просто сгенерировать класс в котором бы у каждого из наследников вызывается init()? Как-будто это позволит избежать потенциальных проблем с ServiceLoader и прокидывать что-то в конструктор наследников BaseInitializer.

3) Думали ли вы применять у себя Dynamic Feature? Для нас это была одна из причин не использовать ServiceLoader, так как для оптимизации R8 ему нужен доступ к классу, что не возможно при использовании Dynamic Feature.

А не лучше ли будет использовать debounce вместо delay? С debounce отбросятся ненужные события и сразу будет нарисован нужный (последний) вариант. Это уменьшит общее количество отрисовок.

Более того, debounce можно повесить на события с UI на ViewModel, которые сообщают о смене позиции камеры, что уберёт часть запросов на сервер.

P.S. Compose рассматривает List (а они основа вашего MapUiState), как Unstable. Это приводит к лишним рекомпозициям, так как Compose не может понять, изменилось ли что-то в списке или нет и принудительно рекомпозирует. Попробуйте использовать неизменяемые коллекции.

Есть такое) В статье это больше для примера того, как усложнить восприятие кода для атакующего.
И насколько я понимаю, при 25 или даже 100 итерациях погрешность из-за чисел с плавающей запятой всё равно будет слишком мала и при округлении "нивелируется".
Это больше актуально когда все вычисления во float (особенно умножение) или когда итераций цикла миллионы или миллиарды.

1) С ходу профита особого не вижу. Это просто property в которую идёт запись и чтение. Но если есть сценарий, где SharedFlow что-то сильно улучшит, то можно без проблем и его применить.

2) В таком случае можно GlobalEvent сделать не sealed class, а interface. Реализации хранить в нескольких модулях (например, api-модуль фичей), которые подключать к тем модулям, которым этот GlobalEvent нужен. Правда тут стоит быть аккуратным, если GlobalEvent нужен лишь ограниченному числу модулей, то возможно он не такой уж и глобальный? И стоит рассмотреть другие варианты общения между компонентами.

А, Вы про это. Да, есть такое, в проде такой код использовать нельзя. Он тут скорее для иллюстрации логики подмешивания дополнительных данных. У меня в нём ещё и корнер-кейсы, вроде того, что android_get_device_api_level может вернуть -1, не учтены.

Я на C++ давно уже не писал, так что это точно не хороший код)

Мультибиндинги рассматривали. Более того, до внедрения описанной в статье системы с meta-data, мультибиндинги были основным способом создания коллекций таких интерфейсов. Они и сейчас у нас остаются для, скажем так, не критичных штук - вроде диплинков и шорткатов.

В целом это весьма хорошее решение. Но конкретно у нас с таким подходом есть ряд проблем:

  • Нет прямой поддержки dynamic-feature, так как их код не виден из главного модуля.

  • Для демоприложений такой граф надо настраивать для каждого из демоприложений. Потом ещё и поддерживать. С системой с meta-data достаточно просто подключить модуль к приложению и он сам заработает.

  • У app есть два варианта "знать" о подключённых к нему модулях: implementation и runtimeOnly. Первый вариант позволяет видеть код из подключённых модулей в app, но в тоже время, если в каком-то из подключённых модулей меняется код, то и app вынужден пересобраться (иногда лишь частично, но не суть). Это било по времени горячей сборки и мы перешли на второй вариант - runtimeOnly. С ним код в app уже не доступен, зато и лишних пересборок нет. Так как код недоступен, то и от мультибиндингов смысла нет.

Он используется в C++ слое. Файловый дескриптор используется двумя утилитами eventfd и epoll, они позволяют сделать что-то вроде wait/notify на уровне системы. Файловый дескриптор выступает чем-то вроде монитора.

Например, приложение сделало все действия которые хотело. Ему больше нечего делать и оно ставится в режим ожидания (wait). Когда системе надо будет его разбудить, она использует файловый дескриптор чтобы его уведомить о том, что пора обрабатывать новые действия (notify).

Если интересна тема, то есть статья про Looper в C++ слое, там подробнее про эту логику написано.

А не размышляли над тем, чтобы уменьшить количество подключаемых модулей к demo-приложению через моки?

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

mockk<BduCustomizeDependencies>(relaxed = true)

Выглядит как костыль, не спорю, но это работает. Правда, при условии, что у вас фичи делятся на api/(impl, ui, bl) модули.

В нашем случае это помогало избегать подключения аналогов :legacy-heavy-module. Что резко сокращало количество подключаемых модулей.

Справедливо. Как-то не подумал об этом

non-blocking IO подход же реализует не фреймворк многопоточки, а конкретная сущность, которая делает IO вызов, например, сетевой клиент. Соответственно, от фреймворков многопоточки ничего зависеть не будет. Я хотел именно их между собой сравнить. И, как мне показалось, на сравнение фреймворков многопоточки, блокирующий IO или нет, не должно повлиять. Поэтому взял блокирующий, так как он сильно проще визуально и нагляднее.

Или я ошибся?

Я генерировал основной график в Google Docs. Затем обрисовывал его в https://app.diagrams.net/ и добавлял дополнительную информацию. Остальные иллюстрации тоже там делал.

К сожалению количество строк сгенерированного кода не считал, а вот по размеру сборки подскажу. Для debug сборки (без обфускации, AppBundle и прочего) размер такой:
- Dagger. Всего 240.6 Мб. Вес именно кода 73.3 Мб.
- Yatagan + kapt. Всего 239.1 Мб. Вес именно кода 71.8 Мб.
- Yatagan + Reflect. Всего 239 Мб. Вес именно кода 71.7 Мб.
- При использовании KSP вес примерно такой же, как и с Yatagan + Kapt.

Если что, размер релизной сборки после обфускации и AppBundle - 80 Мб.

По размеру сборки и кода понятно, что Yatagan у нас генерирует где-то 100 Кб кода, а Dagger 1.5 Мб. Так что, где-то в 15 раз разница по количеству генерируемого кода.

Ну и у нас для Dagger проброшенны аргументы formatGeneratedSource как disabled и fastInit как enable. По идее, оба флага могут чуть уменьшать размер сгенерированного Dagger кода.

Протестировать количество каналов хорошая идея. Спасибо!

  1. Количество каналов памяти, как я понимаю, в основном повысит пропускную способность памяти. Поднятие частоты памяти тоже на это влияет. Но по тестам частота/пропускная способность памяти почти не повлияла. Как будто увеличение количества каналов не должно помочь. Или я заблуждаюсь?
    По поводу кэша - да. Чем его больше - тем лучше) Но тут сложно определить насколько именно. Наверно только если сравнить Ryzen 5800X и Ryzen 5800X3D которые только размером кэша и отличаются.

  2. Тут речь скорее про то, что замена HDD на SSD достаточно дешёвый способ увеличить скорость сборки. Относительно замены процессора или оперативной памяти, конечно же. Так что, честно говоря, не вижу особого смысла собираться на HDD.

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

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

Dagger, например, сам генерирует Dagger<ComponentName>.Builder если отсутствует костомный Component.Builder.

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

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

Получится примерно так

internal class SomeEventBuilder(
   private val userId: Int,
   private val itemsIds: List<Int>,
   private val timestampProvider: () -> Long
) : EventBuilder {

   override fun buildEvent() = event {
       analytics1 {
           eventName = "SomeEventName"
           reason = "SomeReason"
           action = CommonAnalyticsAction.CLICK
           source {
               feature = Feature1AnalyticsFeature.FEATURE_1
               screen = Feature1AnalyticsScreen.FEATURE_1_SCREEN
               block = customBlock("SomeBlock")
           }
       }
       analytics2 {
           eventName = "SomeName"
           source = "SomeBlockOfUser$userId"
           action = customAction("Click")
           screen = Feature1AnalyticsScreen.FEATURE_1_SCREEN
       }
   }
}

1

Information

Rating
Does not participate
Location
Омск, Омская обл., Россия
Works in
Date of birth
Registered
Activity