Dev Easy Notes
3.17K subscribers
123 photos
1 video
147 links
Работаю в IT уже 8 лет. Рассказываю про разработку простым языком. Полезность скрыта под тупыми шутками и слоем мата. Лучший underground канал про разработку, который вы сможете найти.

По сотрудничеству писать @haroncode
Download Telegram
Что такое Zygote
🔥10
Медленно мы подходим к интересным и более хардкорным темам. ZygotеInit каждый видел в стэке вызов функций. Независимо от того, что у вас за приложение стартовать все будет отсюда.

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

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

В init.rc описано как стартовать все демоны, сервисы и остальные элементы системы. Этот же файл запускает AndroidRuntime. В С++ коде он кстати так и называется AndroidRuntime. Этот самый AndroidRuntime в свою очередь запускает JVM и в частности main функцию нашего ZygotеInit.

Что происходит в ZygotInit.main? Для начала подгружаются все классы для работы этой самой JVM и все все Android зависимости необходимые для работы. Если интереснее подробнее разобраться в том, как работают ClassLoader у меня и на это есть серия постов. После загрузки всех необходимых классов, запускается некий ZygoteServer. Этот ZygoteServer запускает бесконечный цикл и чего-то ждет.

Вот тут начинается самое интересное. Эта та магия системного программирования которую практически никогда не увидишь в обычных проектах. Для объяснения нужно написать немного кода на низкоуровневом языке. C++ я жутко не люблю и мне кажется от должен уже отправится на полку истории, поэтому мы черканем пару строк на Rust. Не волнуйтесь сложно не будет, я профессионал. Пример вот такой:

unsafe { // unsafe просто позволяет запускать небезопасные штуки
let fork_result = libc::fork(); //вот тут происходит магия
match fork_result {
-1 => println!("Ошибка при попытке сделать fork"),
0 => println!("Бонжур мы в child процессе, pid {}”, libc::getpid()),
_ => {
println!("Тут мы в изначальном процессе, pid {}", libc::getpid());
}
}
}


Сейчас нужно будет на максимум включить абстрактное мышление. Значит есть в Unix такой системный вызов, который называется fork. Все что он делает, копирует процесс и запускает его как отдельный. Еще раз вдумайтесь! Этот системный вызов берет вашу прогу, копирует все состояния переменных, все потоки, все стэки, весь хип все это дело переносит в отдельное место в оперативной памяти и запускает как отдельный процесс.

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

ZygotInit запускает ZygoteServer, который просто ждет команду от системы на запуск нового процесса. ZygoteServer запускает бесконечный цикл и вот тут интересно. Когда система дает команду на создание нового процесса, этот новый процесс выходит из этого бесконечного цикла и идет дальше запуская ActivityThread и т.д. Изначальный же процесс так и остается в бесконечном цикле в ожидании новых команд от ОС.

Все скорее всего слышали, что когда стартует приложение, у нас создается копия JVM. Мало кто объясняет как именно создается и зачем копия? Как это происходит мы уже разобрали, остался вопрос зачем? Все просто, Android и так не всегда быстрый, а если бы при каждом старте еще и нужно было каждый раз подгружать все нужные классы было бы вообще грустно. Поэтому разработчики придумали тупо копировать инстанс JVM с уже загруженными классами.

Ну а что там происходит с процессом, который вышел из этого бесконечного цикла мы разберем в следующих постах.
👍24🔥62
Минутка мини рекламы.
Мои кореша, тестируют сервис mentee, который помогает повышать скиллы айтишников. Сейчас они проводят эксперимент с форматом тренировочных собеседований, и для этого очень нужен ваш фидбек.

Если хочешь пройти тренировочное собеседование с разработчиками из топовых IT-компаний России (Яндекс, Тинькофф, VK, Авито), а также получить качественную обратную связь – оставь заявку, и с тобой обязательно свяжутся :).

Собеседования проходят в онлайн-режиме, а для лучших кандидатов обещают сделать рефералку в одну из компаний!
Если интересно вот форма 👉 https://forms.gle/Pmj4tLkSiW6zyza57
👍18
Как система дергает методы ЖЦ Activity

👇
🔥15👍3
Итак, я наконец-то добрался до того, чтобы уже сделать предпоследний пост этой серии. Сейчас вы узнаете каким образом дергаются методы ЖЦ все ваших Activity.

