Общая ViewModel
Не самая очевидная вещь: один экземпляр ViewModel может быть доступен из нескольких фрагментов или активитей
Например, вам нужна аватарка пользователя в двух фрагментах
Вы можете:
— для каждого фрагмента создать свою ViewModel и прописать в них обоих одинаковую логику получения аватарки. При этом в каждой из этих вьюМоделей будет своя MutableLiveData<Bitmap>, хранящая картинку
— создать одну вьюМодел и получить к ней доступ из обоих фрагментов
Фокус в том, что вьюМодел хранится у кого-то. И этот кто-то - ViewModelStoreOwner. Владельцем (Owner) могут быть:
- фрагменты
- активити
- кастомные классы, реализующие интерфейс ViewModelStoreOwner (редкий кейс)
Хранитель вьюМодели определяется во время получения ее экземпляра через ViewModelStoreProvider(ViewModelStoreOwner owner)
Так вот, если в качестве владельца передать не сам фрагмент, а его родительскую активити, то хранить экземпляр вьюМодел будет активити. А доступ к этой активити есть и у второго фрагмента из нашего примера. Значит, когда он захочет получить вьюМодель через ViewModelStoreProvider(getActvivityy()), то он получит тот же экземпляр вьюМодели, с которым работал первый фрагмент (то есть там уже будет лежать аватарка)
Знать об этом способе стоит, но хороший ли это подход с точки зрения архитектуры и памяти?
Не самая очевидная вещь: один экземпляр ViewModel может быть доступен из нескольких фрагментов или активитей
Например, вам нужна аватарка пользователя в двух фрагментах
Вы можете:
— для каждого фрагмента создать свою ViewModel и прописать в них обоих одинаковую логику получения аватарки. При этом в каждой из этих вьюМоделей будет своя MutableLiveData<Bitmap>, хранящая картинку
— создать одну вьюМодел и получить к ней доступ из обоих фрагментов
Фокус в том, что вьюМодел хранится у кого-то. И этот кто-то - ViewModelStoreOwner. Владельцем (Owner) могут быть:
- фрагменты
- активити
- кастомные классы, реализующие интерфейс ViewModelStoreOwner (редкий кейс)
Хранитель вьюМодели определяется во время получения ее экземпляра через ViewModelStoreProvider(ViewModelStoreOwner owner)
Так вот, если в качестве владельца передать не сам фрагмент, а его родительскую активити, то хранить экземпляр вьюМодел будет активити. А доступ к этой активити есть и у второго фрагмента из нашего примера. Значит, когда он захочет получить вьюМодель через ViewModelStoreProvider(getActvivityy()), то он получит тот же экземпляр вьюМодели, с которым работал первый фрагмент (то есть там уже будет лежать аватарка)
Знать об этом способе стоит, но хороший ли это подход с точки зрения архитектуры и памяти?
Style vs Theme
Если вы копируете код, чтобы вставить его в другом месте, задумайтесь. Что-то вы делаете не так
Это касается и xml-разметки. Одинаковые параметры вьюшек нужно выносить в файл styles.xml и ссылаться на них с помощью android:theme="" или style=""
Сначала их одинаковые черты:
и theme, и style — мапа (набор ключ-значений), где:
• ключ - название атрибута
• значение - цвет, размер, форма, тип (например, inputType) или ссылка на файл с ресурсами, где лежит конкретное значение
Теперь различие:
• Style — перечисленные атрибуты применяются только к той вью, к которой применен style
• Theme — перечисленные атрибуты применяются не только к той вью, к которой применена тема, но и ко всем её дочерним вьюшками
Например, если вы примените theme на уровне приложение (укажите в манифесте), то все активити, вьюГруппы и вьюшки будут брать значения оттуда
Если примените theme к ViewGroup (Linear, Relative...), то и сама вьюГруппа, и все её наследники будут брать значения оттуда. При этом все древо родителей об этих значениях не узнают
когда лучше использовать одно, а когда второе?
Если вы копируете код, чтобы вставить его в другом месте, задумайтесь. Что-то вы делаете не так
Это касается и xml-разметки. Одинаковые параметры вьюшек нужно выносить в файл styles.xml и ссылаться на них с помощью android:theme="" или style=""
Сначала их одинаковые черты:
и theme, и style — мапа (набор ключ-значений), где:
• ключ - название атрибута
• значение - цвет, размер, форма, тип (например, inputType) или ссылка на файл с ресурсами, где лежит конкретное значение
Теперь различие:
• Style — перечисленные атрибуты применяются только к той вью, к которой применен style
• Theme — перечисленные атрибуты применяются не только к той вью, к которой применена тема, но и ко всем её дочерним вьюшками
Например, если вы примените theme на уровне приложение (укажите в манифесте), то все активити, вьюГруппы и вьюшки будут брать значения оттуда
Если примените theme к ViewGroup (Linear, Relative...), то и сама вьюГруппа, и все её наследники будут брать значения оттуда. При этом все древо родителей об этих значениях не узнают
когда лучше использовать одно, а когда второе?
👍5
minSdkVersion vs targetSdkVersion
В файле build.gradle уровня приложения можно указать оба значения. Разница в них следующая:
• minSdkVersion — указывает минимальную версию Android (а точнее version Android SDK Platform API), на которую можно будет установить приложение
Например, при разработке вы заметили, что используете функцию, которая появилась только с Android API 20. Если не установить minSdkVersion = 20, то у пользователей на более ранних версиях приложение будет падать на месте вызове несуществующей в их версии функции
• targetSdkVersion — указывает версию Android SDK, фишки который вы, как разработчик, полностью оттестировали
Например, в версии 23 (Android 6.0) переработали систему разрешений (permissions): раньше все разрешения приложение должно было запросить в процессе установки. Начиная с SDK 23, разрешения запрашиваются во время работы приложения
Получается, все приложения, не обновленные разработчиками после выхода Android 6.0, должны падать с ошибкой Runtime Permission, так как во время выполнения приложения разрешения в коде не проверяются?
Нет, падать они не будут. В них установлен targetSdkVersion < 23, а это значит, что система сделает для них исключение и даст им работать по старым правилам (то есть им будет достаточно получить разрешения во время установки приложения)
....
Если с этими все понятно, то что такое maxSdkVersion и compileSdkVersion?
Почему стоит следить за обновлением targetSdkVersion или почему не стоит оставаться на старых версиях?
В файле build.gradle уровня приложения можно указать оба значения. Разница в них следующая:
• minSdkVersion — указывает минимальную версию Android (а точнее version Android SDK Platform API), на которую можно будет установить приложение
Например, при разработке вы заметили, что используете функцию, которая появилась только с Android API 20. Если не установить minSdkVersion = 20, то у пользователей на более ранних версиях приложение будет падать на месте вызове несуществующей в их версии функции
• targetSdkVersion — указывает версию Android SDK, фишки который вы, как разработчик, полностью оттестировали
Например, в версии 23 (Android 6.0) переработали систему разрешений (permissions): раньше все разрешения приложение должно было запросить в процессе установки. Начиная с SDK 23, разрешения запрашиваются во время работы приложения
Получается, все приложения, не обновленные разработчиками после выхода Android 6.0, должны падать с ошибкой Runtime Permission, так как во время выполнения приложения разрешения в коде не проверяются?
Нет, падать они не будут. В них установлен targetSdkVersion < 23, а это значит, что система сделает для них исключение и даст им работать по старым правилам (то есть им будет достаточно получить разрешения во время установки приложения)
....
Если с этими все понятно, то что такое maxSdkVersion и compileSdkVersion?
Почему стоит следить за обновлением targetSdkVersion или почему не стоит оставаться на старых версиях?
👍1
Начинаем выполнять ТЗ в четыре раза быстрее, а в освободившееся время чилим
Рутинный код пишется в несколько раз быстрее, если использовать следующие хот-кеи (о существовании которых почему-то студия не рассказывает большими красными буквами при первом старте):
Alt + Enter — вызывает бесполезное контекстное меню. но пару функций в нём маст хев:
• если вызвать меню, поставив курсор на строку, то первым пунктом будет Extract string resource (позволяет переместить строку в файл strings. xml, где ей и место)
• если вызвать на приватном поле, то предложит сгенерировать геттер и сеттер
• если вызвать на фигурных скобках, в которые обернута только одна строка, то сможете быстро удалить эти фигурные скобки
выделить код + Ctrl + Alt + V — создает переменную и присваивает ей выделенное значение
выделить код + Ctrl + Alt + M — создать функцию и вынести в неё выделенные строки кода
Shift + F6 — переименовать переменную, функцию, класс... вызвать можно в любом месте, студия переименует элемент во всех местах
Ctrl + Alt + O — удалить все неиспользуемые импорты из текущего файла
Ctrl + O — показать список методов, которые можно имплементировать или переопределить
Ctrl + клик мыши — переход к исходникам (к классу, функции, переменной...)
Ctrl + F — поиск по файлу
двойное нажатие Shift — поиск по всему проекту (поиск файлов, классов, функций...)
....
какими ещё хоткеями вы экономике себе время на дополнительную серию сериальчика?
Рутинный код пишется в несколько раз быстрее, если использовать следующие хот-кеи (о существовании которых почему-то студия не рассказывает большими красными буквами при первом старте):
Alt + Enter — вызывает бесполезное контекстное меню. но пару функций в нём маст хев:
• если вызвать меню, поставив курсор на строку, то первым пунктом будет Extract string resource (позволяет переместить строку в файл strings. xml, где ей и место)
• если вызвать на приватном поле, то предложит сгенерировать геттер и сеттер
• если вызвать на фигурных скобках, в которые обернута только одна строка, то сможете быстро удалить эти фигурные скобки
выделить код + Ctrl + Alt + V — создает переменную и присваивает ей выделенное значение
выделить код + Ctrl + Alt + M — создать функцию и вынести в неё выделенные строки кода
Shift + F6 — переименовать переменную, функцию, класс... вызвать можно в любом месте, студия переименует элемент во всех местах
Ctrl + Alt + O — удалить все неиспользуемые импорты из текущего файла
Ctrl + O — показать список методов, которые можно имплементировать или переопределить
Ctrl + клик мыши — переход к исходникам (к классу, функции, переменной...)
Ctrl + F — поиск по файлу
двойное нажатие Shift — поиск по всему проекту (поиск файлов, классов, функций...)
....
какими ещё хоткеями вы экономике себе время на дополнительную серию сериальчика?
👍4
Количество разработчиков и просто отличных ребят в этом чатике перевалило за первую сотню
Всем пива за свой счет! 🍻
Задумайтесь, когда мы еще соберемся такой хорошей компанией? Повод будет, когда приблизимся к пятистам подписчикам, а это ой как ни скоро
Что в ближайших планах:
• поднять количество постов — едва ли, а вот качество в приоритете
• создать бота, который сможет:
• во-первых, выдавать рандомную статью из канала - лента телеги очень неудобна для просмотра всех постов. мало кому хватит усидчивости долистать канал до самого начала, но там же контент не хуже. а будет так: нажал кнопку - получил пост, который ещё не читал
• во-вторых, собирать предложения по темам, которые стоило бы рассмотреть в первую очередь
когда оно случится - не знаю, но все будет
Всем пива за свой счет! 🍻
Задумайтесь, когда мы еще соберемся такой хорошей компанией? Повод будет, когда приблизимся к пятистам подписчикам, а это ой как ни скоро
Что в ближайших планах:
• поднять количество постов — едва ли, а вот качество в приоритете
• создать бота, который сможет:
• во-первых, выдавать рандомную статью из канала - лента телеги очень неудобна для просмотра всех постов. мало кому хватит усидчивости долистать канал до самого начала, но там же контент не хуже. а будет так: нажал кнопку - получил пост, который ещё не читал
• во-вторых, собирать предложения по темам, которые стоило бы рассмотреть в первую очередь
когда оно случится - не знаю, но все будет
Hash\хэш-код\хэш-функция\хэширование
Если вы понимаете, что такое хэш, то гарантированно знаете, как работают: хэш-функции, хэш-таблицы, хэш-мапы, сравнения
Хэш — число, которое однозначно идентифицирует объект, опираясь на заранее придуманные правила сравнения (вольная интерпретация автора, в википедии другие слова)
• хэшем может быть id, если мы заранее договоримся о том, что два объекта точно равны, если равны их id
• хэшем может быть поле name (или любое другое поле), приведенное к числу, если договоримся, что два объекта точно равны, если равны их поля name
• хэшем может быть timestamp создания объекта, сумма всех его полей... почти всё что угодно)
Главное — решить, что в вашей бизнес-логике "равно". Чаще всего "равно" = "у обоих объектов все поля равны" или "равно" = "у обоих объектов равны поля id"
...
В Java у всех классов есть функция hashCode (определена в родительском классе Object). Что для нее "равно"? (для каких объектов вернет одинаковых хэш)
Чем отличается hashCode в Kotlin у классов, помеченных как data class
Проблема коллизий хэш-функции — о чем она и чем опасна?
Если вы понимаете, что такое хэш, то гарантированно знаете, как работают: хэш-функции, хэш-таблицы, хэш-мапы, сравнения
Хэш — число, которое однозначно идентифицирует объект, опираясь на заранее придуманные правила сравнения (вольная интерпретация автора, в википедии другие слова)
• хэшем может быть id, если мы заранее договоримся о том, что два объекта точно равны, если равны их id
• хэшем может быть поле name (или любое другое поле), приведенное к числу, если договоримся, что два объекта точно равны, если равны их поля name
• хэшем может быть timestamp создания объекта, сумма всех его полей... почти всё что угодно)
Главное — решить, что в вашей бизнес-логике "равно". Чаще всего "равно" = "у обоих объектов все поля равны" или "равно" = "у обоих объектов равны поля id"
...
В Java у всех классов есть функция hashCode (определена в родительском классе Object). Что для нее "равно"? (для каких объектов вернет одинаковых хэш)
Чем отличается hashCode в Kotlin у классов, помеченных как data class
Проблема коллизий хэш-функции — о чем она и чем опасна?
Где стоит создавать слушатели для элементов RecyclerView — onCreateViewHolder vs onBindViewHolder
У каждого ViewHolder может быть одна или несколько кнопок, на которых будут висеть листенеры (onClick, onTouch, а может и что-то потяжелее)
Пойдём от противного — почему не стоит выставлять слушатели в onBindViewHolder:
• как известно, в onBindViewHolder приходит holder, в котором лежит view, которая переиспользуется для разных элементов списка. получается, вы каждый раз будете создавать новый объект листенера и вешать его на вьюху, у которой уже есть листенер. создаем лишний объект — теряем память, теряем время
• в onBindViewHolder приходит position, которая есть у холдера в момент байндинга. А что если элемент будет перемещен, допустим, в следствие Drag-and-drop от пользователя?
onBindViewHolder заново вызван не будет, а position изменилось
значит, правильно создавать и вешать слушатель в onCreateViewHolder, получая position через holder.getAdapterPosition() внутри слушателя
также стоит проверять getAdapterPosition() != RecyclerView.NOPOSITION:
position = -1 = NOPOSITION, когда RecyclerView проводит перерасчет позиции холдера (в связи с перемещением\удалением\смещением)
....
также существуют методы:
• getAbsoluteAdapterPosition()
• getBindingAdapterPosition()
• getLayoutPosition()
• getPosition()
в чем между ними разница?
У каждого ViewHolder может быть одна или несколько кнопок, на которых будут висеть листенеры (onClick, onTouch, а может и что-то потяжелее)
Пойдём от противного — почему не стоит выставлять слушатели в onBindViewHolder:
• как известно, в onBindViewHolder приходит holder, в котором лежит view, которая переиспользуется для разных элементов списка. получается, вы каждый раз будете создавать новый объект листенера и вешать его на вьюху, у которой уже есть листенер. создаем лишний объект — теряем память, теряем время
• в onBindViewHolder приходит position, которая есть у холдера в момент байндинга. А что если элемент будет перемещен, допустим, в следствие Drag-and-drop от пользователя?
onBindViewHolder заново вызван не будет, а position изменилось
значит, правильно создавать и вешать слушатель в onCreateViewHolder, получая position через holder.getAdapterPosition() внутри слушателя
также стоит проверять getAdapterPosition() != RecyclerView.NOPOSITION:
position = -1 = NOPOSITION, когда RecyclerView проводит перерасчет позиции холдера (в связи с перемещением\удалением\смещением)
....
также существуют методы:
• getAbsoluteAdapterPosition()
• getBindingAdapterPosition()
• getLayoutPosition()
• getPosition()
в чем между ними разница?
👍5
Handler & Looper
Когда речь заходит о многопоточности, почти все статьи забывают рассказать о том, как главному потоку (aka main thread) удается не заканчиваться
Ведь любой Thread выполняет метод run() и после этого останавливается! MainThread не исключение!
Значит, в методе run главного потока крутится бесконечный while(true), в теле которого раз за разом проверяются все события, происходит отрисовка графики и прочие штуки, которые мы выполняем в главном потоке
Так оно и есть, только немного (много) сложнее и скрыто за абстракциями:
• Looper (Looper.prepare(), Looper.loop(), Looper.quit()) — создает в методе run бесконечный цикл, который не позволяет потоку "закончиться"
• MessageQueue — очередь, в которую складываются приходящие команды. На каждой итерации бесконечного цикла из очереди будут выниматься команды и выполняться
• Handler — забирает команду (Message) из вашего кода и кладет ее в MessageQueue
вы можете закольцевать любой поток, использовав эти механизмы
....
сколько циклов в секунду должен успевать делать Looper главного потока, чтобы приложением можно было пользоваться с комфортом (без просадки кадров в секунду)?
Когда речь заходит о многопоточности, почти все статьи забывают рассказать о том, как главному потоку (aka main thread) удается не заканчиваться
Ведь любой Thread выполняет метод run() и после этого останавливается! MainThread не исключение!
Значит, в методе run главного потока крутится бесконечный while(true), в теле которого раз за разом проверяются все события, происходит отрисовка графики и прочие штуки, которые мы выполняем в главном потоке
Так оно и есть, только немного (много) сложнее и скрыто за абстракциями:
• Looper (Looper.prepare(), Looper.loop(), Looper.quit()) — создает в методе run бесконечный цикл, который не позволяет потоку "закончиться"
• MessageQueue — очередь, в которую складываются приходящие команды. На каждой итерации бесконечного цикла из очереди будут выниматься команды и выполняться
• Handler — забирает команду (Message) из вашего кода и кладет ее в MessageQueue
вы можете закольцевать любой поток, использовав эти механизмы
....
сколько циклов в секунду должен успевать делать Looper главного потока, чтобы приложением можно было пользоваться с комфортом (без просадки кадров в секунду)?
👍3
background / backgroundTint / backgroundTintMode vs tint / tintMode
Овладев этими параметрами View, вы перестанете хранить несколько экземпляров иконок, покрашенных в разные цвета, и сэкономите время на создании одинаковых по форме, но отличающихся по цвету drawable-ресурсов
• android:background = цвет, картинка, иконка, фигура или селектор, описанный в xml-файле
• android:backgroundTint = цвет, который будет накладываться на background
• android:backgroundTintMode = способ смешения цветов
Например, выбрав tintMode, можно определить, будут ли смешиваться альфа-каналы (прозрачность) заданного цвета и ресурса из background.
Также можно задать tintMode, который позволит полностью игнорировать исходный цвет ресурса, заданного в поле background.
Но перекрашивать можно не только background. Например, можно менять цвет ресурса, заданного атрибутом android:src
В этом случае нам понадобятся:
android:tint — цвет, который будет накладываться на src
android:tintMode — способ смешения цветов
Если минимальная версия sdk не позволяет использовать эти атрибуты, можно установить их через библиотеку AppCompat — достаточно заменить префикс android на app (например, app:tintMode)
Вывод: не стоит дублировать ресурсы одной формы — можно хранить их в одном экземпляре, например, черного цвета, а там где нужно — перекрашивать его
Овладев этими параметрами View, вы перестанете хранить несколько экземпляров иконок, покрашенных в разные цвета, и сэкономите время на создании одинаковых по форме, но отличающихся по цвету drawable-ресурсов
• android:background = цвет, картинка, иконка, фигура или селектор, описанный в xml-файле
• android:backgroundTint = цвет, который будет накладываться на background
• android:backgroundTintMode = способ смешения цветов
Например, выбрав tintMode, можно определить, будут ли смешиваться альфа-каналы (прозрачность) заданного цвета и ресурса из background.
Также можно задать tintMode, который позволит полностью игнорировать исходный цвет ресурса, заданного в поле background.
Но перекрашивать можно не только background. Например, можно менять цвет ресурса, заданного атрибутом android:src
В этом случае нам понадобятся:
android:tint — цвет, который будет накладываться на src
android:tintMode — способ смешения цветов
Если минимальная версия sdk не позволяет использовать эти атрибуты, можно установить их через библиотеку AppCompat — достаточно заменить префикс android на app (например, app:tintMode)
Вывод: не стоит дублировать ресурсы одной формы — можно хранить их в одном экземпляре, например, черного цвета, а там где нужно — перекрашивать его
👍4
MVVM — архитектура за 100 слов
(Model - View - ViewModel)
Расширение поста — https://news.1rj.ru/str/dolgo_polo_dev/22
• View — умеет только отображать данные, которые приходят из ViewModel
Обычно View - это Activity или Fragment
View подписывается на LiveData-объекты, которые лежат во ViewModel — когда данные приходят, она их отображает (никак не обрабатывая!)
View передает события (Actions - клики, тачи, вводы тексты...) во ViewModel
• Model — на самом деле Model Source (источник данных, Repository или же Database)
умеет принимать запрос на данные от ViewModel, получать их (с сервера или локального хранилища) и отдавать обратно во ViewModel
• ViewModel — прослойка между View и Model. Умеет:
• принимать события (Actions) от View и реагировать на них
• запрашивать данные у Model
• делать бизнес-логику - обрабатывать данные, трансформировать и соединять их
• отдавать данные во View через объекты LiveData (MutableLiveData)
(Model - View - ViewModel)
Расширение поста — https://news.1rj.ru/str/dolgo_polo_dev/22
• View — умеет только отображать данные, которые приходят из ViewModel
Обычно View - это Activity или Fragment
View подписывается на LiveData-объекты, которые лежат во ViewModel — когда данные приходят, она их отображает (никак не обрабатывая!)
View передает события (Actions - клики, тачи, вводы тексты...) во ViewModel
• Model — на самом деле Model Source (источник данных, Repository или же Database)
умеет принимать запрос на данные от ViewModel, получать их (с сервера или локального хранилища) и отдавать обратно во ViewModel
• ViewModel — прослойка между View и Model. Умеет:
• принимать события (Actions) от View и реагировать на них
• запрашивать данные у Model
• делать бизнес-логику - обрабатывать данные, трансформировать и соединять их
• отдавать данные во View через объекты LiveData (MutableLiveData)
👍2
Как воруется платный контент
Если вы допустите эту ошибку, то любой гений, готовый потратить вечер на хакерство, сможет собрать крякнутую версию вашего приложения с доступом ко всем платным фичам
Не стоит:
• хранить информацию о купленном контенте локально
• проверять доступ к контенту исключительно отдельным запросом к серверу
Почему не стоит или как проводится атака:
1) из установленного приложения вытаскивается apk-архив
2) dex-файлы с кодом перегоняются в smali-код
smali код более читабельный для человека, чем dex
3) в коде находится строчка, в которой проверяется доступ к платному контенту
то есть строчка с if(userHasAccess())
и меняется на строчку if(true) или просто удаляется
4) измененный код собирается обратно в apk-файл
5) новый apk подписывается и устанавливается на устройство
а также рассылается всем друзьям)
Как избежать:
единственный известный мне способ — пнуть бек-разработчика, чтобы он на своей стороне всегда проверял, можно ли этому пользователю отдать контент на запрос от приложения
сервер должен относиться к приложению как к штуке, которая может попытаться его наебать
например, если от приложения приходит запрос на прослушивание аудиокниги, то сервер должен проверить, купил ли пользователь эту книгу
....
о каких еще способах воровства вам известно?
Если вы допустите эту ошибку, то любой гений, готовый потратить вечер на хакерство, сможет собрать крякнутую версию вашего приложения с доступом ко всем платным фичам
Не стоит:
• хранить информацию о купленном контенте локально
• проверять доступ к контенту исключительно отдельным запросом к серверу
Почему не стоит или как проводится атака:
1) из установленного приложения вытаскивается apk-архив
2) dex-файлы с кодом перегоняются в smali-код
smali код более читабельный для человека, чем dex
3) в коде находится строчка, в которой проверяется доступ к платному контенту
то есть строчка с if(userHasAccess())
и меняется на строчку if(true) или просто удаляется
4) измененный код собирается обратно в apk-файл
5) новый apk подписывается и устанавливается на устройство
а также рассылается всем друзьям)
Как избежать:
единственный известный мне способ — пнуть бек-разработчика, чтобы он на своей стороне всегда проверял, можно ли этому пользователю отдать контент на запрос от приложения
сервер должен относиться к приложению как к штуке, которая может попытаться его наебать
например, если от приложения приходит запрос на прослушивание аудиокниги, то сервер должен проверить, купил ли пользователь эту книгу
....
о каких еще способах воровства вам известно?
👍3
AudioFocus — как правильно издать звук
Мы этого практически не замечаем, но в телефоне достаточно много типов свистоперделок — музыка, уведомления, звонки, будильник, короткие звуки в приложениях (редко, но иногда анимации в приложениях озвучивают), фоновая музыка в играх и звуки, связанные с событиями в игре
Весь этот музыкальный мусор может воспроизводиться параллельно, последовательно или асинхронно
Тут введем понятие "фокус" (AudioFocus) — право приложения перетащить большую часть аудио-внимания на себя
Допустим, вы проигрываете музыку. Тут другое приложение заявляет, что хочет сместить фокус на себя. Вы можете выбрать один из путей:
• замолчать - выключите свою музыку, пускай заиграет другое приложение
например, пользователь открыл видео на ютубу - мы ему больше не нужны
• приглушиться - ваша музыка продолжит играть, но тише.
например, пришло уведомление - нашу музыку приглушаем на секунду, чтобы пользователь услышал "дзынь" от пуша
• забить - пускай пользователь слушает оба потока одновременно, его проблемы
• замолчать, но автоматически вернуться
например, пользователь начал слушать голосовое в вк - выключаем нашу музыку, а как голосовое закончится - включаемся заново
все эти махинации разруливаются через AudioManager.OnAudioFocusChangeListener, в который система будет кидать флаг состояние фокуса
....
это единственная область в андроиде, где приложения могут поднасрать друг другу. ведите себя культурно)
Мы этого практически не замечаем, но в телефоне достаточно много типов свистоперделок — музыка, уведомления, звонки, будильник, короткие звуки в приложениях (редко, но иногда анимации в приложениях озвучивают), фоновая музыка в играх и звуки, связанные с событиями в игре
Весь этот музыкальный мусор может воспроизводиться параллельно, последовательно или асинхронно
Тут введем понятие "фокус" (AudioFocus) — право приложения перетащить большую часть аудио-внимания на себя
Допустим, вы проигрываете музыку. Тут другое приложение заявляет, что хочет сместить фокус на себя. Вы можете выбрать один из путей:
• замолчать - выключите свою музыку, пускай заиграет другое приложение
например, пользователь открыл видео на ютубу - мы ему больше не нужны
• приглушиться - ваша музыка продолжит играть, но тише.
например, пришло уведомление - нашу музыку приглушаем на секунду, чтобы пользователь услышал "дзынь" от пуша
• забить - пускай пользователь слушает оба потока одновременно, его проблемы
• замолчать, но автоматически вернуться
например, пользователь начал слушать голосовое в вк - выключаем нашу музыку, а как голосовое закончится - включаемся заново
все эти махинации разруливаются через AudioManager.OnAudioFocusChangeListener, в который система будет кидать флаг состояние фокуса
....
это единственная область в андроиде, где приложения могут поднасрать друг другу. ведите себя культурно)
👍6
Карта канала
• t.me/dolgo_polo_dev/14 — Негласные правила в названиях
• t.me/dolgo_polo_dev/15 — Отличие onStart() от onResume()
• t.me/dolgo_polo_dev/18 — Dependency Injection на пальцах (DI, Dagger, Hilt, Koin)
• t.me/dolgo_polo_dev/19 — .aab (.AAB) - что и зачем
• t.me/dolgo_polo_dev/20 — Да кто этот ваш Context
• t.me/dolgo_polo_dev/21 — Волшебство LiveData
• t.me/dolgo_polo_dev/22 — Главная цель архитектуры или зачем столько страданий
• t.me/dolgo_polo_dev/23 — Каналы уведомлений
• t.me/dolgo_polo_dev/24 — Общая ViewModel
• t.me/dolgo_polo_dev/25 — Style vs Theme
• t.me/dolgo_polo_dev/26 — minSdkVersion vs targetSdkVersion
• t.me/dolgo_polo_dev/27 — Начинаем выполнять ТЗ в четыре раза быстрее, а в освободившееся время чилим
• t.me/dolgo_polo_dev/29 — Hash\хэш-код\хэш-функция\хэширование
• t.me/dolgo_polo_dev/30 — Где стоит создавать слушатели для элементов RecyclerView - onCreateViewHolder vs onBindViewHolder
• t.me/dolgo_polo_dev/31 — Handler & Looper
• t.me/dolgo_polo_dev/32 — background / backgroundTint / backgroundTintMode vs tint / tintMode
• t.me/dolgo_polo_dev/33 — MVVM - архитектура за 100 слов
• t.me/dolgo_polo_dev/34 — Как воруется платный контент
• t.me/dolgo_polo_dev/35 — AudioFocus - как правильно издать звук
• t.me/dolgo_polo_dev/39 — Editable vs String
• t.me/dolgo_polo_dev/43 — Где хранится Bundle во время уничтожения / Когда работает onSaveInstanceState()
• t.me/dolgo_polo_dev/45 — Package Name != Application ID
• t.me/dolgo_polo_dev/47 — Как по hashCode определяется позиция
• t.me/dolgo_polo_dev/48 — Когда не нужно заменять HashMap на ArrayMap
• t.me/dolgo_polo_dev/50 — Концепция InputStream - Reader - Buffered
• t.me/dolgo_polo_dev/51 — Kotlin Coroutine. Suspend - зачем и как?
• t.me/dolgo_polo_dev/52 — sealed interface vs sealed class vs enum
• t.me/dolgo_polo_dev/55 — Kotlin Coroutine — корутины за 100 слов
• t.me/dolgo_polo_dev/57 — Типы permissions для получения локации
• t.me/dolgo_polo_dev/59 — Философия LayoutParams
• t.me/dolgo_polo_dev/60 — Xml - зачем префиксы android, tools, app
• Kotlin Operator
• Как придумать свои единицы измерения на примере Compose
• Task или виды Activity launch mode
• Порядок инициализации полей, конструкторов и блоков
• MVI — архитектура за 100 слов
• URL vs URI
• Kotlin Operator
Рубрика #очевидные_вещи
• t.me/dolgo_polo_dev/40 - правила рубрики
• t.me/dolgo_polo_dev/42 - Java vs Kotlin
Чтобы поделиться постом, кликните по нему ПКМ и выберите Copy Post Link
Или старый добрый Select Message -> Forward
• t.me/dolgo_polo_dev/14 — Негласные правила в названиях
• t.me/dolgo_polo_dev/15 — Отличие onStart() от onResume()
• t.me/dolgo_polo_dev/18 — Dependency Injection на пальцах (DI, Dagger, Hilt, Koin)
• t.me/dolgo_polo_dev/19 — .aab (.AAB) - что и зачем
• t.me/dolgo_polo_dev/20 — Да кто этот ваш Context
• t.me/dolgo_polo_dev/21 — Волшебство LiveData
• t.me/dolgo_polo_dev/22 — Главная цель архитектуры или зачем столько страданий
• t.me/dolgo_polo_dev/23 — Каналы уведомлений
• t.me/dolgo_polo_dev/24 — Общая ViewModel
• t.me/dolgo_polo_dev/25 — Style vs Theme
• t.me/dolgo_polo_dev/26 — minSdkVersion vs targetSdkVersion
• t.me/dolgo_polo_dev/27 — Начинаем выполнять ТЗ в четыре раза быстрее, а в освободившееся время чилим
• t.me/dolgo_polo_dev/29 — Hash\хэш-код\хэш-функция\хэширование
• t.me/dolgo_polo_dev/30 — Где стоит создавать слушатели для элементов RecyclerView - onCreateViewHolder vs onBindViewHolder
• t.me/dolgo_polo_dev/31 — Handler & Looper
• t.me/dolgo_polo_dev/32 — background / backgroundTint / backgroundTintMode vs tint / tintMode
• t.me/dolgo_polo_dev/33 — MVVM - архитектура за 100 слов
• t.me/dolgo_polo_dev/34 — Как воруется платный контент
• t.me/dolgo_polo_dev/35 — AudioFocus - как правильно издать звук
• t.me/dolgo_polo_dev/39 — Editable vs String
• t.me/dolgo_polo_dev/43 — Где хранится Bundle во время уничтожения / Когда работает onSaveInstanceState()
• t.me/dolgo_polo_dev/45 — Package Name != Application ID
• t.me/dolgo_polo_dev/47 — Как по hashCode определяется позиция
• t.me/dolgo_polo_dev/48 — Когда не нужно заменять HashMap на ArrayMap
• t.me/dolgo_polo_dev/50 — Концепция InputStream - Reader - Buffered
• t.me/dolgo_polo_dev/51 — Kotlin Coroutine. Suspend - зачем и как?
• t.me/dolgo_polo_dev/52 — sealed interface vs sealed class vs enum
• t.me/dolgo_polo_dev/55 — Kotlin Coroutine — корутины за 100 слов
• t.me/dolgo_polo_dev/57 — Типы permissions для получения локации
• t.me/dolgo_polo_dev/59 — Философия LayoutParams
• t.me/dolgo_polo_dev/60 — Xml - зачем префиксы android, tools, app
• Kotlin Operator
• Как придумать свои единицы измерения на примере Compose
• Task или виды Activity launch mode
• Порядок инициализации полей, конструкторов и блоков
• MVI — архитектура за 100 слов
• URL vs URI
• Kotlin Operator
Рубрика #очевидные_вещи
• t.me/dolgo_polo_dev/40 - правила рубрики
• t.me/dolgo_polo_dev/42 - Java vs Kotlin
Чтобы поделиться постом, кликните по нему ПКМ и выберите Copy Post Link
Или старый добрый Select Message -> Forward
Telegram
Dolgo.polo Dev
Негласные правила в названиях
Называть классы можно как угодно, но со временем каждый разработчик приходит к одним и тем же ключевым словам, которые на удивление очень точно описывают содержание файла:
Base -> класс, описывающий переменные и функции, которые…
Называть классы можно как угодно, но со временем каждый разработчик приходит к одним и тем же ключевым словам, которые на удивление очень точно описывают содержание файла:
Base -> класс, описывающий переменные и функции, которые…
👍3🔥2❤1
Dolgo.polo Dev | Денис Долгополов pinned «Карта канала • t.me/dolgo_polo_dev/14 — Негласные правила в названиях • t.me/dolgo_polo_dev/15 — Отличие onStart() от onResume() • t.me/dolgo_polo_dev/18 — Dependency Injection на пальцах (DI, Dagger, Hilt, Koin) • t.me/dolgo_polo_dev/19 — .aab (.AAB) -…»
Editable vs String
• String - класс из Java
• Editable - интерфейс из Android SDK
• String - неизменяем в размере (все операции, меняющие размер строки, на самом деле создают новую строку)
• Editable - позволяет работать с массивом символов динамической длины
• String - хранит только символы
• Editable - хранит символы и их разметку (цвет, размер, стиль ≈ Spannable)
Editable активно используют EditText и TextView
Точнее в них используется имплементация Editable — SpannableStringBuilder, которая помимо всего прочего обеспечивает взаимодействие с интерфейсом TextWatcher (afterTextChanged() - beforeTextChanged() - onTextChanged())
....
бываю ли кейсы, когда стоит самому имплементировать Editable или хотя бы использовать его в местах, не связанных с EditText\TextView?
• String - класс из Java
• Editable - интерфейс из Android SDK
• String - неизменяем в размере (все операции, меняющие размер строки, на самом деле создают новую строку)
• Editable - позволяет работать с массивом символов динамической длины
• String - хранит только символы
• Editable - хранит символы и их разметку (цвет, размер, стиль ≈ Spannable)
Editable активно используют EditText и TextView
Точнее в них используется имплементация Editable — SpannableStringBuilder, которая помимо всего прочего обеспечивает взаимодействие с интерфейсом TextWatcher (afterTextChanged() - beforeTextChanged() - onTextChanged())
....
бываю ли кейсы, когда стоит самому имплементировать Editable или хотя бы использовать его в местах, не связанных с EditText\TextView?
Новый проект - #очевидные_вещи
• Суть — написать несколько постов о вещах, которые очевидны всем, у кого есть хотя бы полгода-год опыта, но совершенно неочевидны новичкам (вспомните себя, пытающихся понять, зачем в java есть классы)
В постах будет как моё субъективное мнение, так и моё субъективное мнение, составленное из десятка прочтенных холиваров на заезженные темы
• Цель — когда в следующий раз вы получите вопрос на эту тему от своего ученика, друга или незнакомца в чате, сможете отправить ссылку на пост, а не писать снова #очевидные_вещи
• Суть — написать несколько постов о вещах, которые очевидны всем, у кого есть хотя бы полгода-год опыта, но совершенно неочевидны новичкам (вспомните себя, пытающихся понять, зачем в java есть классы)
В постах будет как моё субъективное мнение, так и моё субъективное мнение, составленное из десятка прочтенных холиваров на заезженные темы
• Цель — когда в следующий раз вы получите вопрос на эту тему от своего ученика, друга или незнакомца в чате, сможете отправить ссылку на пост, а не писать снова #очевидные_вещи
Java vs Kotlin
#очевидные_вещи
Нужно ли знать Java? - нужно, так как
все сотни тысяч строк проектов, на которых вы будете работать, и библиотек, которые затащите в свой проект, будут написаны на Java
от них не удастся изолироваться, придется понимать, как оно работает
то же касается и обучения — множество важных статей написано на Java и никто их переписывать не будет
Почему переходят на Kotlin?
• он короче (можно сделать то же, что сделает Java-код, но меньшим количеством строк-символов)
• он напичкан разными фишками, которые ускоряют разработку
меньше кода - больше профита - разработчикам легче - бизнесу меньше платить за часы программиста
• все новые фишки завязаны на нём - мультиплатформенность, корутины с асинхронностью, compose и т. д.
нет, не котлин все это изобрел, но сейчас развивается это на нем
• google решил поддержать Kotlin. а если компания такого уровня решает сделать технологию базовой - придется с технологией считаться
Лучше Kotlin или Java?
а мы тут не философствовать собрались. закиньте этот вопрос в любой чатик и получите две тысячи аргументом за обе щеки
Можно ли начать с изучения Kotlin?
Найду ли работу, зная только Kotlin?
в маленьком стартапе - да, в других местах - нет
и в любом случае тебя жизнь заставит начать читать Java-код
Как понять, что знаю язык достаточно для поиска работы?
никак. углубляться в язык можно бесконечно.
но для начала достаточно, если твоих знаний хватает, чтобы решить тз, которое ставит работодатель
(насколько качественным получится результат - вопрос совсем другой)
#очевидные_вещи
Нужно ли знать Java? - нужно, так как
на Kotlin активно пишут только последние 3 года
все сотни тысяч строк проектов, на которых вы будете работать, и библиотек, которые затащите в свой проект, будут написаны на Java
от них не удастся изолироваться, придется понимать, как оно работает
то же касается и обучения — множество важных статей написано на Java и никто их переписывать не будет
Почему переходят на Kotlin?
• он короче (можно сделать то же, что сделает Java-код, но меньшим количеством строк-символов)
• он напичкан разными фишками, которые ускоряют разработку
меньше кода - больше профита - разработчикам легче - бизнесу меньше платить за часы программиста
• все новые фишки завязаны на нём - мультиплатформенность, корутины с асинхронностью, compose и т. д.
нет, не котлин все это изобрел, но сейчас развивается это на нем
• google решил поддержать Kotlin. а если компания такого уровня решает сделать технологию базовой - придется с технологией считаться
Лучше Kotlin или Java?
а мы тут не философствовать собрались. закиньте этот вопрос в любой чатик и получите две тысячи аргументом за обе щеки
Можно ли начать с изучения Kotlin?
можно. языки схожи, разницы относительно немного
Найду ли работу, зная только Kotlin?
в маленьком стартапе - да, в других местах - нет
и в любом случае тебя жизнь заставит начать читать Java-код
Как понять, что знаю язык достаточно для поиска работы?
никак. углубляться в язык можно бесконечно.
но для начала достаточно, если твоих знаний хватает, чтобы решить тз, которое ставит работодатель
(насколько качественным получится результат - вопрос совсем другой)
Где хранится Bundle во время уничтожения / Когда работает onSaveInstanceState()
onSaveInstanceState() — вызывается, только когда Activity уничтожается по инициативе системы, а не юзера
вызывается, когда активити уничтожена из-за:
• поворота экрана
• нехватки памяти
• смены языка
• других системных событий, требующих пересоздания\временного уничтожения активити
не вызывается:
• если пользоваться ушел с экрана кнопкой "назад"
• пользователь закрыл приложение
Bundle — объект-мапа, хранящий информацию в виде "ключ-значение"
Bundle на время пересоздания активити записывается в объект ActivityRecord, отвечающий за хранение информации о нашей активити
ActivityRecord в свою очередь связан через Task c ActivityManagerService.... нормальному человеку эти тонкости знать уже не надо)
В конечном итоге Bundle сериализуется (превращается в строку) и записывается на диск (а не остается в оперативке)
...,
в Bundle нельзя сохранять тяжелые данные. но почему?
чем отличается onCreate(Bundle state) от onRestoreInstanceState(Bundle state)?
onSaveInstanceState() — вызывается, только когда Activity уничтожается по инициативе системы, а не юзера
вызывается, когда активити уничтожена из-за:
• поворота экрана
• нехватки памяти
• смены языка
• других системных событий, требующих пересоздания\временного уничтожения активити
не вызывается:
• если пользоваться ушел с экрана кнопкой "назад"
• пользователь закрыл приложение
Bundle — объект-мапа, хранящий информацию в виде "ключ-значение"
Bundle на время пересоздания активити записывается в объект ActivityRecord, отвечающий за хранение информации о нашей активити
ActivityRecord в свою очередь связан через Task c ActivityManagerService.... нормальному человеку эти тонкости знать уже не надо)
В конечном итоге Bundle сериализуется (превращается в строку) и записывается на диск (а не остается в оперативке)
...,
в Bundle нельзя сохранять тяжелые данные. но почему?
чем отличается onCreate(Bundle state) от onRestoreInstanceState(Bundle state)?
🔥1
как узнали о канале?
Anonymous Poll
18%
hubr
42%
чаты в tg
19%
посты в группах tg
2%
vk
1%
tiktok
4%
скинули ссылку друзья-знакомые
14%
другие ресурсы
Package Name != Application ID
Я был уверен, что Play Market использует имя пакета для аутентификации приложения ⇒ изменять пакет после публикации нельзя. Это не так
• package name — имя папок, в которых лежит проект. используется для
• сборки проекта
• расположения генерируемых файлов (например, com.dick.test.R)
• доступа к классам через точку (например, .MainActivity = com.dick.test.MainActivity)
следовательно, package name, прописанный в манифесте, должен обязательно совпадать с реальным названием папок
• applicationId — уникальный айдишник приложения
прописывается в buiild.gradle → android → defaultConfig
используется во всех местах, где просят прописать package name приложения — например, в Play Market и Firebase Console
даже Context.getPackageName() возвращает application id, а не package name... не спрашивайте почему
....
но есть еще интересный факт: после сборки apk в манифест приложения в поле package зашивается applicationId
Я был уверен, что Play Market использует имя пакета для аутентификации приложения ⇒ изменять пакет после публикации нельзя. Это не так
• package name — имя папок, в которых лежит проект. используется для
• сборки проекта
• расположения генерируемых файлов (например, com.dick.test.R)
• доступа к классам через точку (например, .MainActivity = com.dick.test.MainActivity)
следовательно, package name, прописанный в манифесте, должен обязательно совпадать с реальным названием папок
• applicationId — уникальный айдишник приложения
прописывается в buiild.gradle → android → defaultConfig
используется во всех местах, где просят прописать package name приложения — например, в Play Market и Firebase Console
даже Context.getPackageName() возвращает application id, а не package name... не спрашивайте почему
....
но есть еще интересный факт: после сборки apk в манифест приложения в поле package зашивается applicationId
Ставим несколько клик-листенеров на разные буквы TextView
Иногда возникает потребность отобразить сплошной текст, в котором будет несколько ссылок
Можно:
• повесить OnTouchListener и вычислять координату каждой буквы по тапу — но это больше похоже на лайфхаки из тик-тока а-ля "что делать, если сверху кружка запаяна, а снизу в ней дырка"
• засунуть в проект библиотеку с FlowLayout (а-ля LinearLayout, но с автоматическим переносом вьюх на следующую строчку) и запихать в него отдельные TextView для каждого слова — но не надо
А можно сделать быстро и встроенными средствами:
1. создаем для каждой ссылки отдельный SpannableString
2. на каждый SpannableString вешаем свой ClickableSpan(), содержащий колбэк onClick()
3. собираем все SpannableString через SpannableStringBuilder и передаем его через textView.setText(builder)
4. меняем textView.movementMethod = LinkMovementMethod, чтобы TextView научилась обрабатывать касания на отдельные участки
5. ставим цвет ссылок с помощью textView.linkTextColor = color
готово, нажатия на определенные символы будет выполнять разные действия
....
по совету читателей решил приводить небольшие куски кода - ссылки буду оставлять в комментариях
Иногда возникает потребность отобразить сплошной текст, в котором будет несколько ссылок
Можно:
• повесить OnTouchListener и вычислять координату каждой буквы по тапу — но это больше похоже на лайфхаки из тик-тока а-ля "что делать, если сверху кружка запаяна, а снизу в ней дырка"
• засунуть в проект библиотеку с FlowLayout (а-ля LinearLayout, но с автоматическим переносом вьюх на следующую строчку) и запихать в него отдельные TextView для каждого слова — но не надо
А можно сделать быстро и встроенными средствами:
1. создаем для каждой ссылки отдельный SpannableString
2. на каждый SpannableString вешаем свой ClickableSpan(), содержащий колбэк onClick()
3. собираем все SpannableString через SpannableStringBuilder и передаем его через textView.setText(builder)
4. меняем textView.movementMethod = LinkMovementMethod, чтобы TextView научилась обрабатывать касания на отдельные участки
5. ставим цвет ссылок с помощью textView.linkTextColor = color
готово, нажатия на определенные символы будет выполнять разные действия
....
по совету читателей решил приводить небольшие куски кода - ссылки буду оставлять в комментариях
👍5