Количество разработчиков и просто отличных ребят в этом чатике перевалило за первую сотню
Всем пива за свой счет! 🍻
Задумайтесь, когда мы еще соберемся такой хорошей компанией? Повод будет, когда приблизимся к пятистам подписчикам, а это ой как ни скоро
Что в ближайших планах:
• поднять количество постов — едва ли, а вот качество в приоритете
• создать бота, который сможет:
• во-первых, выдавать рандомную статью из канала - лента телеги очень неудобна для просмотра всех постов. мало кому хватит усидчивости долистать канал до самого начала, но там же контент не хуже. а будет так: нажал кнопку - получил пост, который ещё не читал
• во-вторых, собирать предложения по темам, которые стоило бы рассмотреть в первую очередь
когда оно случится - не знаю, но все будет
Всем пива за свой счет! 🍻
Задумайтесь, когда мы еще соберемся такой хорошей компанией? Повод будет, когда приблизимся к пятистам подписчикам, а это ой как ни скоро
Что в ближайших планах:
• поднять количество постов — едва ли, а вот качество в приоритете
• создать бота, который сможет:
• во-первых, выдавать рандомную статью из канала - лента телеги очень неудобна для просмотра всех постов. мало кому хватит усидчивости долистать канал до самого начала, но там же контент не хуже. а будет так: нажал кнопку - получил пост, который ещё не читал
• во-вторых, собирать предложения по темам, которые стоило бы рассмотреть в первую очередь
когда оно случится - не знаю, но все будет
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
Как по hashCode определяется позиция
Вероятно, все слышали, что есть структуры данных, в которых позиция, на которой будет храниться элемент, определяется его hashCode
Но hashCode — это Integer, значит, в таких структурах как HashMap, должен лежать массив длиной 2^32 ?
Нет, делается немного хитрее (на примере HashMap):
1. создается массив определенного размера например, arr.length = 16
2. когда наступает момент добавить новый элемент, вычисляется его хэш (хэш ключа). например, hashCode = 200
3. вычисляется позиция, на которую нужно положить элемент = hashCode % arr.length = 200 % 16 = 8
4. на позиции 8 создается массив. в него добавляется наш элемент
5. когда следующий элемент попадает на позицию 8, наш первый элемент сохраняет на него ссылку
да, мы получили массив связанных списков
да, нам теперь плевать на коллизии
дальше можно подумать, как эту систему балансировать, изменяя размер arr.length
....
есть ли другие подходы к сохранению данных, с вычислением позиции через hashCode элемента?
Вероятно, все слышали, что есть структуры данных, в которых позиция, на которой будет храниться элемент, определяется его hashCode
Но hashCode — это Integer, значит, в таких структурах как HashMap, должен лежать массив длиной 2^32 ?
Нет, делается немного хитрее (на примере HashMap):
1. создается массив определенного размера например, arr.length = 16
2. когда наступает момент добавить новый элемент, вычисляется его хэш (хэш ключа). например, hashCode = 200
3. вычисляется позиция, на которую нужно положить элемент = hashCode % arr.length = 200 % 16 = 8
4. на позиции 8 создается массив. в него добавляется наш элемент
5. когда следующий элемент попадает на позицию 8, наш первый элемент сохраняет на него ссылку
да, мы получили массив связанных списков
да, нам теперь плевать на коллизии
дальше можно подумать, как эту систему балансировать, изменяя размер arr.length
....
есть ли другие подходы к сохранению данных, с вычислением позиции через hashCode элемента?
👍1
Когда не нужно заменять HashMap на ArrayMap
Думаю, все в курсе — если попробовать использовать HashMap, студия предложит заменить тип на ArrayMap
Почему (короткий ответ) — ArrayMap оптимизирован под Android
Почему (правильный ответ) —
HashMap для каждой пары ключ-значение создаёт объект Map.Entry, который хранит ключ, значение, хэш ключа, ссылку на следующий объект
Такая сложность нужна, так как HashMap хранит данные в массиве массивов (см. "связанные посты" в комментариях)
Но даже пустой объект весит n-байт, а тут столько побочной информации
Поэтому ArrayMap реализует другой подход: он содержит два массива - один mArray для keys и objects, второй mHashes для хэша ключей
Процесс добавления новой пары:
1. в mArray кладется ключ
2. на следующую позицию в mArray кладется объект
3. вычисляется hash от ключа и кладется в mHashes
Вывод: в HashMap поиск по ключу быстрее, но ArrayMap съедает в разы меньше памяти
....
А зачем тогда SparseArray?
Бывали ли у вас ситуации, когда правильный выбор структуры давал заметный прирост к производительности или андроид-разработчику в реальности такие знания не пригождаются?
Думаю, все в курсе — если попробовать использовать HashMap, студия предложит заменить тип на ArrayMap
Почему (короткий ответ) — ArrayMap оптимизирован под Android
Почему (правильный ответ) —
HashMap для каждой пары ключ-значение создаёт объект Map.Entry, который хранит ключ, значение, хэш ключа, ссылку на следующий объект
Такая сложность нужна, так как HashMap хранит данные в массиве массивов (см. "связанные посты" в комментариях)
Но даже пустой объект весит n-байт, а тут столько побочной информации
Поэтому ArrayMap реализует другой подход: он содержит два массива - один mArray для keys и objects, второй mHashes для хэша ключей
Процесс добавления новой пары:
1. в mArray кладется ключ
2. на следующую позицию в mArray кладется объект
3. вычисляется hash от ключа и кладется в mHashes
Вывод: в HashMap поиск по ключу быстрее, но ArrayMap съедает в разы меньше памяти
....
А зачем тогда SparseArray?
Бывали ли у вас ситуации, когда правильный выбор структуры давал заметный прирост к производительности или андроид-разработчику в реальности такие знания не пригождаются?
👍2👎1
с наступившим, работяги!
желаю отдохнуть от работы, учебы, самообразования и всего, от чего хочется отдохнуть, чтобы взяться за все это с новыми силами и получить новые успехи
желаю отдохнуть от работы, учебы, самообразования и всего, от чего хочется отдохнуть, чтобы взяться за все это с новыми силами и получить новые успехи
❤1
Концепция InputStream - Reader - Buffered
С понятием InputStream мобильный разработчик сталкивается, когда нужно прочитать локальный файл или загрузить его по ссылке
Общая логика считывания больших объемов данных (или данных неизвестного размера) следующая:
• InputStream (все его реализации) — класс, позволяющий обозначить источник данных (файл, ссылка, массив байтов...) и начать считывать его байт за байтом
• Reader (все его реализации) — класс, преобразующий байты, считываемые при помощи InputStream, в символы по заданной таблице кодирования (например, Unicode)
• Buffered (все его реализации) — класс, позволяющий ускорить процесс чтения за счет буферизации источника данных
Зачем буфер: если мы читаем файл с жесткого диска, то каждая операция запроса и переноса байтов с самого диска в оперативку занимает много времени. Потому нашей программе лучше за одно обращение считать n-байтов, а не один. Это справедливо для сетевых запросов
Комбинация реализаций позволяет считать данные в нужном формате и с оптимальными настройками буферизации
Та же самая история справедлива и для отдачи байтов: OutputStream - Writer - Buffered
....
загадка: по какой таблице кодирования Reader/Writer сопоставит байты и символы? откуда он ее возьмет - зашита в Java, определена в Android, задается JVM...?
С понятием InputStream мобильный разработчик сталкивается, когда нужно прочитать локальный файл или загрузить его по ссылке
Общая логика считывания больших объемов данных (или данных неизвестного размера) следующая:
• InputStream (все его реализации) — класс, позволяющий обозначить источник данных (файл, ссылка, массив байтов...) и начать считывать его байт за байтом
• Reader (все его реализации) — класс, преобразующий байты, считываемые при помощи InputStream, в символы по заданной таблице кодирования (например, Unicode)
• Buffered (все его реализации) — класс, позволяющий ускорить процесс чтения за счет буферизации источника данных
Зачем буфер: если мы читаем файл с жесткого диска, то каждая операция запроса и переноса байтов с самого диска в оперативку занимает много времени. Потому нашей программе лучше за одно обращение считать n-байтов, а не один. Это справедливо для сетевых запросов
Комбинация реализаций позволяет считать данные в нужном формате и с оптимальными настройками буферизации
Та же самая история справедлива и для отдачи байтов: OutputStream - Writer - Buffered
....
загадка: по какой таблице кодирования Reader/Writer сопоставит байты и символы? откуда он ее возьмет - зашита в Java, определена в Android, задается JVM...?
🔥5👍3❤1