Существует куча различных кейсов, как меняются ЖЦ Activity, вот например некоторые из них:
👉 Старт первой Activity
👉 Запуск новой Activity поверх старой
👉 Переворот Activity 
👉 Уничтожение предыдущих Activity из-за нехватки памяти 
👉 Запуск Activity другого приложение поверх наших Activity 

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

Когда я первый раз увидел код приложения под Android, я был в недоумении, а где же метод main? Все казалось какой-то магией, мы создаем Activity у которых когда-то будут вызваны методы. На самом деле метод main есть и его довольно легко найти. Он находится в классе ActivityThread. ActivityThread это основной поток нашего приложения, тот самый поток в котором работает главный Looper. В ActivityThread сокрыто куча всего, всяких настроек и т.д, но нас сейчас интересует лишь старт Activity.
 
В предыдущем посте мы обсудили, как запускается процесс нашего приложения. Вот что происходит далее, в начале запускается статичный метод main. В этом методе инициализируется главный Looper и после этого дергается метод bindApplication через IPC у ActivityManagerService. Этим мы сообщаем сервису, что вот мы стартанули приложения и нам сейчас нужна помощь с Activity. Далее ActivityManagerService производит свою магию и дергает уже наш метод ActivityThread, т.к при вызове метода bindApplication мы передали объект нашего ActivityTread

После того как ActivityManagerService дернул наш метод, ActivityThread отправляет сообщение BIND_APPLICATION в очередь Looper и запускает этот самый Looper. На этом моменте выполнение метода main прекращается, т.к запустился бесконечный цикл Looper. Далее Looper главного потока выполняет сообщения BIND_APPLICATION которое просто вызывает еще один метод для настройки и дальше ничего не происходит потому как в Looper главного потока больше нет сообщений.

Пока главный поток настраивался, ActivityManagerService подготавливал первую Activity к запуску. Как именно он это делает: на основе информации полученной из ActivityThread ActivityManagerService знает о том, какую Activity нужно запустить. Однако он не создает саму Activity, у него лишь есть информация о том, что это за Activity и как ее нужно запустить. Затем он создает специальный объект, этот объект транзакция, в котором есть вся информация перечисленная выше. Этот объект отправляется через IPC в главный Looper нашего приложения. 

Сообщения транзакции представлены в виде специального сообщения. Как вы знаете можно при помощи специальных колбэков в Handler перехватывать нужные нам сообщения. В нашем приложении есть такой объект как TransactionExecutor, он и перехватывает эти сообщения и затем выполняет.

В переданной транзакции есть информация о том, какой метод ЖЦ какой Activity нужно вызвать. Информация эта представлена специальным объектом, который описывает статус ЖЦ Activity. Если говорить конкретно, то там есть специальный класс наследники которого так и называются OnCreateActivityItem, OnStartActivityItem и т.д. Получив транзакцию TransactionExecutor дергает нужные методы.

По сути это все, в системе есть куча различных сервисов, которые знают о наших Activity и решают у какой Activity какой ЖЦ вызывать. И вызывают они методы ЖЦ через транзакции послание через Binder. Часть транзакции приходят из вне, часть же транзакции создает наше же приложение, например когда одна Activity перекрывает другую. Транзакцию с методом onStop создает сам ActivityThread.
🔥33👍6👏2
Путь от нажатия в лаунчере, до запуска приложения

👇
🔥16
Осталась финальная тема, собрать воедино путь от момента нажатия на иконку приложения до появления первой Activity. 

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

Перехватив нажатие на иконку приложения, мы говорим системе, что нужно запустить новую Activity. Раньше это делалось так: берем имя пакета приложения, из Context получаем нужный нам Intent для запуска первой Activity и запускаем будто это наша Activity. Сейчас так не делают, для запуска первой Activity из лаунчера нужно использовать специальный сервис LauncherApps. Сделано так потому что начиная с 8 Android изменились правила по работе с пользователями. В него мы передаем пакет приложения и координаты иконки на экране, которые нужны для показа красивой анимации старта приложения.

