Начну с истории. Сидим мы как-то с коллегами в баре, и я завел разговор про compose. Описывал я его при помощи маркетинговых заголовков гугла, о том что в нем нужно меньше кода, он проще и удобнее, производительнее. На что получил вопрос: “ну вот, а конкретно, какие проблемы решаем compose?”. В тот вечер я их так и не убедил, и осадочек остался. Чтобы компенсировать этот пробел, давайте разберем, а зачем вообще compose появился, какие проблемы он решает, в чем вообще прикол декларативного UI?
Как обычно то что я планировал сделать одним постом, разрослось на два. Поэтому в первом посте разберем что вообще такое декларативный UI и немного истории, а уже во втором я постараюсь ответить на вопрос какие конкретно проблемы решает compose.
👉 История развития UI
👉 Какие проблемы решает Compose
👉 Проблемы Compose
Как обычно то что я планировал сделать одним постом, разрослось на два. Поэтому в первом посте разберем что вообще такое декларативный UI и немного истории, а уже во втором я постараюсь ответить на вопрос какие конкретно проблемы решает compose.
👉 История развития UI
👉 Какие проблемы решает Compose
👉 Проблемы Compose
🤔22👍6🥰4🔥2❤1
Давайте начнем с того, что поймем разницу м/у императивным и декларативным UI. Понять разницу можно легко через аналогию. Допустим вы хотите заказать сэндвич в кафе.
В императивном стиле это выглядит так: "Нужно взять белый батон, разрезать его пополам, затем намазать хлеб кетчупом, после нарезать колбасы и положить на батон, затем добавить сыр, нарезать помидор и положить на сыр, после добавить лук и накрыть его другом куском хлеба"
В декларативном стиле: "Хочу сэндвич с колбасой".
Из примера должно быть понятно что отличия в том, что в одном случае мы описываем как хотим получить результат, во втором случае описываем лишь результат, а система должна уже сам решить как его достигать.
Касательно интерфейсов индустрия прошла немалый путь. Изначально как вы помните интерфейсов как таковых не было, была командная строка и всем ее хватало. Затем начали появляться оконные системы потому как был запрос на удобный способ взаимодействия с компом без запоминания тысячи команд.
В этот же момент начали появляться объектно ориентированные языки программирования, и оказалось что довольно удобно при помощи объектов описывать UI элементы вроде кнопок, текста, чекбоксов и т.д.
Затем разработчики поняли что все равно приходится дублировать код, а еще приходится постоянно кучу раз перезапускать сборку, чтобы понять, а стоит ли кнопка в том месте в котором должна.
Появилась идея, давайте описывать UI в специальном формате, чтобы можно было, не пересобирая приложение, понять стоит ли кнопка там где нужно. Появились специальные языки разметки, в вэбе с которого все началось это html, в Android xml, в iOS xib и еще многие другие для разных платформ. Так или иначе мы пришли к тому, что делать интерфейсы из кода не удобно, охото иметь возможно быстро накидать верстку, проверить специальным инструментом для preview все ли стоит там где нужно, а уже потом прикручивать логику.
И вот тут казалось бы можно и остановится, ведь интерфейсы уже стали декларативными. Ведь мы уже не говорим как именно рисовать интерфейс, мы описываем просто результат который хотим получить при помощи верстки. Почему тогда декларативными называют только недавно вышедшие SwiftUI, Compose и React?
Основная проблема в том, что требования к интерфейсам со временем тоже поменялись. Если раньше нас устраивала статичная страница, то сейчас мы хотим красивые анимации, кучу плавных переходов, красивые всплывающие окна. Интерфейсы уже давно не статичные, они должны меняться в ответ на действия пользователя, все должно быть максимально интерактивно. Получается что у нас есть верстка, а также есть код который её меняет.
Появляется код в приложении который добавляет элементы, удаляет, что-то скрывает или анимирует. В этот момент наш UI перестает быть декларативным, потому как мы явно прописываем в коде, что конкретно нужно поменять. Вот тут сдвинуть кнопку, а вот эту перекрасить вот в такой цвет, а вот этот чекбокс сделать disable. Думаю суть вы уловили.
При всем при этом UI становится сложнее, появляется куча элементов которые уже не опишешь через язык вёрстки. Вспомните сколько нужно написать кода, чтобы показать список в котором будет еще список с горизонтальным скролом?
У каждой большой компании появляется своя выделенная команда, которая делает свои UI элементы. И вы только вдумайтесь, отдельная команда задача которой разрабатывать свои кнопки немного отличающиеся по поведению от стандартных. Сука кнопки!
Сложность появляется там, где ее по идее быть не должно. Эпоха языков вёрстки подходит к концу. Они уже давно не помогают решить проблемы которые у нас возникают. Технологии уже давно позволяют быстро компилировать участи кода, чтобы показывать preview верстки даже если она описана не через xml или html. Поэтому xml уже сдает позиции, да и языки исключительно для верстки тоже.
В императивном стиле это выглядит так: "Нужно взять белый батон, разрезать его пополам, затем намазать хлеб кетчупом, после нарезать колбасы и положить на батон, затем добавить сыр, нарезать помидор и положить на сыр, после добавить лук и накрыть его другом куском хлеба"
В декларативном стиле: "Хочу сэндвич с колбасой".
Из примера должно быть понятно что отличия в том, что в одном случае мы описываем как хотим получить результат, во втором случае описываем лишь результат, а система должна уже сам решить как его достигать.
Касательно интерфейсов индустрия прошла немалый путь. Изначально как вы помните интерфейсов как таковых не было, была командная строка и всем ее хватало. Затем начали появляться оконные системы потому как был запрос на удобный способ взаимодействия с компом без запоминания тысячи команд.
В этот же момент начали появляться объектно ориентированные языки программирования, и оказалось что довольно удобно при помощи объектов описывать UI элементы вроде кнопок, текста, чекбоксов и т.д.
Затем разработчики поняли что все равно приходится дублировать код, а еще приходится постоянно кучу раз перезапускать сборку, чтобы понять, а стоит ли кнопка в том месте в котором должна.
Появилась идея, давайте описывать UI в специальном формате, чтобы можно было, не пересобирая приложение, понять стоит ли кнопка там где нужно. Появились специальные языки разметки, в вэбе с которого все началось это html, в Android xml, в iOS xib и еще многие другие для разных платформ. Так или иначе мы пришли к тому, что делать интерфейсы из кода не удобно, охото иметь возможно быстро накидать верстку, проверить специальным инструментом для preview все ли стоит там где нужно, а уже потом прикручивать логику.
И вот тут казалось бы можно и остановится, ведь интерфейсы уже стали декларативными. Ведь мы уже не говорим как именно рисовать интерфейс, мы описываем просто результат который хотим получить при помощи верстки. Почему тогда декларативными называют только недавно вышедшие SwiftUI, Compose и React?
Основная проблема в том, что требования к интерфейсам со временем тоже поменялись. Если раньше нас устраивала статичная страница, то сейчас мы хотим красивые анимации, кучу плавных переходов, красивые всплывающие окна. Интерфейсы уже давно не статичные, они должны меняться в ответ на действия пользователя, все должно быть максимально интерактивно. Получается что у нас есть верстка, а также есть код который её меняет.
Появляется код в приложении который добавляет элементы, удаляет, что-то скрывает или анимирует. В этот момент наш UI перестает быть декларативным, потому как мы явно прописываем в коде, что конкретно нужно поменять. Вот тут сдвинуть кнопку, а вот эту перекрасить вот в такой цвет, а вот этот чекбокс сделать disable. Думаю суть вы уловили.
При всем при этом UI становится сложнее, появляется куча элементов которые уже не опишешь через язык вёрстки. Вспомните сколько нужно написать кода, чтобы показать список в котором будет еще список с горизонтальным скролом?
У каждой большой компании появляется своя выделенная команда, которая делает свои UI элементы. И вы только вдумайтесь, отдельная команда задача которой разрабатывать свои кнопки немного отличающиеся по поведению от стандартных. Сука кнопки!
Сложность появляется там, где ее по идее быть не должно. Эпоха языков вёрстки подходит к концу. Они уже давно не помогают решить проблемы которые у нас возникают. Технологии уже давно позволяют быстро компилировать участи кода, чтобы показывать preview верстки даже если она описана не через xml или html. Поэтому xml уже сдает позиции, да и языки исключительно для верстки тоже.
👍28❤8🔥7
Как я уже упоминал, в анализе технологий нужно всегда исходить из проблемы, которую они решают. Я выделил 4 проблемы, которые он может решить. Некоторые притянуты зауши, тем не менее…
Сложность
Основная проблема которую решает Compose это сложность. Очевидно что мы становимся очень привередливыми к UI. Обычные элементы нам уже кажутся скучными. Чтобы у продукта был коммерческий успех у него обязан быть классный UX и красивый дизайн. Градиенты, тени, сложные списки со сложной анимацией все это переходит в разряд обычного приложения и если этого нет, то мы начинаем чувствовать неудобство при использовании.
Все это приводит к страшной запутанности кода. У нас есть верстка в xml, есть Custom View которые описываются кодом. После мы должны написать код для фрагмента, далее поместить его в Activity, Activity в утку, утку в зайца ну вы поняли. У всех этих элементов есть свои ЖЦ и даже простое приложение с одной кнопкой требует кучу кода. Каждый раз тратим на кучу времени на болерплейт, который как кажется можно не писать.
В основе Compose лежит идея описания UI при помощи чистых функций. Ранее я делал посты, как чистые функции помогают сделать код проще, в запиненных сообщениях. Благодаря тому, что делаем все через код, это позволяет на лету заменять расположение элементов на экране. Не нужно теперь создавать свою кнопку если нужно в ней например разместить два TextView, теперь это все доступно их коробки.
Файл с версткой
С версткой какая проблема, мы создаем её через xml, который потом упаковывается в специальный бинарный формат для скорости. В момент когда нам нужно показать экран, нужный файл с версткой сначала подгружается из файловой системы, затем парсится и через рефлексию создаются нужные View. Только после этого они рассчитываются и отрисовываются на экране.
Это конечно уже максимально оптимизированный процесс, однако наличие рефлексии и подгрузки из файла говорит о том, что накладные расходы все же есть.
Состояние у View
В приложении обычно есть как минимум два слоя. UI слой и Presentation. Архитектура UI в Android построена таким образом, что у нее есть состояние. Это состояние чекбоксов, введенный текст, добавленные или удаленные View и т.д. В Presentation слое у нас тоже есть состояние. Основная проблема, что эти состояния нужно синхронизировать.
Это проводит к тому, что у нас логика управления состоянием размазывается. Какую-то часть сохраняем в Bundle, какую-то часть в Presentation. Со временем все это разрастается так, что хочется сменить профессию. Порой стараются не делать состояние в Presentation, и в итоге у нас состояние контролирует UI, что он делать не должен. Есть архитектуры вроде MVI которые сглаживают эту проблему, но не решают окончательно.
Не должно быть у UI своего состояние. Состояние должно быть только у Presentation слоя, который уже передает его UI. Compose именно так и построен. У Compose функций нет своего состояния, мы лишь указываем что хотим получать на экране в зависимости от изменения состояния Presentation.
⬇
Сложность
Основная проблема которую решает Compose это сложность. Очевидно что мы становимся очень привередливыми к UI. Обычные элементы нам уже кажутся скучными. Чтобы у продукта был коммерческий успех у него обязан быть классный UX и красивый дизайн. Градиенты, тени, сложные списки со сложной анимацией все это переходит в разряд обычного приложения и если этого нет, то мы начинаем чувствовать неудобство при использовании.
Все это приводит к страшной запутанности кода. У нас есть верстка в xml, есть Custom View которые описываются кодом. После мы должны написать код для фрагмента, далее поместить его в Activity, Activity в утку, утку в зайца ну вы поняли. У всех этих элементов есть свои ЖЦ и даже простое приложение с одной кнопкой требует кучу кода. Каждый раз тратим на кучу времени на болерплейт, который как кажется можно не писать.
В основе Compose лежит идея описания UI при помощи чистых функций. Ранее я делал посты, как чистые функции помогают сделать код проще, в запиненных сообщениях. Благодаря тому, что делаем все через код, это позволяет на лету заменять расположение элементов на экране. Не нужно теперь создавать свою кнопку если нужно в ней например разместить два TextView, теперь это все доступно их коробки.
Файл с версткой
С версткой какая проблема, мы создаем её через xml, который потом упаковывается в специальный бинарный формат для скорости. В момент когда нам нужно показать экран, нужный файл с версткой сначала подгружается из файловой системы, затем парсится и через рефлексию создаются нужные View. Только после этого они рассчитываются и отрисовываются на экране.
Это конечно уже максимально оптимизированный процесс, однако наличие рефлексии и подгрузки из файла говорит о том, что накладные расходы все же есть.
Состояние у View
В приложении обычно есть как минимум два слоя. UI слой и Presentation. Архитектура UI в Android построена таким образом, что у нее есть состояние. Это состояние чекбоксов, введенный текст, добавленные или удаленные View и т.д. В Presentation слое у нас тоже есть состояние. Основная проблема, что эти состояния нужно синхронизировать.
Это проводит к тому, что у нас логика управления состоянием размазывается. Какую-то часть сохраняем в Bundle, какую-то часть в Presentation. Со временем все это разрастается так, что хочется сменить профессию. Порой стараются не делать состояние в Presentation, и в итоге у нас состояние контролирует UI, что он делать не должен. Есть архитектуры вроде MVI которые сглаживают эту проблему, но не решают окончательно.
Не должно быть у UI своего состояние. Состояние должно быть только у Presentation слоя, который уже передает его UI. Compose именно так и построен. У Compose функций нет своего состояния, мы лишь указываем что хотим получать на экране в зависимости от изменения состояния Presentation.
⬇
🔥24👍2
Путаница с темами и стилями
Очень частая ошибка, особенно у начинающих. Не понятно чем стиль отличается от темы. Они буквально описываются одним и тем же атрибутом style. В любую View можно указать вместо темы стиль, а вместо стиля тему и никакой линтер нигде не скажет, что что-то не так. Разница описывается лишь неймингом в xml. Когда нет никаких ограничений, рано или поздно кто-то сотворит фигню.
Помимо этого порой сложно узнать какие атрибуты вообще тема устанавливает. Это происходит из архитектурного ограничения. Для стилей и тем хорошо подходит принцип наследования. Однако xml это вообще не тот язык, чтобы описывать наследование. Поэтому у тем и стилей достаточно костыльное наследование в xml. Нужно скакать по ссылкам в надежде, что ты доберешься до корневой темы, которая устанавливает нужные тебе атрибуты.
В compose эту проблему решили просто тем, что теперь тема или стиль это конкретный объект. Благодаря системе типизации их невозможно спутать, компилятор тупо такое не пропустит. Можно легко понять какие атрибуты у тебя установлены в теме, так как это конфигурируется специальной функцией.
Очень частая ошибка, особенно у начинающих. Не понятно чем стиль отличается от темы. Они буквально описываются одним и тем же атрибутом style. В любую View можно указать вместо темы стиль, а вместо стиля тему и никакой линтер нигде не скажет, что что-то не так. Разница описывается лишь неймингом в xml. Когда нет никаких ограничений, рано или поздно кто-то сотворит фигню.
Помимо этого порой сложно узнать какие атрибуты вообще тема устанавливает. Это происходит из архитектурного ограничения. Для стилей и тем хорошо подходит принцип наследования. Однако xml это вообще не тот язык, чтобы описывать наследование. Поэтому у тем и стилей достаточно костыльное наследование в xml. Нужно скакать по ссылкам в надежде, что ты доберешься до корневой темы, которая устанавливает нужные тебе атрибуты.
В compose эту проблему решили просто тем, что теперь тема или стиль это конкретный объект. Благодаря системе типизации их невозможно спутать, компилятор тупо такое не пропустит. Можно легко понять какие атрибуты у тебя установлены в теме, так как это конфигурируется специальной функцией.
🔥26👍3❤1
Баги
Баги, баги баги. Класс View развивался с самой первой версии Android, а это уже как 14 лет. Даже несмотря на это, во View все еще есть недочеты. Ожидать что Compose, которому всего пару лет, сразу будет работать идеально это довольно оптимистично. Естественно Compose еще поуши в багах, которые со временем (я надеюсь) пофиксят. Поэтому если и внедрять его в существующие проекты то очень аккуратно и желательно под каким-нибудь фиче флагом.
Гугл, который любит Deprecated
Это не относится напрямую к самому фреймворку Compose, однако частично его касается. Compose делают инженеры из компании Гугл. А Гугл славится чем, что любит депрекейтить свои же решения. Реально, они в одном из релизов, задепрекейтили аннотацию Deprecated. Support Library, Billing Library, Synthetics, Volley (формально она не deprecated, но ее поддерживает всего один разраб, а пользуются еще 2.5, что не особо вдохновляет) это те немногие решения от которых Гугл просто отказались.
Разумеется нет никаких оснований полагать, что от разработки и поддержки Compose откажутся просто так. Однако нужно понимать, что Compose закрытый проект, который поддерживает одна компания и в случае чего будет достаточно трудно с него слезть. Будет конечно сюр, если они через пару лет выпустят ComposeX и скажут, все что было до этого deprecated, пожалуйста, не используйте.
Производительность
Один из плюсов Compose по обещаниям разработчиков это производительность. В целом так оно и есть, благодаря подходу позиционной мемоизации (поставьте пару сердец если хотите про это отдельный пост). Compose функции и правда очень производительны и перерисовывается только малая часть экрана которая изменилась.
Однако ходят слухи о том, что в Compose тормозят списки. Списки действительно тормозят, но только в debug сборке, потому как в релизной сборке включается куча оптимизаций про которые забывают. Некоторые разработчики проводили тесты которые показывают что, Compose и правда медленнее текущего решения примерно на 10% в релизной сборке, что для нового фреймворка терпимо.
В итоге можно сказать, что Compose существенно не влияет на производительность. Однако в дебажной сборке он будет безбожно тормозить. Помимо этого плохая идея смешивать RecyclerView и Compose, вот тут оптимизации идут лесом и списки правда будут тормозить даже в релизной сборке. Если решили делать списки на Compose, то делайте их полностью на Compose.
Диалоги
Вот тут пока грустно. Если посмотреть на базовый пример того, как показываются диалоги. Решение выглядит мягко говоря костыльно. Нельзя как раньше сказать системе покажи диалог и забить на него.
Сейчас показывая диалог вы должны полностью контролировать его показ, он как бы становится частью вашей верстки. Диалог в Compose обязывает создавать состояние, чтобы контролировать показ этого самого диалога. Удобно это или нет, уже решать вам.
Скоуп ViewModel
С ViewModel тоже не все однозначно. Изначально ViewModel проектировались именно под скоуп фрагмента или activity, другими словами ViewModel умирали со смертью компонента в котором используются. Фишка в том, что если мы используем Compose без фрагментов или activity у нас нет компонентов, которые бы позволяли отлеживать ЖЦ. Точнее говоря он есть, та самая первая Acitivty с которой начинается запуск Compose и если вы будете просто использовать ViewModel они будут жить пока не умрет та самая первая Activity. Вот только она нихера не умрет, пока приложение используется и это может доставить гемороя.
Частично эту проблему решили. Однако решили тем, что вы обязаны использовать библиотеку навигации от гугла, если хотите что-то другое, то делайте все на фрагментах. Вот такой тоталитаризм от гугла.
P.S прикиньте в телеге есть пользователь с ником “Deprecated” и если писать эту аннотацию с @ то телега считает что я указываю именно его. Забавно, вот у него наверное оповещений навалом с разных каналов про разработку)
UPD: Compose не закрытый проект, а open source тут я вас обманул. Однако если Гугл откажется от его поддержки не факт, что кто-то возмется
Баги, баги баги. Класс View развивался с самой первой версии Android, а это уже как 14 лет. Даже несмотря на это, во View все еще есть недочеты. Ожидать что Compose, которому всего пару лет, сразу будет работать идеально это довольно оптимистично. Естественно Compose еще поуши в багах, которые со временем (я надеюсь) пофиксят. Поэтому если и внедрять его в существующие проекты то очень аккуратно и желательно под каким-нибудь фиче флагом.
Гугл, который любит Deprecated
Это не относится напрямую к самому фреймворку Compose, однако частично его касается. Compose делают инженеры из компании Гугл. А Гугл славится чем, что любит депрекейтить свои же решения. Реально, они в одном из релизов, задепрекейтили аннотацию Deprecated. Support Library, Billing Library, Synthetics, Volley (формально она не deprecated, но ее поддерживает всего один разраб, а пользуются еще 2.5, что не особо вдохновляет) это те немногие решения от которых Гугл просто отказались.
Разумеется нет никаких оснований полагать, что от разработки и поддержки Compose откажутся просто так. Однако нужно понимать, что Compose закрытый проект, который поддерживает одна компания и в случае чего будет достаточно трудно с него слезть. Будет конечно сюр, если они через пару лет выпустят ComposeX и скажут, все что было до этого deprecated, пожалуйста, не используйте.
Производительность
Один из плюсов Compose по обещаниям разработчиков это производительность. В целом так оно и есть, благодаря подходу позиционной мемоизации (поставьте пару сердец если хотите про это отдельный пост). Compose функции и правда очень производительны и перерисовывается только малая часть экрана которая изменилась.
Однако ходят слухи о том, что в Compose тормозят списки. Списки действительно тормозят, но только в debug сборке, потому как в релизной сборке включается куча оптимизаций про которые забывают. Некоторые разработчики проводили тесты которые показывают что, Compose и правда медленнее текущего решения примерно на 10% в релизной сборке, что для нового фреймворка терпимо.
В итоге можно сказать, что Compose существенно не влияет на производительность. Однако в дебажной сборке он будет безбожно тормозить. Помимо этого плохая идея смешивать RecyclerView и Compose, вот тут оптимизации идут лесом и списки правда будут тормозить даже в релизной сборке. Если решили делать списки на Compose, то делайте их полностью на Compose.
Диалоги
Вот тут пока грустно. Если посмотреть на базовый пример того, как показываются диалоги. Решение выглядит мягко говоря костыльно. Нельзя как раньше сказать системе покажи диалог и забить на него.
Сейчас показывая диалог вы должны полностью контролировать его показ, он как бы становится частью вашей верстки. Диалог в Compose обязывает создавать состояние, чтобы контролировать показ этого самого диалога. Удобно это или нет, уже решать вам.
Скоуп ViewModel
С ViewModel тоже не все однозначно. Изначально ViewModel проектировались именно под скоуп фрагмента или activity, другими словами ViewModel умирали со смертью компонента в котором используются. Фишка в том, что если мы используем Compose без фрагментов или activity у нас нет компонентов, которые бы позволяли отлеживать ЖЦ. Точнее говоря он есть, та самая первая Acitivty с которой начинается запуск Compose и если вы будете просто использовать ViewModel они будут жить пока не умрет та самая первая Activity. Вот только она нихера не умрет, пока приложение используется и это может доставить гемороя.
Частично эту проблему решили. Однако решили тем, что вы обязаны использовать библиотеку навигации от гугла, если хотите что-то другое, то делайте все на фрагментах. Вот такой тоталитаризм от гугла.
P.S прикиньте в телеге есть пользователь с ником “Deprecated” и если писать эту аннотацию с @ то телега считает что я указываю именно его. Забавно, вот у него наверное оповещений навалом с разных каналов про разработку)
UPD: Compose не закрытый проект, а open source тут я вас обманул. Однако если Гугл откажется от его поддержки не факт, что кто-то возмется
❤46👍5
Все слышали про Дядю Боба и его вклад в индустрию. Одна из известнейших его идей это Clean Architecture, которую сейчас используют оочень много проектов. В какой проект не приди там будет Clean, за некоторыми исключениями. На этот пост меня сподвигла ситуация, когда я собеседовал разработчика и при решении задачи с архитектурой, он сказал что всегда нужно делать Interactor, даже когда в них нет логики и они просто проксируют вызовы, на всякий случай.
Соль в том, что чем дальше двигается индустрия тем больше Clean Architecture обретает ореол сверхценной идеи. Если ты не используешь Clean значит твоя архитектура хрень и скорее всего проект не взлетит. Успех проекта зависит же исключительно от архитектуры!
Мы стали забывать, что Clean это лишь рекомендация человека, который был простым разработчиком. Да с большим опытом, да очень медийным, но все равно разработчиком. Людям склонно ошибаться, индустрия разработки это не математика, нельзя придумать формулу, которая будет одинакова везде. Каждый проект отличается.
В целом можно понять почему разработчики пытаются полностью следовать Clean и делают кучу интерфейсов. Всегда есть страх того, что вот сейчас придет заказчик и скажет поменять базу данных. А мы как раз готовились к этому всю жизнь, у нас все на интерфейсах и мы быстро мигрируем. Только вот прикол в том, что заказчик не придет с таким, особенно если речь идет о Android. Он максимум придет с предложением перекрасить кнопки в другой цвет. Мы защищаемся от изменений которые никогда не произойдут.
К чему все эти разглагольствования. Clean это лишь рекомендация по архитектуре, а это значит не нужно все делать именно так как описано в книге Мартина, можно и нужно отходить в сторону если считаете, что так будет удобнее. Даже используя Clean нужно задавать вопросы и не следовать слепо общему тренду.
Я не буду говорить как правильно делать архитектуру, этого никто не знает. Если человек говорит, что знает как сделать архитектуру которая всем поможет он пиздит. Однако я могу сказать что не нужно делать:
👉 Не нужно покрывать все и вся интерфейсами, если вы не подменяете это в тестах. Эти интерфейсы только мешают. У нас сейчас крутые IDE, которые если необходимо могут сами выделить интерфейсы когда это понадобится.
👉 Не нужно делать проксирующие интеракторы без логики. Нафиг эти интеракторы которые существуют просто потому что. Чем меньше кода, тем меньше багов.
👉 Не нужно спорить про разницу между Interactor и UseCase. Давайте начистоту, да никто не знает в чем разница. Даже сам Мартин уже не помнит этого. Не занимайтесь холиваром, лучше займитесь делом.
👉 Не нужно пытаться предсказать изменения и везде где можно постелить солому в виде абстракций. Вы не сможете предсказать изменения. Однако это тонкая грань, архитектура не должна ломаться от изменений в требованиях, но и не нужно пытаться предсказать все на свете.
🚫 Не нужно делать интерфейсы на интеракторы. Вот это вообще масло масленное. Довольно часто можно такое заметить. Интеракторы вы точно не будете заменять в тестах, это чистая бизнес логика. Если вам нужно заменить интерактор в тесте, значит интерактор делает то, что не должен!
Подводя итог, чтобы дать конкретные наставления. Если вы джун, строго следуйте Clean, так вы точно не натворите полной фигни. Если вы уже мидл или сеньор задумывайтесь о том, нужно ли вам строго следовать канонам или они только мешают.
Соль в том, что чем дальше двигается индустрия тем больше Clean Architecture обретает ореол сверхценной идеи. Если ты не используешь Clean значит твоя архитектура хрень и скорее всего проект не взлетит. Успех проекта зависит же исключительно от архитектуры!
Мы стали забывать, что Clean это лишь рекомендация человека, который был простым разработчиком. Да с большим опытом, да очень медийным, но все равно разработчиком. Людям склонно ошибаться, индустрия разработки это не математика, нельзя придумать формулу, которая будет одинакова везде. Каждый проект отличается.
В целом можно понять почему разработчики пытаются полностью следовать Clean и делают кучу интерфейсов. Всегда есть страх того, что вот сейчас придет заказчик и скажет поменять базу данных. А мы как раз готовились к этому всю жизнь, у нас все на интерфейсах и мы быстро мигрируем. Только вот прикол в том, что заказчик не придет с таким, особенно если речь идет о Android. Он максимум придет с предложением перекрасить кнопки в другой цвет. Мы защищаемся от изменений которые никогда не произойдут.
К чему все эти разглагольствования. Clean это лишь рекомендация по архитектуре, а это значит не нужно все делать именно так как описано в книге Мартина, можно и нужно отходить в сторону если считаете, что так будет удобнее. Даже используя Clean нужно задавать вопросы и не следовать слепо общему тренду.
Я не буду говорить как правильно делать архитектуру, этого никто не знает. Если человек говорит, что знает как сделать архитектуру которая всем поможет он пиздит. Однако я могу сказать что не нужно делать:
👉 Не нужно покрывать все и вся интерфейсами, если вы не подменяете это в тестах. Эти интерфейсы только мешают. У нас сейчас крутые IDE, которые если необходимо могут сами выделить интерфейсы когда это понадобится.
👉 Не нужно делать проксирующие интеракторы без логики. Нафиг эти интеракторы которые существуют просто потому что. Чем меньше кода, тем меньше багов.
👉 Не нужно спорить про разницу между Interactor и UseCase. Давайте начистоту, да никто не знает в чем разница. Даже сам Мартин уже не помнит этого. Не занимайтесь холиваром, лучше займитесь делом.
👉 Не нужно пытаться предсказать изменения и везде где можно постелить солому в виде абстракций. Вы не сможете предсказать изменения. Однако это тонкая грань, архитектура не должна ломаться от изменений в требованиях, но и не нужно пытаться предсказать все на свете.
🚫 Не нужно делать интерфейсы на интеракторы. Вот это вообще масло масленное. Довольно часто можно такое заметить. Интеракторы вы точно не будете заменять в тестах, это чистая бизнес логика. Если вам нужно заменить интерактор в тесте, значит интерактор делает то, что не должен!
Подводя итог, чтобы дать конкретные наставления. Если вы джун, строго следуйте Clean, так вы точно не натворите полной фигни. Если вы уже мидл или сеньор задумывайтесь о том, нужно ли вам строго следовать канонам или они только мешают.
👍49❤5🔥1
В комментариях меня просили сделать посты про то, как стартует и в целом работает Activity со стороны JVM. Порой желание узнать как работает та или иная технология может завести довольно далеко. Поэтому я решил не ограничиваться только запуском Activity. Это будет достаточно длинная серия, из довольно хардкорных постов, посвящённых тому, как вообще работает Android.
В этой серии мы разберем:
👉 За что вообще отвечает ОС
👉 Что за sandbox
👉 Все ли сокеты используются только для сети
👉 Что такое Binder и нужно ли боятся AIDL
👉 Как мы получаем системные сервисы через Context
👉 Что такое Zygote
👉 Как система дергает методы ЖЦ Activity
👉 Путь от нажатия в лаунчере, до запуска приложения
Значит сразу, большая часть инфы этих постов вам скорее всего никогда не пригодится, разве что в будете в Гугл собесится. Однако зная как это работает под капотом сможете козырять на собесе и выбивать себе оффер по больше и выпендриваться на конфах и барах что я считаю святым вообще.
В этой серии мы разберем:
👉 За что вообще отвечает ОС
👉 Что за sandbox
👉 Все ли сокеты используются только для сети
👉 Что такое Binder и нужно ли боятся AIDL
👉 Как мы получаем системные сервисы через Context
👉 Что такое Zygote
👉 Как система дергает методы ЖЦ Activity
👉 Путь от нажатия в лаунчере, до запуска приложения
Значит сразу, большая часть инфы этих постов вам скорее всего никогда не пригодится, разве что в будете в Гугл собесится. Однако зная как это работает под капотом сможете козырять на собесе и выбивать себе оффер по больше и выпендриваться на конфах и барах что я считаю святым вообще.
🔥68👍15❤1
Вы задумывались об этом вопросе? Скорее всего некоторым повезло и препод с умным видом рассказывал вам про важность и устройство какой-нибудь ОС. Если кому не повезло как мне и вам вообще об этом не рассказывали, то присаживайтесь, давай-то по быстрому пройдемся по этому топику.
Итак, ответ в вкратце для чего нужна ОС. ОС – просто абстракция, чтобы когда вы писали программы вообще не думали о том, как ваша прога будет работать с железом. Представьте ситуацию, где вам потребовалось бы покрасить кнопку и вы держали в голове все порты монитора и думали о том, на какой порт подать напряжение чтобы кнопка покрасилась. Да еще и делать это нужно для каждого конкретного монитора. Жуть же правда, а именно так и было до создания ОС.
Теперь ответ не вкратце. Есть три пункта, выполнив которые мы можем сказать, что ОС состоялась:
👉 Планировщик процессов
👉 Менеджер памяти
👉 IPC (inter process communication)
Планировщик процессов это штука, которая вообще вводит понятие процесса. Планировщик решает сколько выдать процессу времени на процессоре, как распаралелить выполнение процессов и еще кучу всего связанного с безопасностью. Вы говорите ОС, хочу запустить эту прогу, а ОС уже сама создает новый процесс. Для этой программы все будет выглядеть так, будто ей доступно все железо на компе, хотя на само деле нет. Прям как вам говорят на входе в ВУЗ что у вас открыты все двери.
Менеджер памяти делает тоже самое что и планировщик процессов только с памятью. Этот менеджер следит за тем, чтобы какая-то одна прога не сожрала всю память, выделяет память под новый процесс, делает дифрагментацию, swap и вот это все. Когда мы выделяем память динамически, именно этот менеджер предоставляет нам память. Помимо этого он занимается виртуализацией памяти. Вашей программе кажется что ей доступна память оперативки в диапазоне ячеек с 0 до 1024, а на самом деле она работает в диапазоне 1024 по 2048. Это абстракция чтобы нам заранее не ломать голову, в каком участке памяти будет работать наша прога.
IPC это механизм межпроцессного взаимодействия. Не путайте с межпроцессорным, это две разные вещи! Так вот, этот механизм позволяет двум разным процессам общаться друг с другом. Фишка ОС в чем, что каждый процесс у нее это черный ящик, он вообще ничего не знает о других процессах и не может залезать в их память. Порой возникает необходимость поделится данными с другим процессом. Есть куча вариантов как передать данные другому процессу: через сеть, через файл, через буфер обмена или просто на прямую через оперативку. С файлом или сетью все довольно понятно, а через оперативку как, у нас же черный ящик? Вот именно безопасная передача данных между процессами напрямую через оперативку и есть головная боль IPC.
Естественно помимо эти трёх пунктов еще есть устройства ввода/вывода всякие драйвера, но эти три пункта прям обязательны и они самые важные.
Итак, ответ в вкратце для чего нужна ОС. ОС – просто абстракция, чтобы когда вы писали программы вообще не думали о том, как ваша прога будет работать с железом. Представьте ситуацию, где вам потребовалось бы покрасить кнопку и вы держали в голове все порты монитора и думали о том, на какой порт подать напряжение чтобы кнопка покрасилась. Да еще и делать это нужно для каждого конкретного монитора. Жуть же правда, а именно так и было до создания ОС.
Теперь ответ не вкратце. Есть три пункта, выполнив которые мы можем сказать, что ОС состоялась:
👉 Планировщик процессов
👉 Менеджер памяти
👉 IPC (inter process communication)
Планировщик процессов это штука, которая вообще вводит понятие процесса. Планировщик решает сколько выдать процессу времени на процессоре, как распаралелить выполнение процессов и еще кучу всего связанного с безопасностью. Вы говорите ОС, хочу запустить эту прогу, а ОС уже сама создает новый процесс. Для этой программы все будет выглядеть так, будто ей доступно все железо на компе, хотя на само деле нет. Прям как вам говорят на входе в ВУЗ что у вас открыты все двери.
Менеджер памяти делает тоже самое что и планировщик процессов только с памятью. Этот менеджер следит за тем, чтобы какая-то одна прога не сожрала всю память, выделяет память под новый процесс, делает дифрагментацию, swap и вот это все. Когда мы выделяем память динамически, именно этот менеджер предоставляет нам память. Помимо этого он занимается виртуализацией памяти. Вашей программе кажется что ей доступна память оперативки в диапазоне ячеек с 0 до 1024, а на самом деле она работает в диапазоне 1024 по 2048. Это абстракция чтобы нам заранее не ломать голову, в каком участке памяти будет работать наша прога.
IPC это механизм межпроцессного взаимодействия. Не путайте с межпроцессорным, это две разные вещи! Так вот, этот механизм позволяет двум разным процессам общаться друг с другом. Фишка ОС в чем, что каждый процесс у нее это черный ящик, он вообще ничего не знает о других процессах и не может залезать в их память. Порой возникает необходимость поделится данными с другим процессом. Есть куча вариантов как передать данные другому процессу: через сеть, через файл, через буфер обмена или просто на прямую через оперативку. С файлом или сетью все довольно понятно, а через оперативку как, у нас же черный ящик? Вот именно безопасная передача данных между процессами напрямую через оперативку и есть головная боль IPC.
Естественно помимо эти трёх пунктов еще есть устройства ввода/вывода всякие драйвера, но эти три пункта прям обязательны и они самые важные.
🔥41👍10
В предыдущем посте мы затронули тему планировщика процессов. Давайте пройдемся поглубже.
Значитса процесс – сущность операционной системы. Процесс это черный ящик, которому ОС накидывает ресурсы, это мы уже поняли. Каждый процесс обособлен, и ничего не знает про других. Почему сделано именно так? Тупо из-за безопасности. Вот у нас банковское приложение которое работает с данными карты пользователя. Никому бы не понравилось, если какой-то левый процесс, залез в нашу память и украл все данные.
Тут можно затронуть тему отличия потока и процесса. Поток в некотором смысле это легковесный процесс. Суть очень похожа, несколько потоков также могут выполняться на разных процессорах параллельно. Разница в том, что потоки работают в рамках одного процесса или адресного пространства. Это значит что потоки могут довольно просто использовать общую память. В коде можно насоздавать потоков, которые работают с одной переменной. Процессы так не могут.
Механизм, который отвечает за безопасность, другими словами запрещающий процессам лезть в чужую память и файлы, а также пытаться прочитать данные пользователя без разрешения называется Application Sandbox. Application Sandbox не относится к самому Android, он работает на уровне ядра Linux.
Если вы работали с Linux вам должно совсем просто. Если нет, то вот как это работает. Каждому приложению выдается уникальный user Id или UID. Другими словами каждому приложению выдается свой пользователь. Это не реальный пользователь, а просто абстракция для системы упрощающая работу с правами. Каждый user id входит в какую-то группу. У каждой группы также есть уникальный id (GID). При установке приложения ему выдается UID и GID. При установке GID назначается первичный, т.е группа в которую входит user приложения при создании. Group это что-то вроде клуба. Каждый участник клуба может входить в несколько клубов. Group нужны для раздачи прав. Назначаем какие права конкретной группе и все участники получают эти права.
Естественно у UID которые выдаются обычным приложениям нет админских прав, значит они не могут сделать все что им вздумается. Системным приложения, такие как например контакты назначается привилегированный пользователь, т.е UID который изначально входит в специальную Group. Эдакий клуб джентельменов в который левых не берут. Когда пользователь выдает какие-то права приложению, например локацию. UID этого приложения помещается в Group, которой локация доступна. Далее когда ваше приложение через системный сервис пытается получить локацию, этот сервис проверяет UID этого приложение на наличие нужных прав, пытаясь найти этот UID в нужной Group.
Именно по это причине нежелательно рутовать устройство которым вы пользуетесь. Потому как в этом случае, у вашего пользователя (реального, не пользователя приложения) появляется возможность установить приложения которым похер на все ограничения. Такие приложения получают сразу привилегированного пользователя и могут творить что захотят без вашего ведома.
Жесткая аналогия, но я ничего не могу с собой поделать она мне нравится. Рутовать свое устройство это как брить яйца бритвой цирюльника. Да бритье более качественное, но есть риск того, что у вас довольно на долго упадет настроение.
Значитса процесс – сущность операционной системы. Процесс это черный ящик, которому ОС накидывает ресурсы, это мы уже поняли. Каждый процесс обособлен, и ничего не знает про других. Почему сделано именно так? Тупо из-за безопасности. Вот у нас банковское приложение которое работает с данными карты пользователя. Никому бы не понравилось, если какой-то левый процесс, залез в нашу память и украл все данные.
Тут можно затронуть тему отличия потока и процесса. Поток в некотором смысле это легковесный процесс. Суть очень похожа, несколько потоков также могут выполняться на разных процессорах параллельно. Разница в том, что потоки работают в рамках одного процесса или адресного пространства. Это значит что потоки могут довольно просто использовать общую память. В коде можно насоздавать потоков, которые работают с одной переменной. Процессы так не могут.
Механизм, который отвечает за безопасность, другими словами запрещающий процессам лезть в чужую память и файлы, а также пытаться прочитать данные пользователя без разрешения называется Application Sandbox. Application Sandbox не относится к самому Android, он работает на уровне ядра Linux.
Если вы работали с Linux вам должно совсем просто. Если нет, то вот как это работает. Каждому приложению выдается уникальный user Id или UID. Другими словами каждому приложению выдается свой пользователь. Это не реальный пользователь, а просто абстракция для системы упрощающая работу с правами. Каждый user id входит в какую-то группу. У каждой группы также есть уникальный id (GID). При установке приложения ему выдается UID и GID. При установке GID назначается первичный, т.е группа в которую входит user приложения при создании. Group это что-то вроде клуба. Каждый участник клуба может входить в несколько клубов. Group нужны для раздачи прав. Назначаем какие права конкретной группе и все участники получают эти права.
Естественно у UID которые выдаются обычным приложениям нет админских прав, значит они не могут сделать все что им вздумается. Системным приложения, такие как например контакты назначается привилегированный пользователь, т.е UID который изначально входит в специальную Group. Эдакий клуб джентельменов в который левых не берут. Когда пользователь выдает какие-то права приложению, например локацию. UID этого приложения помещается в Group, которой локация доступна. Далее когда ваше приложение через системный сервис пытается получить локацию, этот сервис проверяет UID этого приложение на наличие нужных прав, пытаясь найти этот UID в нужной Group.
Именно по это причине нежелательно рутовать устройство которым вы пользуетесь. Потому как в этом случае, у вашего пользователя (реального, не пользователя приложения) появляется возможность установить приложения которым похер на все ограничения. Такие приложения получают сразу привилегированного пользователя и могут творить что захотят без вашего ведома.
Жесткая аналогия, но я ничего не могу с собой поделать она мне нравится. Рутовать свое устройство это как брить яйца бритвой цирюльника. Да бритье более качественное, но есть риск того, что у вас довольно на долго упадет настроение.
👍24😁7🔥5
Поговорим о демонах. В Unix системах есть программы которые работают в фоне со своей уникальной целью. По большей частью это служебные программы, написанные на низкоуровневом языке, гарантирующие работу какой-то подсистемы. Такие программы, написанные на низкоуровневом языке вроде Си называют демонами. Интересная история названия, почему именно демоны? Создатели Unix фанатели от греческой мифологии. В греческой мифологии Демон это персонаж выполняющий задачи, которыми не хотят заниматься боги.
Когда система стартует, она запускает кучу нативных демонов. Например, демон под названием netd, основная задача которого, следить за состоянием сети и сообщать об этом системному сервису. Определить демон ли программа можно по названию, у демонов есть следующая конвенция в названии {имя демона}d. Суть в том, что демоны предоставляют данные системным сервисам.
В предыдущих постах я упоминал системные сервисы. Что за системные сервисы? Вообще сервис как вы помните это основной компонент приложения. Это значит что приложение может быть лишь из одного этого компонента. Легко сделать приложение в котором будет лишь один сервис и ничего больше. Так вот в грубом представлении системные сервисы и есть такие приложения с одним сервисом.
В целом системные сервисы мало чем отличаются от обычных. Работают примерно также, за исключением того, что они запускаются от привилегированного пользователя и они не выгружаются системой. Помимо этого, системным сервисы часто общаются с системными демонами.
В итоге мы имеем нативного демона который крутится в своем фоновом процессе, а также системный сервис работающий в другом процессе. Как передать данные из нативного демона этому сервису?
Вариантов куча, но сразу скажу что в Android используется Unix Domain Socket (UDS). Практически этот тот же самый сокет который мы используем для передачи данных по сети, но оптимизированный для передачи данных на одной машине. Этот сокет не использует сетевую карту, что избавляет от проверок IP и всего что связано с сетью. UDS для передачи данных использует файл. Это один из самых базовых способов передачи данных между процессами. Как понятно из названия эти сокеты есть во всех Unix системах, в том числе и в Android.
Однако у UDS есть два очень жирных минуса. Во-первых, способ достаточно медленный, т.к мы все данные передаем через файл, что несет свои издержки связанные с дисками. Во-вторых, способ нифига не защищенный, и это опять-таки из-за того что данные передаются через обычный файл. Конечно, для нативных системных сервисов это относительно безопасно, т.к обычное приложение не сможет прочитать из этого файла благодаря системе прав, о которой говорили ранее.
Поэтому для обычных приложений нет никакого смысла использовать UDS. Каким образом тогда у нас передаются данные между приложениями, и как мы получаем данные с системных сервисов которые работают в других процессах? Через Binder о котором поговорим далее.
Когда система стартует, она запускает кучу нативных демонов. Например, демон под названием netd, основная задача которого, следить за состоянием сети и сообщать об этом системному сервису. Определить демон ли программа можно по названию, у демонов есть следующая конвенция в названии {имя демона}d. Суть в том, что демоны предоставляют данные системным сервисам.
В предыдущих постах я упоминал системные сервисы. Что за системные сервисы? Вообще сервис как вы помните это основной компонент приложения. Это значит что приложение может быть лишь из одного этого компонента. Легко сделать приложение в котором будет лишь один сервис и ничего больше. Так вот в грубом представлении системные сервисы и есть такие приложения с одним сервисом.
В целом системные сервисы мало чем отличаются от обычных. Работают примерно также, за исключением того, что они запускаются от привилегированного пользователя и они не выгружаются системой. Помимо этого, системным сервисы часто общаются с системными демонами.
В итоге мы имеем нативного демона который крутится в своем фоновом процессе, а также системный сервис работающий в другом процессе. Как передать данные из нативного демона этому сервису?
Вариантов куча, но сразу скажу что в Android используется Unix Domain Socket (UDS). Практически этот тот же самый сокет который мы используем для передачи данных по сети, но оптимизированный для передачи данных на одной машине. Этот сокет не использует сетевую карту, что избавляет от проверок IP и всего что связано с сетью. UDS для передачи данных использует файл. Это один из самых базовых способов передачи данных между процессами. Как понятно из названия эти сокеты есть во всех Unix системах, в том числе и в Android.
Однако у UDS есть два очень жирных минуса. Во-первых, способ достаточно медленный, т.к мы все данные передаем через файл, что несет свои издержки связанные с дисками. Во-вторых, способ нифига не защищенный, и это опять-таки из-за того что данные передаются через обычный файл. Конечно, для нативных системных сервисов это относительно безопасно, т.к обычное приложение не сможет прочитать из этого файла благодаря системе прав, о которой говорили ранее.
Поэтому для обычных приложений нет никакого смысла использовать UDS. Каким образом тогда у нас передаются данные между приложениями, и как мы получаем данные с системных сервисов которые работают в других процессах? Через Binder о котором поговорим далее.
👍18🔥8❤1
Итак, Binder. У нас задача передать данные между двумя процессами и желательно через оперативку, чтобы не было накладных расходов и все было безопасно. Для этого в Android был разработан Binder.
Binder это по сути фреймворк для построения межпроцессной коммуникации. Сам Binder работает на уровне ядра системы. Его фишка в том, что он позволяет описать интерфейсы на специальном языке AIDL. AIDL это что-то вроде супер урезанной Java. После чего при помощи кодогенерации сгенерировать классы Java. Дальше вы просто вызываете методы у Java классов как будто у вас все в одном процессе. По факту данные будут ходить между процессами.
Я не стал тут описывать как работает Binder и AIDL так уже делал это стародавних постах. В то время еще не было комментариев на канале и никто мне не сказал что вставлять смайлики в технический текст хреновая идея, поэтому уж извините. Короче, тут и тут самая важная инфа про AIDL и немного про Binder.
В этом посте я лишь сделаю пару важных дополнений. Безопасность в Binder обеспечивается тем, что в методе сервиса т.е в Stub можно получить UID и GID вызывающего приложения. Получив эти данные мы можем убедиться, что данное приложение действительно имеет разрешение на эти данные и все такое.
Вызывая методы на стороне клиента (Proxy) важно не забывать что они блокирующие! Сама коммуникация между процессами через Binder довольно быстрая, т.к все идет напрямую через память. Однако фиг знает что там будет делать сервис на той стороне. Что означает, что методы могут выполняться долго, поэтому желательно если вы вызываете методы через Binder убедится, что это не главный поток.
Что еще интересного у Binder, каждый раз на стороне Stub создается новый поток при вызове. Это значит что вы можете вызвать методы параллельно и на стороне сервиса они будут выполняться также параллельно.
Зачем про это знать. Скорее всего на практике вы с этим маловероятно столкнетесь, однако есть куча приложений и библиотек, где это используется. Самый яркий пример это Яндекс.Метрика. Когда вы затягиваете ее к себе, она стартует сервис в другом процессе, и вызывая методы метрики она передает данные через Binder.
Про Binder могут спросить на собесе, но обычно это уже уровень сеньора, поэтому просто знать как примерно это работает не повредит.
Binder это по сути фреймворк для построения межпроцессной коммуникации. Сам Binder работает на уровне ядра системы. Его фишка в том, что он позволяет описать интерфейсы на специальном языке AIDL. AIDL это что-то вроде супер урезанной Java. После чего при помощи кодогенерации сгенерировать классы Java. Дальше вы просто вызываете методы у Java классов как будто у вас все в одном процессе. По факту данные будут ходить между процессами.
Я не стал тут описывать как работает Binder и AIDL так уже делал это стародавних постах. В то время еще не было комментариев на канале и никто мне не сказал что вставлять смайлики в технический текст хреновая идея, поэтому уж извините. Короче, тут и тут самая важная инфа про AIDL и немного про Binder.
В этом посте я лишь сделаю пару важных дополнений. Безопасность в Binder обеспечивается тем, что в методе сервиса т.е в Stub можно получить UID и GID вызывающего приложения. Получив эти данные мы можем убедиться, что данное приложение действительно имеет разрешение на эти данные и все такое.
Вызывая методы на стороне клиента (Proxy) важно не забывать что они блокирующие! Сама коммуникация между процессами через Binder довольно быстрая, т.к все идет напрямую через память. Однако фиг знает что там будет делать сервис на той стороне. Что означает, что методы могут выполняться долго, поэтому желательно если вы вызываете методы через Binder убедится, что это не главный поток.
Что еще интересного у Binder, каждый раз на стороне Stub создается новый поток при вызове. Это значит что вы можете вызвать методы параллельно и на стороне сервиса они будут выполняться также параллельно.
Зачем про это знать. Скорее всего на практике вы с этим маловероятно столкнетесь, однако есть куча приложений и библиотек, где это используется. Самый яркий пример это Яндекс.Метрика. Когда вы затягиваете ее к себе, она стартует сервис в другом процессе, и вызывая методы метрики она передает данные через Binder.
Про Binder могут спросить на собесе, но обычно это уже уровень сеньора, поэтому просто знать как примерно это работает не повредит.
🔥32👍3❤1
Итак, задумывались ли вы, что происходит когда вы достаете из Context скажем ConnectivityManager? Как вы помните ConnectivityManager это класс, который позволяет отлеживать состояние сети. У него достаточно удобное API позволяющее узнать если ли сейчас сеть, повесить колбеки на это, короче все что связано с сетью. Получаем мы его вот так:
И вот если вдуматься в синтаксис, ну абсурд же происходит! Вроде достаем сервис, а получаем менеджер. Да и системный же сервис работает в другом процессе, что за дичь вообще? Сейчас все раскидаем. Наше приключение начинается с Context. Два основных факта про Context. Первый – Context это God object умеющий все на свете и вообще связующий класс через который мы общаемся с системой. Второй – сам по себе Context это абстрактный класс.
У Context куча наследников, в том числе основные компоненты: Application, Activity, Service и т.д. В Context 200 методов и реализовывать их в каждом наследнике, учитывая то, что они делают примерно одно и тоже, это сизифов труд. Поэтому разработчики сделали класс ContextImpl, который реализует основные методы. Вспоминаем шаблон декоратор. Activity наследует Context, после Activity переопределяет методы Context которые ей нужны, а все остальные методы просто вызывают методы класса ContextImpl. Аналогично и во всеми остальными наследниками Context.
Нас сейчас интересует конкретный метод Context
SystemServiceRegistry это класс, в котором огромный блок static наполняющий эту саму
При старте системы, она запускает SystemServer, который в свою очередь запускает системные сервисы, прям все. Сервисов много, но нас сейчас интересует ConnectivityServiceInitializer. ConnectivityServiceInitializer это системный сервис, основная задача которого просто создать объект класса ConnectivityService и сохранить его у себя. Хоть ConnectivityService и называется сервисом, это просто обычный класс, в котором скрыта логика по общению с netd. С тем самым демоном о котором говорили ранее.
Если ты дочитал до сюда и все еще легко ставь❤️. В системе есть AIDL интерфейс с названием IConnectivityManager. ConnectivityService это Stub интерфейса IConnectivityManager. Или по другому, к ConnectivityService кто-то может стучаться через Binder и вызывать его методы из другого процесса. И этот кто-то ConnectivityManager, который при создании получает Proxy интерфейса IConnectivityManager. Получив proxy, можно вызывать методы ConnectivityManager, который крутится в ConnectivityServiceInitializer и имеющий доступ к netd, а тем самым к информации о сети.
Жестко, я понимаю сам офигивал от такой картины. Главное тут это то, что во многих аспектах работает связка Manager–Service. Складываем все части мозаики воедино получаем что, есть системные сервисы и есть всякие менеджеры которые мы получаем из Context. Через эти менеджеры мы можем общаться с системными сервисами и общение это происходит через Binder. Разумеется не все что мы получаем через getSystemService обязательно общается с каким-то системным сервисом. Есть например LayoutInflator который ни с кем не общается и просто создается как обычный объект в SystemServiceRegistry.
В этом посте описана суть на примере ConnectivityManager. Есть некоторые сервисы, которые построены сложнее, где по 10 слоев абстракций между менеджером и системным сервисом. Однако основа у всех одна.
val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
И вот если вдуматься в синтаксис, ну абсурд же происходит! Вроде достаем сервис, а получаем менеджер. Да и системный же сервис работает в другом процессе, что за дичь вообще? Сейчас все раскидаем. Наше приключение начинается с Context. Два основных факта про Context. Первый – Context это God object умеющий все на свете и вообще связующий класс через который мы общаемся с системой. Второй – сам по себе Context это абстрактный класс.
У Context куча наследников, в том числе основные компоненты: Application, Activity, Service и т.д. В Context 200 методов и реализовывать их в каждом наследнике, учитывая то, что они делают примерно одно и тоже, это сизифов труд. Поэтому разработчики сделали класс ContextImpl, который реализует основные методы. Вспоминаем шаблон декоратор. Activity наследует Context, после Activity переопределяет методы Context которые ей нужны, а все остальные методы просто вызывают методы класса ContextImpl. Аналогично и во всеми остальными наследниками Context.
Нас сейчас интересует конкретный метод Context
getSystemService
. Каждый раз при вызове этого метода он идет в SystemServiceRegistry, который по сути: Map<String, (Context) -> Any>
. И getSystemService
делает примерно вот что:fun getSystemService(str:Stiring):
Any {
val fetcher = systemServiceRegistry[str]
return fetcher(this)
}
SystemServiceRegistry это класс, в котором огромный блок static наполняющий эту саму
Map
. Суть – есть имя сервиса и логика как его получить, обращаемся к SystemServiceRegistry по имени сервиса, и он нам его собирает. Точнее говоря, собирает не сам сервис, а некоторого клиента или менеджера который умеет работать с каким-то системным сервисом. Именно так мы и получаем ConnectivityManager, однако это только начало.При старте системы, она запускает SystemServer, который в свою очередь запускает системные сервисы, прям все. Сервисов много, но нас сейчас интересует ConnectivityServiceInitializer. ConnectivityServiceInitializer это системный сервис, основная задача которого просто создать объект класса ConnectivityService и сохранить его у себя. Хоть ConnectivityService и называется сервисом, это просто обычный класс, в котором скрыта логика по общению с netd. С тем самым демоном о котором говорили ранее.
Если ты дочитал до сюда и все еще легко ставь❤️. В системе есть AIDL интерфейс с названием IConnectivityManager. ConnectivityService это Stub интерфейса IConnectivityManager. Или по другому, к ConnectivityService кто-то может стучаться через Binder и вызывать его методы из другого процесса. И этот кто-то ConnectivityManager, который при создании получает Proxy интерфейса IConnectivityManager. Получив proxy, можно вызывать методы ConnectivityManager, который крутится в ConnectivityServiceInitializer и имеющий доступ к netd, а тем самым к информации о сети.
Жестко, я понимаю сам офигивал от такой картины. Главное тут это то, что во многих аспектах работает связка Manager–Service. Складываем все части мозаики воедино получаем что, есть системные сервисы и есть всякие менеджеры которые мы получаем из Context. Через эти менеджеры мы можем общаться с системными сервисами и общение это происходит через Binder. Разумеется не все что мы получаем через getSystemService обязательно общается с каким-то системным сервисом. Есть например LayoutInflator который ни с кем не общается и просто создается как обычный объект в SystemServiceRegistry.
В этом посте описана суть на примере ConnectivityManager. Есть некоторые сервисы, которые построены сложнее, где по 10 слоев абстракций между менеджером и системным сервисом. Однако основа у всех одна.
❤59👍9🤯4🔥2