Как вы уже знаете у нас всегда работает связка Manager -> SystemService. Поэтому все вызовы методов LauncherApps отправляются в LauncherAppsService, который в основном занимаете проверкой разрешений, ищет от какого пользователя открылось приложение вот это все и дальше это все отправляется в волшебный класс ActivityStarter. 

ActivityStarter это конечная точка запуская любой Activity, каждый раз когда вы запускаете Activity, причем неважно это ваша Activity или другого приложения все вызовы приходят в ActivityStarter. В ActivityStarter есть всего один метод execute, в котором и располагается вся логика. Работа с ActivityStarter чем-то напоминает работу с запросами при помощи OkHttp. Аналогично, большой билдер с настройками запроса и потом один метод который запрос исполняет.

В ActivityStarter располагается логика по обработке флагов и launchMode. Вот эти singleTop, singeTask и т.д все это рассчитывается в ActivityStarter и уже он решает какие Activity будут убраны, а какие добавлены. Помимо этого разруливает в какой Task будет добавлена Activity. ActivityStarter занимается лишь расчетом как должна быть запущена Activity, сам запуск Activity уже проводят другие сервисы об этом есть инфа в прошлом посте. 

Дальше происходит проверка если ли активный процесс данного приложения. Ведь если он есть, это означает что приложение просто было свернуто и не нужно запускать новый процесс. Если же процесса нет, то через специальные волшебные статические функции из класса ZygoteProcess через метод startViaZygote мы подаем сигнал тому самому бесконечному циклу в ZygoteInit для запуска нового процесса и одновременно с этим показываем сплэш скрин.

Сплэш мы показываем начиная с Android 12, а в версиях до этого, показываем window background темы первой Activity. За показ старта приложения всегда отвечает StartingSurfaceController  он же и запоминает как выглядит приложение при сворачивании, чтобы потом показать его в Recents.  В момент когда вы сворачивали приложение система запомнила как выглядел последний экран, поэтому при разворачивании приложения система рисует анимацию основываясь на этой информации.

На этом все, я наконец-то закончил эту серию. Я надеюсь у вас сложилась более менее общая картина. Подвести итог можно такой, что в коде Android много интересных концепций которые можно использовать в работе. Знать все названия классов и т.д нет никакого смысла, потому как все постоянно меняется. Скорее всего со следующим релизом, то что я описал тут будет уже не актуально.
👍32🔥101
Привет всем. Итак я буквально недавно закончил саму тяжелую для меня серию. Тяжелая она потому как сама по себе это тема Rocket Science и от этой темы не такой большой отклик и эмоциональный и по просмотрам. Помимо этого я немного выпал почти на 2 недели, у меня был отпуск и максимальная прокрастинация из-за темы про Android. Сейчас посты будут выходить чуть чаще, и я думаю возможно некоторые будут выходить без картинок, потому как именно они и занимают львиную долю времени.

В ближайшее время я не буду делать разборов как работает та или иная технология сложная технология под капотом и делать больше делать постов на приземленные темы. Как мне кажется от этого и вам будет больше пользы. На работе я сменил команду на Platform-Tech которая занимается сборками, автоматизациями и CI, скорее всего рано или поздно сделаю посты на эти темы.
👍499🔥4
Чтобы стать хорошим инженером, желательно под капотом знать как устроена технология с которой ты работаешь каждый день. Это отличный совет, но часто забывают указать границу, когда копать технологию вглубь не нужно и даже порой вредно. Универсальный совет по проведению этой границы. Если есть уверенность, что знание технологии вглубь тебя ускорит, каеф продолжай. Если же не уверен, сходи и займись чем-то другим.

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

Эта же история и с тем как под капотом работает Android или как система дергает ЖЦ Activity. Как развлечение узнать как там это устроено прикольно, но вот когда это становится требованием это странно. Проблема этих знаний в том, что вы можете знать как работает Android, но ничего не сможете с этим сделать, если вы не разработчик Google.  

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

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

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

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

2️⃣ причина. Вас про эту технологию спросили на собесе, но вы смогли ответить только поверхностно. Поэтому ходите на собесы хотя бы раз в год, даже если вы уже работаете для актуализации знаний. Однако тут со здравым скептицизмом, если вас на собеседовании на позицию Android разработчика спрашивают про версии gcc, то это не вы дурак это в компании душные идиоты и нахер такую компанию. 

3️⃣ причина. Вам тупо нефиг делать и вы хотите себя интеллектуально развлечь, так сказать утешить инженерный интерес. Только в этом случае важно понимать, что это уже раздел хобби и на прямую не влияет на вашу работу. 

Подводя итог, как не потратить время на копание вглубь технологии, знания о которой никуда не уперлись. Включаете меркантильность на максимум, если за глубину платят мы изучаем, если нет то забиваем.
39👍13👎1
😁14👎1🤔1
Итак, в прошлом посте был вопрос “А как понять, что интервьюер не знает, о чем спрашивает?”. Вопрос довольно прикольный, поэтому я решил по-быстрому накидать пост про собесы. Как я уже говорил, собеседование это нефига не экзамен, это лишь проверка сработаетесь ли вы вместе или нет. Отсюда выходит две вещи. 

Первое – если вы чего-то не знаете то пофиг, нестрашно. Даже в собесе на сеньорскую позицию никто не будет ожидать, что вы знаете прям все. Главное не пытайтесь придумывать и тянуть время если не знаете и еще очень круто когда вы отвечаете в стиле: “Я эту тему не копал, но могу предположить, что это работает так и так”. Цель собеса не понять знаете ли вы что-то, а умеете ли думать. И еще не гуглите ответ во время собеса, это охренеть как палевно, у вас скорее всего не получится это сделать незаметно.

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

Собеседующие могут ошибаться и нести чушь. Вы его можете поставить на место, а он потом обидится и напишет в фидбеке в графе soft skills что вы мудак. Поэтому сначала пройдите собес, а уже потом можете в личку прийти к нему и сказать что он не прав 😁

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

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

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

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

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

После собеса не забывайте спрашивать собеседующих. Я вообще не понимаю людей, которые приходят на собес и после вообще ничего не спрашивают. Хотя казалось бы, можно же кучу вопросов задать, по технологиям, по компании по процессам. Это те вещи которые вам как разработчику должны быть капец как важны, вы же собираетесь в этом месте провести довольно продолжительное время.
24🔥6👍5
Ну и напоследок. После собеса всегда просите фидбек, даже если молчат не стесняйтесь закидывать сообщениями HR по поводу фидбека. В идеале всегда должен быть результат с собеса, это либо фидбек, либо офер. Если HR долго не приходит с фидбеком, то тут два варианта. Либо у HR жесткая запарка и они просто медленные, либо компания мутная контора. Крутые компании всегда дают фидбек. Если компания на вас забила и не дала вам фидбэк, значит это хорошо что вы туда не попали. Потому как если с HR процессом у них все грустно, значит и в остальном тоже будет плохо.

 P.S компании maang вроде как забивают на фидбэк, но это исключение 😄
17👏2
ViewModel

👇
👍17
{1/2} Сразу скажу, что не вижу смысла просто копировать документацию к ViewModel. Потому как с ними работать и как их использовать уже есть тысячи статьей и я просто внизу приведу список того, что считаю более менее норм. Тут я хочу просто разобрать основную проблему которую они решают, рассмотреть как они работают под капотом и в чем разница м/у тем же Moxy. 

Итак, погнали. Проблема, которую решает ViewModel – сохранение состояние экрана без сохранения в Bundle. Например, из-за размера или по причине того, что в Parcelable не сохранишь вещи вроде Disposable. Разумеется разработчики задумались, а каким образом можно сохранить данные между переворотами. 

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

И пара пацанов из no name галеры запилили Moxy. Библиотека Moxy взлетела на расцвете архитектуры MVP. Разумеется были и другие похожие решения, но это был самый хайповый инструмент. Сейчас это уже считается пережиток прошлого и некоторые компании даже стесняются признаваться, что у них он до сих пор используется. Недостатка у Moxy всего два это кодогенерация и сильная завязка на архитектуру MVP. 

Значит как работала Moxy. Вы унаследовали свой Presenter и Activity базовых классов, затем ставили специальную аннотацию на полем презента в Activity и происходила магия кодогенерации, благодаря которой этот презентер переживал смерть активити между переворотами. Вся магия сводилась к тому, что презентеры тупо сохранялись в статическую HashMap. 

Спустя какое-то время, Гугл после увидел, что у разработчиков есть нужна в адекватном инструменте для сохранения состояния и запилила ViewModel. Разработчикам из гугла потребовалось 8 лет чтобы это увидеть. С такой скоростью адекватное API для клавиатуры мы увидим примерно к 28 году. 

В 2017 году пришел Гугл и сказал что MVP параша, и нормальные ребята используют MVVM. Помимо этого, они любезно как раз сделали все для использования этого подхода. MVVM это не разработка гугла, это придумали Microsoft еще тогда когда у всех были Nokia, но об этом как-нибудь потом.
👏25👍4😁3
{2/2} По началу все с презрением относились к этой технологии, потому как тогда было модно хейтить Гугл за их неудобные и багующие либы. В целом за эти 5 лет ничего не поменялось. Однако за это время все признали ViewModel и по сути сейчас это основной способ хранить данные м/у убийствами View и весьма удобный. 

ViewModel удобен тем, что можно делать хоть по 100 мелких на Activity или фрагмент. Плюс к этому есть метод для очистки, который точно дернется системой когда ViewModel умрет. Однако есть пара минус в том, что для проверки работы ViewModel нужно прям крутить экран, потому как Moxy можно было проверять при помощи настройки “Don’t keep Activities”. А ViewModel ломаются с этой настройкой, потому как считают что в этом случае ты сам ушел с экрана.

У ViewModel явное преимущество над другими решениями в том, что код по работе с ViewModel встроен в Activity. Встроен не в обычную Activity, а в ту которая идет с Jetpack. На самом деле встроен не сильно, там лишь добавили пару методов для удобства, при желании можно сделать свою реализацию ViewModel.

ViewModel работает несколько хитрее нежели Moxy. Они сохраняются не в статике, а в ActivityRecord. В предыдущих постах я писал про ActivityThread, которая умеет кучу всего. Так вот, в этом самом ActivityThread хранится список ActivityRecord, это некоторые данные по Activity, которые будут жить пока мы с Activity не уйдем, т.е даже между поворотами. 

Как я уже сказал выше, мы можем сделать нашу собственную реализацию ViewModel. Для этого нужно использовать метод переопределить метод onRetainNonConfigurationInstance. Если вы унаследовали свою Activity от любой Activity которая идет в Jetpack, у вас не получить переопределить этот метод, потому как в Activity Jetpack этот метод помечен как final.

Однако поиграться все равно можно, через метод onRetainCustomNonConfigurationInstance (не путайте с предыдущим). В этом методе нужно вернуть объект, какой вашей душе угодно. Допустим будем возвращать HashMap<String, CustomViewModel>. После всех переворотов, мы можем получить наши данные через метод getLastCustomNonConfigurationInstance. Вот так, можно легко и просто сохранить данные без Bundle.

Ровно такой механизм и используют ViewModel. ViewModelStore (который используется в ViewModelProvider) это буквально и есть HashMap<String, ViewModel>. Разумеется если ваш процесс умрет, умрут и данные сохраненные через onRetainNonConfigurationInstance, и соответственно все ViewModel, но вы это и так знали.

Метод onRetainCustomNonConfigurationInstance помечен как deprecated, и вместо него гугл рекомендует использовать ViewModel. По мне так это правильно, не хотелось бы приходя на проект видеть велосипед с этим методом. 

Значит теперь пара советов по использованию ViewModel. Не страдайте фигней и сразу подключайте ktx и используйте делегаты. Дальше за вас все сделает система, главное правильно интегрируйтесь с DI. Большинство DI сами умеют работать с ViewModel. Что почитать:

👉 Официальная дока по ViewModel это must have и в целом там очень доступно все написано
👉 Тоже с официальной доки, туториал который должен помочь

Ну и совет, не тащите LiveData, предпочитайте Kotlin Flow или Rx.
Вот теперь вопрос к публике: вспомните дурацкое поведение фрагментов, и задумайтесь, а очистится ли ViewModel в таком фрагменте?
👏28👍6
LiveData

👇
👍9
В прошлом посте я набросил на LiveData. Сейчас хочу описать почему я считаю LiveData не самым крутым решением. Давайте начнем вообще с проблемы, из-за чего появилась LiveData. Хочу тут заострить внимание, что само по себе решение довольно неплохое, оно конкретно мне не нравится и в посте постараюсь объяснить почему.

Как вы помните MVVM в целом базируется на идее о том, что у нас есть View и есть ViewModel. View подписывается на изменения с ViewModel и когда получает события изменяет элементы на экране и т.д. После того как Google стала пропагандировать MVVM встал вопрос о том, каким образом реализовать обсервабельность.

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

В момент появления ViewModel моду набирало реактивное программирования. Подход когда у тебя есть потоки данных и ты декларативно это все описываешь всем пришелся по вкусу. В то время как раз начала расцветать RxJava. Однако у RxJava есть два основных минуса, почему ее не стали использовать для передачи событий из ViewModel на View.

Первый минус это ее сложность. RxJava действительно довольно сложная либа в плане понимания, особенно для начинающих. Сложная в основном потому, что нужно думать потоками данных, а в начале все думают императивно. Второй минус по дефолту RxJava не умеет отписываться когда умирает View и это нужно делать вручную, что довольно неудобно. Поэтому Гугл решить эту проблему сделав свою урезанную версию RxJava. LiveData решает все проблемы описанные выше и создает парочку своих.

Первая проблема, которую создает LiveData архитектурная. Эта проблема выходит из одного золотого правила: “Если можно наговнокодить с либой, это будет сделано рано или поздно”. LiveData идеальна для передачи данных от ViewModel к View. Все на этом идеальность кончается, однако в проектах где я был и где использовалась LiveData, она волшебным образом проникала на Domain слой. Причем Гугл в этом даже помогает, они даже в room добавили возможность возвращать LiveData чтобы следить за изменениями.

Чем плохо что LiveData может оказаться в Domain слое? В Domain слое речь идет о бизнес логике, а значит и о применении кучи различных операторов. У LiveData максимально всратое API для операторов, она тут дико проигрывает RxJava и Flow. Помимо этого LiveData очень сильно завязана на View, нельзя подписаться не на UI потоке и без View. Конечно можно подписаться без View, но только нельзя будет отписаться, прям как от email рассылок.

Ну и вторая проблема это тесты. Опять таки из-за того, что LiveData очень завязана на UI нужно во первых реализовать свою мини версию Handler чтобы вообще можно было тесты запустить. Во-вторых нельзя просто так сделать функцию которая например ждет пару секунд, и если событий нет падает. В Rx и Flow такое делается довольно просто и интуитивно, в LiveData нужно выдумывать что-то вроде такого.

Ну и естественно как же вспомнить проблему SingleLiveEvent, для которой Гугл уже 5 лет уперто не желает выпускать мелкий классик, и каждый раз нужно пилить свой.

Подводя итог. LiveData прикольный инструмент и довольно мощный. Его можно тащить в свои тестовые, его можно тащить в мелкие проекты, особенно когда вы только начали изучать разработку. Однако в уже большие проекты или когда вы уже себя уверено чувствуете лучше затаскивать RxJava, а лучше FLow потому как сейчас для View есть много расширений по работе с корутинами.
👍29👎1
🎋 Дополнение к LiveData

Сейчас я работаю над интересным постом про DRM, однако пост занял больше времени чем я рассчитывал. Поэтому чтобы вы не скучали, я вам докину еще один недостаток про LiveData, про который недавно узнал.

У LiveData есть два оставновных метода которые позволяют отправить в нее данные: setValue и postValue. Разница в том, что setValue пытается просто заметить данные в поле, и поэтому если вы вызовете этот метод не на UI потоке он упадет. Второй метод postValue уже можно использовать из любого потока, под капотом он просто вызываем метод setValue, но через Handler.

В прошлом после я уже упомянул сложности в Unit тестировании. Эти сложности как раз выходять из того, что LiveData создает Handler, который нельзя создать вне эмулятора. Эта сложность решаемая, т.к разработчики позволили заменить этот класс который под капотом использует Handler. Однако это не все сложности которые приносит метод postValue.

Вторая проблема связана с обновлением данных LiveDate. Если у LiveData нет подписчиков, то метод postValue не будет обновлять данные до тех пор, пока они не появятся. Другими словами, вы используете LivaData, чтобы допустим, хранить состояние, и вы хотите получать эти данные и на UI и в самой ViewModel.

Пока UI активен все хорошо, однако как только UI умирает, вы не можете получить свежие данные из LiveData. При обращении к полю вы будете получать старые данные, а это может привести к ох каким неплохим багам.

Почему сделано именно так? Ответ на этот вопрос я не знаю, возможно разработчики решили так избежать лишней нагрузки на UI поток. Мне показалось, что вам будет интересно знать про такое поведение.
👍43👏1
DRM

{1/2} Недавно в разговоре со знакомым разработчиком из Кинопоиска он упомянул DRM. Я раньше никогда не слышал про эту штуку, но как оказалось знать про нее must have если вы хотите работать с медиа: фильмы, музыка, книги и т.д.

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

И вот тут возникает вопрос, ведь можно накачать музыки и потом просто забрать эти файлы. Как минимум не составит большого труда зайти на флешку через File Manager и эти файлы забрать. Естественно разработчики давным давно задумались о том, а как вообще защитить подобного рода данные. Ответ на этот вопрос DRM.

DRM - digital rights management набор технических средств которые ограничивают копирование. Разрешить чтение файла, но при этом запретить копирование задача не тривиальная и в некотором смысле почти недостижимая. С этой системой все так или иначе сталкивались когда например, пытались сделать копию диска с игрой. В детстве я помню, что на некоторых дисках была защита из-за которой просто так нельзя было скопировать игру. В общем и целом, все вот эти защиты которые часто используют игры и есть DRM.

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

DRM это общее название, конкретная система гугла называется Widevine. Однако сам Android поддерживает и другие схемы DRM, но их рассматривать не будем, ибо это скучно. Ну так вот, как это работает. Значит этот Widevine помогает зашифровать потоковый контент так, чтобы его нельзя было просто так взять и скопировать. Работает он с двумя основными протоколами, Dash и HLS. Подробнее о них расскажу как-нибудь потом, сейчас просто запомните эти протоколы помогают сделать потоковую передачу данных.

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

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

Вот вы сидите в приложении для музыки, нажимаете на иконку песни. В этот момент приложение отправляет запрос к вашему серверу на скачивание данных песни и получает первый файлик который называется InitData, в нем вшита информация о том, какая это схема DRM и еще всякая разная метаинформация. Как я уже упоминал выше, в большинстве случаев это будет Widevine.

Далее мы c этой InitData идем в системный сервис MediaDrm и получаем запрос. Получаем мы его в виде байтового массива. Берем этот запрос и по Http клиенту идем на сервер гугла, который хранит нашу лицензию. Сервер проверят запрос и отдает нам лицензию.

После мы берем эту лицензию и отправляем в MediaDrm. Далее инициализируем специальный класс MediaCodec который и отвечает на расшифровку контента. Теперь когда все настроено мы можем наконец проигрывать песню. Скачиваем небольшую часть песни которая зашифрована, передаем данные в MediaCodec он расшифровывает мы играем. И так на каждый чанк (часть файла музыки или видео). Чтобы играть офлайн, нужно при помощи специальных методов указать MediaDrm что лицензию необходимо сохранить, чтобы использовать позже.

Продолжение ниже 👇
👍26🔥31🤔1
{2/2} Примерно так это все и работает. Куча этапов и куча проверок на каждом этапе, поэтому просто так скачать видос и сохранить не получится. Помимо этого, так как MediaDrm работает на уровне системы (см. картинку) это позволяет сделать вещи вроде запрета скриншотов и записи экрана. Разумеется эти схемы обходятся, они не идеальны, но точно защитят контент от большей части пользователей.

На каждый трек и каждый видос защищённый при помощи Widevine ваш клиент делает запрос на сервера Гугл за лицензией. Что это означает? Что гугл может в любой момент не отдавать лицензию если ему ваш IP показался странным. Может на уровне операционной системы сделать так, что определенный контент вы не сможете смотреть легально. Работникам Кинопоиска или Яндекс Музыки система DRM приносит много сюрпризов и не самых положительных отзывов 😄

Если вы прочитали все это и ужаснулись, что сколько всего нужно знать, чтобы просто показать видос на устройстве не парьтесь. За вас это все уже сделано в Exoplayer, вам только его нужно передать правильные ссылки.
👍24🔥2