Academy Minsk News & Announcements
515 subscribers
503 photos
29 videos
2.17K links
Самые интересные новости, видео с конференций из Android мира. Анонсы митапов, важных и полезных конференций от сообщества Android Academy Minsk.
Обсудить материал вы можете в чате: https://t.me/androidacademyminsk
Download Telegram
#news #auditing

Exploring Android11: Data Access Auditing

TL;DR
Data Access Auditing API - отличная возможность узнать какие части кода вашего приложения или библиотеки доступается к пользовательским данным.

В последних версиях Android большой упор делается на конфиденциальность пользовательских данных. Новая версия Android добавила новое API, который помогает разработчикам лучше понимать пользовательские данные, к которым они обращаются, - как из собственного кода, так и из сторонних зависимостей в рамках проекта.
👉 Чтобы добавить эту функцию, существующий класс AppOpsManager был расширен OnOpNotedCallback, который можно зарегистрировать в этом классе. Этот коллбэк будет запускаться всякий раз, когда к данным обращались, независимо от источника этого вызова 💪💪💪
👉 Т.к. проекты растут и становятся все больше, то становится сложно отслеживать к каким данным обращается ваше приложение. Кроме того, сторонние SDK могут получать доступ к пользовательским данным 😱😱
👉 С новым аудит API мы сможем узнать кто и каким образом обращается к частным данным. С OnOpNotedCallback из класса AppOpsManager можно получить обратный вызов при каждом доступе к данным. В рамках этого вызова мы можем затем обработать триггер по своему усмотрению
👉 Чтобы включить, нам нужно определить OnOpNotedCallback:
val opsCallback = object : AppOpsManager.OnOpNotedCallback() {
override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
myLogger.logAppOpNoted(syncNotedAppOp.op)
}
override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
myLogger.logAppOpNoted(syncNotedAppOp.op)
}
override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
myLogger.logAppOpNoted(asyncNotedAppOp.op)
}
}
👉 Давайте подробней рассмотрим эти методы:
1️⃣ onNoted - вызывается, когда операция была отмечена при синхронном вызове API
2️⃣ onSelfNoted - аналогично onNoted, за исключением того, что он вызывается, когда в собственном процессе приложений отмечена операция
3️⃣ onAsyncNoted - вызывается, когда отмечена операция, которая не может быть предоставлена ни onNoted, ни onSelfNoted
👉 Теперь регистрируем наш коллбэк в AppOpsManager:
val opsManager = getSystemService(AppOpsManager::class.java) as AppOpsManager
opsManager.setOnOpNotedCallback(mainExecutor, opsCallback)
👉 Теперь мы будем получать события всякий раз, когда к личным данным будет идти запрос в рамках нашего приложения
👉 Может случиться так, что мы будем получать много разных событий с разных частей нашего приложения. Возникает вопрос группировки этих данных. Для этого нам могут помочь Attribution Tags - эти теги могут применяться там, где мы получаем доступ к данным, которые затем передаются в функции OnOpNotedCallback
👉 Для этого можно использовать функцию createAttributionContext(). Например:
private lateinit var attributionContext: Context
override fun onCreate(savedInstanceState: Bundle?) {
attributionContext = createAttributionContext("searchNearbyShops")
}
fun getLocation() {
val locationManager = attributionContext.getSystemService(
LocationManager::class.java) as LocationManager
}
👉 И уже после мы можем этот идентификатор "searchNearbyShops" использовать в OnOpNotedCallback:
val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
myLogger.logAppOpNoted(
syncNotedAppOp.op,
syncNotedAppOp.attributionTag
)
}
override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
myLogger.logAppOpNoted(
syncNotedAppOp.op,
syncNotedAppOp.attributionTag
)
}
override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
myLogger.logAppOpNoted(
syncNotedAppOp.op,
syncNotedAppOp.attributionTag
)
}
}

❤️ Напомним про митап, где Вадим Высоцкий подробно разобрал это API и показал как его использовать. Митап можно посмотреть тут
#webinar #android11 #Hilt #onActivityResult #Paging

Уже через час ребята из Android Academy Global начнут рассказывать самые последние обновления, связанные с #Android11, а спикеры сегодня будут из Google, Revolut 🔥😎

1️⃣ Hilt: Dependency Injection
2️⃣ Jetpack Paging v3
3️⃣ Jetpack AppStart, ActivityResult

👉 Присоединяйтесь: https://www.youtube.com/watch?v=8hZLlXNZo_o
#news #rx #relay

RxRelay – это магия? Subject vs RxRelay

TL;DR
Единственное отличие RxRelay от Subject — это отсутствие двух методов onComplete и onError, чтобы разработчик не мог вызвать терминальный стейт.

Помните ли еще Rx? А использовали RxRelay? А может знаете в чем отличие Subject vs RxRelay? Если да, то можете проверить свои знания ниже, потому что автор как раз рассказывает про эти отличия.

Существует три мнения касательно различий:
1️⃣ Те, кто не понимают зачем RxRelay используется в их проекте, зачем он нужен и чем отличается от Subject
2️⃣ Те, кто думают, что RxRelay «проглатывает» ошибки или «после того, как произошла ошибка RxRelay, продолжит работать, а Subject — нет» (та самая магия)
3️⃣ Те, кто действительно знает, что такое RxRelay

Давайте проверим мнение 2️⃣ и посмотрим, в чем разница между RxRelay и Subject. Рассмотрим код, который при клике на кнопку мы пушим единицу в relay:
private val relay = PublishRelay.create<Int>()
private var isError: Boolean = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val disposable1 = relay
.map {
if (isError) {
isError = false
throw Exception()
} else {
isError = true
}
}.subscribe(
{
Log.d("test", "Цепочка с ошибкой: onNext")
},
{
Log.d("test", "Цепочка с ошибкой: onError")
}
)

val disposable2 = relay
.subscribe(
{
Log.d("test", "Цепочка без ошибки: onNext")
},
{
Log.d("test", "Цепочка без ошибки: onError")
}
)

btn.setOnClickListener {
relay.accept(1)
}
}

После трех раз подряд клика на кнопку в консоль будет распечатано следующее:
#первый раз клик
D/test: Цепочка с ошибкой: onNext
D/test: Цепочка без ошибки: onNext
#второй раз клик
D/test: Цепочка с ошибкой: onError
D/test: Цепочка без ошибки: onNext
#третий раз клик
D/test: Цепочка без ошибки: onNext

🕵️‍♂️ Давайте разберемся почему так происходит:
👉 При первом клике мы пушим в relay данные. Оба подписчика срабатывают
👉 При втором клике в цепочке у первого подписчика или disposable1 возникает ошибка, а второй - disposable2 работает нормально
👉 При третьем клике первый подписчик или disposable1 уже не срабатывает, так как он получил терминальное состояние onError 😱. Дальше будет работать только второй подписчик disposable2

⚠️ В итоге мы проверили, что цепочка на основе RxRelay не может работать после того, как возникла ошибка

Так в чем же отличие? 🧐
RxReplay - это Subject без методов onComplete и onError, даже исходный код классов почти одинаковый. Если мы вызовем на Subject эти методы, то он перестанет работать, так как получит терминальное состояние. Поэтому автор библиотеки убрал эти методы, потому что те разработчики, которые не знают об этом свойстве Subject могут случайно вызвать их 🤘
#news #hilt #migration

Migrating the Google I/O app to Hilt

TL;DR
Миграция с dagger android на Hilt упрощает и уменьшает количества кода

Рассказывать, что такое Hilt, наверное, уже ни к чему, потому как только ленивый не писал про эту библиотеку 😎 В данном посте мы освежим в памяти и посмотрим как можно мигрировать на Hilt в проекте, где раньше использовался dagger android 💪 Таким приложением будет Google I/O app iosched. Для более подробных инструкций по миграции можно посмотреть в руководстве.
🔥 Первая интересная цифра после миграция: 2к строчек кода были заменены на 500 😱
Давайте разберемся почему так ⬇️:
👉 Hilt поставляется с набором предопределенных компонентов, включая ApplicationComponent, ActivityComponent или FragmentComponent. Можно и свои создавать компоненты
👉 Внедрение зависимостей в Android - это некоторая сложность, потому что компоненты, такие как Activity, создаются платформой. dagger.android упростил этот процесс, предложив вызвать AndroidInjection.inject(this), и, расширив DaggerAppCompatActivity или DaggerFragment. Еще был модуль, который определял, какие subcomponents должен создать dagger.android, их область действия и все модули, включенные в них, используя @ContributesAndroidInjector для Activity и Fragments:
@ActivityScoped
@ContributesAndroidInjector(modules = [OnboardingModule::class])
internal abstract fun onboardingActivity(): OnboardingActivity
👉 Кроме того, каждый фрагмент имеет свой собственный subcomponent, также сгенерированный с помощью @ContributesAndroidInjector в своем собственном модуле:
@FragmentScoped
@ContributesAndroidInjector
internal abstract fun contributeOnboardingFragment(): OnboardingFragment
👉 С Hilt нужно просто удалить привязки @ContributesAndroidInjector и добавить аннотацию @AndroidEntryPoint ко всем компонентам Android:
@AndroidEntryPoint
class OnboardingFragment : Fragment() {...}
👉 Для других типов привязок мы по-прежнему используем модули для их определения, и они должны быть помечены @InstallIn:
@InstallIn(FragmentComponent::class)
@Module
internal class SessionViewPoolModule {...}
👉 Scoping работает с привычными предопределенными областями, такими как ActivityScoped, FragmentScoped, ServiceScoped и т.д.:
@FragmentScoped
@Provides
@Named("sessionViewPool")
fun providesSessionViewPool(): RecyclerView.RecycledViewPool = RecyclerView.RecycledViewPool()
👉 Hilt очень хорошо помогает в интеграции с Architecture Components. Он поддерживает внедрение ViewModels и WorkManager Workers 🔥 Для этого сначала мы создадим фабрику ViewModel для Fragment и Activity для получения ViewModel через ViewModelProviders:
class SessionDetailFragment {
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var sessionDetailViewModel: SessionDetailViewModel
override fun onCreateView(...) {
sessionDetailViewModel = viewModelProvider(viewModelFactory)
}
}
Далее с Hilt можно написать так:
private val viewModel: AgendaViewModel by viewModels()
👉 Далее до Hilt использовали multibinding для прокидывания зависимостей во ViewModel с помощью ViewModelKey:
@Binds
@IntoMap
@ViewModelKey(SessionDetailViewModel::class)
abstract fun bindSessionDetailFragmentViewModel(viewModel: SessionDetailViewModel): ViewModel
Теперь это можно заменить так:
class SessionDetailViewModel @ViewModelInject constructor(...) { … }
Это же прекрасно, не так ли?! ❤️❤️❤️
👉 Instrumented тестирование в Hilt происходит так:
@CustomTestApplication(MainTestApplication::class)
class CustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, CustomTestRunner_Application::class.java.name, context)
}
}
Нам нужно вернуть CustomTestRunner_Application, где тестовый Application класс определен в аннотации @CustomTestApplication.

Больше тестирования вы найдете в конце статьи 😉 Оставим на домашнее чтение 😜
#news #materialcomponents #updates

Material Components for Android 1.2.0 is now available

TL;DR
Добавили анимации перехода между Fragments, Activities, Views. Slider новый виджет, который можно использовать для выбора диапазона значений. Новый виджет ShapeableImageView понимает разные шейпы. И еще несколько обновлений.

Недавно вышла стабильная версия библиотеки Material Components for Android (MDC-Android) 1.2.0!

📚 Дополнительно стоит ознакомится с release notes

Давайте разберемся что нового в ней добавилось и какие нас ждут изменения:
С последней версии 1.1.0 добавили motion систему, компонент слайдера и многое другое. Все то, чтобы было добавлено в альфа-, бета- версиях теперь стало стабильно
Material motion:
Эта система включает в себя набор из четырех шаблонов перехода. Шаблоны перехода:
1️⃣ Преобразование контейнера
2️⃣ Общая ось
3️⃣ Исчезать хитро
4️⃣ Исчезать
Эти шаблоны построенные на основе как библиотеки AndroidX Transition так и Android Framework Transition. Их можно использовать для перехода между Fragments, Activities и Views. Преобразование контейнера между Fragments с использованием Jetpack Navigation выглядит так:
<!-- fragment_a.xml -->
<View
android:id="@+id/start_view"
android:transitionName="start_container" />
<!-- fragment_b.xml -->
<View
android:id="@+id/end_view"
android:transitionName="end_container" />
// FragmentB.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
sharedElementEnterTransition = MaterialContainerTransform()
}
// FragmentA.kt
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
exitTransition = Hold()
}
...
val directions = FragmentADirections.actionFragmentAToFragmentB()
val extras = FragmentNavigatorExtras(startView to "end_container")
findNavController().navigate(directions, extras)

Далее в статье можно найти как делаются остальные переходы 💪💪
Sliders позволяют пользователям делать выбор из диапазона значений. Они идеально подходят для настройки таких параметров, как громкость, яркость или применения фильтров изображения. Новое обновление позволяет использовать sliders с виджетами Slider и RangeSlider. Они похожи на SeekBar, но имеют дополнительные функции и поддерживают Material Theming. Выглядит это так:
<!-- In layout -->
<com.google.android.material.slider.Slider
android:id=”@+id/slider”
...
android:valueFrom="0.0"
android:valueTo="100.0"
android:stepSize="10.0" />
...
slider.addOnChangeListener { slider, value, fromUser -> ... }
...
val values = rangeSlider.values
ShapeableImageView:
Этот новый виджет является расширением AppCompatImageView, который понимает shape theming. Он поддерживает различные размеры углов, срезанные углы, а также разную ширину и цвет обводки:
<!-- res/values/shape.xml -->
<style name=”ShapeAppearanceOverlay.App.CornerSize50Percent”
parent=””>
<item name=”cornerSize”>50%</item>
</style>
<!-- res/values/styles.xml -->
<style name=”Widget.App.ShapeableImageView”
parent=”Widget.MaterialComponents.ShapeableImageView”>
<item name=”shapeAppearance”>
?attr/shapeAppearanceSmallComponent
</item>
<item name=”shapeAppearanceOverlay”>
@style/ShapeAppearanceOverlay.App.CornerSize50Percent
</item>
<item name=”strokeWidth”>1dp</item>
<item name=”strokeColor”>?attr/colorPrimary</item>
</style>
<!-- In layout -->
<com.google.android.material.imageview.ShapeableImageView
...
style=”@style/Widget.App.ShapeableImageView”
app:srcCompat=”@drawable/image” />
🔥🔥 Поддержка materialThemeOverlay во всех компонентах. В отличие от android:theme, его можно использовать в стилях компонентов по умолчанию
MaterialButton теперь не игнорирует теперь передаваемые drawables в android:background. MaterialShapeDrawable по-прежнему будет использоваться по умолчанию
#news #themes #styles

Using Theme Overlays

TL;DR
Theme overlay мощная вещь, которая позволяет настраивать внешнее представление View как в runtime, так и в XML.

Автор продолжает серию статей про темы и стили. На очереди у нас разговор про theme overlay. Если не ознакомились с предыдущими статьями, то стоит обновить знания в подробных постах нашего канала тут и тут.
Theme overlays используются для переопределения значений из вашей темы. В статье автор рассмотрел три случая, когда они могут быть полезными:
1️⃣ Theme overlays в layouts:
👉 В какой-то момент, появилась фича в приложении, на экране которой Toolbar имеет совсем другой цвет. Например, со светлой тематики на темно-синию.
👉 Можно было бы явно установить цвет фона для Toolbar, но тогда нужно будет обновить цвет текста, цвет значка навигации, цвета пунктов меню действий и т.д.
👉 Вместо этого можно воспользоваться theme overlay, в которой переопределить colorPrimary и colorOnPrimary:
<style name="ThemeOverlay.Monzo.Toolbar.Loans" parent="">
<item name="colorPrimary">@color/navy</item>
<item name="colorOnPrimary">@color/white</item>
</style>
И позже установить в Toolbar:
<androidx.appcompat.widget.Toolbar
...
android:theme="@style/ThemeOverlay.Monzo.Toolbar.LoansNavy" />

2️⃣ Theme overlays в дефолтных стилях:
👉 android:theme не работает если указать в стиле по умолчанию 😭 View inflater применяет theme overlay, проверяя, указана ли android:theme, а затем использует ContextThemeWrapper для обертывания Context с overlay, прежде чем использовать этот обернутый контекст для создания экземпляра View
👉 Стили по умолчанию считываются после создания экземпляра View, а атрибуты стиля по умолчанию определяются внутри конструктора View с двумя аргументами, что означает, что View inflater не может проверить стиль представления по умолчанию для атрибута android:theme 😉
👉 Eсть один атрибут materialThemeOverlay, который поддерживается множеством компонентов из библиотеки Material Design Components, и он работает в стилях по умолчанию 🤘🔥
👉 Одним из вариантов использования - обновление цветов, используемых элементами выбора: радио-кнопки, переключатели, чекбоксы. Например, определяем theme overlay, который будет контролировать основной цвет:
<style name="ThemeOverlay.Monzo.CompoundButton" parent="">
<item name="colorSecondary">?attr/mdsColorBlueTint</item>
</style>
Далее можно использовать materialThemeOverlay для ссылки на этот ресурс в наших стилях по умолчанию:
<style name="Widget.Monzo.CompoundButton.CheckBox" parent="Widget.MaterialComponents.CompoundButton.CheckBox">
<item name="android:textColor">?attr/mdsColorPrimaryContent</item>
<item name="materialThemeOverlay">@style/ThemeOverlay.Monzo.CompoundButton</item>
</style>

⚠️ Мы могли бы обновить colorSecondary в нашей теме, но это будет небезопасно! Лучше использовать materialThemeOverlay непосредственно в стиле по умолчанию для компонентов, которые мы хотим изменить

3️⃣ Theme overlays в коде:
👉 В приложении у автора кнопка может выглядеть со слишком закругленными краями
<shape android:shape="rectangle">
<corners android:radius="?attr/monzoButtonCornerRadius" />
...
👉 Можно использовать theme overlay с android:theme, однако автор решил управлять этим программно. Добавили настраиваемый атрибут представления isPill и программно применили наложение темы при создании представления:
fun wrapContext(context: Context, attrs: AttributeSet): Context {
val materialContext = MaterialThemeOverlay.wrap(context, attrs, DEF_STYLE_ATTR, 0)
val isPill = typedArray.getBoolean(R.styleable.MonzoButton_isPill, false)
typedArray.recycle()
return if (isPill) {
return ContextThemeWrapper(materialContext, R.style.ThemeOverlay_Monzo_MonzoButton_Pill)
} else {
materialContext
}
}
По коду должно быть понятно, что делается и зачем.
#feedback #webinar

Спасибо, что присутствовали на онлайн митапе 04.08.2020!

Ребята рассмотрели:
1️⃣ Hilt: Dependency Injection
2️⃣ Jetpack Paging v3
3️⃣ Jetpack AppStart, ActivityResult

🧾 Мы надеемся, что вы не забыли еще свои чувства с нашей встречи. Будем благодарны, если вы заполните нашу фидбек-форму🔥
Просим вас оставить ваши фидбеки, и тем самым вы поможете нам стать лучше.😎

Спешим поделиться с вами важными ссылками:
📗 Презентации вы можете найти здесь
📸 Наш YouTube-канал, где вы всегда сможете найти нужную вам трансляцию

⚡️Наша следующая онлайн встреча состоится 18.08. Очень скоро мы поделимся с вами более подробной информацией. Пожалуйста, не переключайтесь!
#announcements #android11 #camerax #navigation

Ребята из Android Academy Global не останавливаются на достигнутом, и организуют следующий онлайн митап по #Android11Updates. Планируйте свой вечер 18.08.2020 Android 11 #4 - Android Navigation Component and Camera X 😎
В этот раз обсудим:

1️⃣ (ENG) The Art of Jetpack Navigation Component by Pavel Strelchenko, HH.ru 🔥
Два года назад на Google I/O было представлено новое решение для навигации в Android-приложениях. Есть очень много материалов и примеров как можно реализовать навигацию в рамках нового подхода, но о проблемах перевода большого приложения на Navigation Component практически нет. 
В своём докладе Павел расскажет о кейсах, с которыми может встретиться Android-разработчик, желающий в бою опробовать Navigation Component 😉

2️⃣ (ENG) CameraX by Pavel Sliusar, Fitbit 💪
С камерой на Андроиде работать не сказать, чтобы легко. Сложный жизненный цикл, жёсткий контракт, долгая инициализация, потоки... 
CameraX спешит на помощь! Упрощённый, унифицированный и предсказуемый API. Первая альфа-версия была представлена в прошлом году. Посмотрим, какой прогресс библиотека сделала за год, с альфы до беты. Разберёмся, как анализировать изображение с камеры телефона для машинного анализа

Регистрация тут.

(Ссылка на YouTube-трансляцию придёт за несколько минут до начала)
#news #video #updates #playconsole

Welcome to your new Play Console

Какие изменения были сделаны в Play Console. Давайте узнаем из видео. А пока краткий обзор:
👉 Дизайн консоли поменялся на Material: стало удобно ориентироваться, все интуитивно понятно и удобно
👉 Теперь консоль легко скейлится и выглядит неплохо как на телефоне, так и на больших экранах
👉 Поменяли навигацию. Теперь нажимая на hamburger menu, вы увидеть сгруппированные списки по функциональности: Releases, Grow, Qaulity, Android Vitals и т.д.
👉 Изменилось представление Releases Overview, где можно найти информацию по последним релизам, когда были сделаны последний апдейты, в каком треке и т.д.
👉 Был добавлен mail inbox, куда будут приходить самые важные сообщения о релизах, о различных изменениях, что нужно поправить и т.д. Стоит с этим поиграться
👉 Для того, чтобы быстро познакомится с каждой из фич, были добавлены обучающие видео на главную страницу, которые потому разобраться что к чему
👉 Проработали дизайн Ratings & Reviews, где можно быстро получить информацию про рейтинг приложения и узнать последние ревью
👉 Добавили полезный инструмент Android Performance Tuner, который помогает измерять frame rate. Будет полезно для игр 💪
👉 Появилась возможность загружать расшифровку для native крашей, чтобы можно было сразу читать ошибки в play console и понимать какое место вызвало сбой

❤️ Команда разработчиков очень ждет ваших фидбэков, которые можно пошарить с ними прям из консоли 📗
#news #resources #raw

Assets or Resource Raw folder of Android?

TL;DR
Необходимо учитывать ограничения и возможности папок res и assets, когда дело касается куда складывать файлы-ресурсы

Скорее всего вы уже сталкивались с проблемой где хранить файлы ресурсы такие как JSON, mp3, pdf и т.д. В Android фреймворке существует два способа: assets или res/raw папки. Оба они кажутся одинаковыми, поскольку они могут читать файл и генерировать InputStream:
1️⃣ assets.open(assetPathFilename)
2️⃣ resources.openRawResource(resourceRawFilename)

Встает законный вопрос: а когда куда что складывать?

Автор предлагает рассмотреть следующие вопросы, которые помогут при принятии решения:
👉 Название файлов может быть разным:
1️⃣ В assets папке название файлов может быть любим, там нет правил по названию
2️⃣ В resource папке название файлов может содержать только буквы a – z нижнего регистра, 0–9 или знак подчеркивания
👉 Хранить в подкаталогах:
1️⃣ В assets папке можно создавать подкаталоги и хранить там файлы
2️⃣ В resource папке можно хранить файлы только в этой папке
👉 Список имен файлов во время выполнения:
1️⃣ Усли захотим узнать какие папки, файлы лежат в assets, то это легко можно сделать:
assets.list(FOLDER_NAME)?.forEach { file ->
println(file)
}
2️⃣ Для resource папки такой нет возможности
👉 Проверка во времени компиляции:
1️⃣ Можно получить InputStream для assets папки assets.open("filename"), но если файла такого нет, то нужно уметь обрабатывать ошибку
2️⃣ В resource папке можно сделать так resources.openRawResource(R.raw.filename), т.е. тут явно мы указываем название нашего файла, который должен лежать в этой папке, иначе не будет сгенерирован id в R файле
👉 Конфигурация:
1️⃣ Если вы хотите, чтобы для другой конфигурации (язык устройства, Android API, ориентация экрана) читались разные файлы, вам необходимо вручную определить конфигурацию и прочитать соответствующий файл
2️⃣ Папкам в res можно приcваивать имена, которые будут учитывать разные конфигурационные вещи. Например, raw-29 - это значит файлы, которые лежат в этой папки будут применяться, если версия Android >= 29 на телефоне
👉 Имя файла, доступное из XML:
1️⃣ Нет простого способа получить XML-файл (например, AndroidManifest.xml), указывающий на файл в папке с ресурсами
2️⃣ Мы можем легко получить доступ к файлу в res/raw с помощью @raw/filename
​​#announcements #android11 #camerax #navigation

Уже завтра, 18.08.2020 😎
ребята из Android Academy Global организуют следующий онлайн митап #Android11Updates. В программе Android 11 #4 - Android Navigation Component and Camera X 📸

Ребята рассмотрят:

1️⃣ (ENG) The Art of Jetpack Navigation Component by Pavel Strelchenko, HH.ru 🔥
2️⃣ (ENG) CameraX by Pavel Sliusar, Fitbit 💪

Регистрация тут.

(Ссылка на YouTube-трансляцию придёт за несколько минут до начала) 📺
#news #kotlin #updates

Kotlin 1.4 Released

Увидел свет новый релиз Kotlin 1.4. В этом релизе было уделено особое внимание улучшению производительности и качества Kotlin и связанных с ним инструментов. Давайте пройдемся по основным фичам в новой версии:
👉 Было исправлено более 60 проблем с производительностью, включая те, что вызывали утечки памяти и зависание IDE
👉 Теперь подсветка работает от 1.5 до 4 раз быстрее 💪
👉 Автодополнение срабатывает гораздо быстрее, чего стоит тольки цифр: количество случаев, в которых автодополнение занимает больше 500 мс, сократилось почти вдвое!
👉 Был реализован и показан в предыдущих RC версиях отладчик корутин
👉 Команда долго и упорно разрабатывала новый Kotlin компилятор. Но, новые части внедряются постепенно. Вот что дошло:
1️⃣ По умолчанию включен новый, более мощный алгоритм вывода типов
2️⃣ Новые IR-бэкенды для JVM и JS доступны в альфа-режиме

👉 Теперь давайте про возможности языка, где тоже много нового:
🔥 Преобразования SAM для интерфейсов Kotlin
🔥 Смешанные именованные и позиционные аргументы
🔥 Использование break и continue внутри циклов when
🔥 Завершающая запятая 😉

👉 Не обошлось и без обновлений в стандартной библиотеке Kotlin, где добавили новые операторы, улучшили делегирования свойств и многое другое
👉 Были сделаны дополнения и изменения для Kotlin/JVM, Kotlin/JS, Kotlin/Native
👉 А также улучшили Kotlin Multiplatform, но об этом лучше читать уже подробнее в доках
#news #security #testing

Testing Jetpack Security with Robolectric

TL;DR
Чтобы протестировать EncryptedSharedPreferences необходимо будет создать свой AndroidKeyStore провайдер, фейковый KeyStoreSpi, а также фейковый KeyGenerator

Автор рассказывает случай, когда он начала тестировать Jetpack SecurityEncryptedSharedPreferences с помощью Robolectric, но вскоре обнаружил исключение java.security.KeyStoreException: AndroidKeyStore not found 🙀🙀
В статье автор рассмотрел почему это не работает и как можно обойти проблемы.

Для начала давайте напишем код, который будет создавать такие preferences и шифровать данные:
👉 Для начала напомним, что такое EncryptedSharedPreferences - это реализация SharedPreferences, в которой ключи и значения зашифрованы 💪 Подключить ее к проекту можно так:
dependencies {
implementation("androidx.security:security-crypto:1.1.0-alpha01")
}
👉 Чтобы создать EncryptedSharedPreferences, необходимо сначала создать MasterKey:
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()

⚠️ MasterKey также можно настроить для обеспечения аппаратной поддержки и аутентификации пользователя
👉 Далее уже можно создать EncryptedSharedPreferences:
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"preference file name",
masterKey,
PrefKeyEncryptionScheme.AES256_SIV,
PrefValueEncryptionScheme.AES256_GCM
)
👉 И позже можно использовать это так:
sharedPreferences.edit().apply {
putString("key", "value")
}.apply()
sharedPreferences.getString("key", "default") // returns "value"

Теперь можно уже начинать тестировать наш код.
👉 Автор написал тест, с которым можно ознакомится в статье
👉 При запуске мы получим ошибку:
java.security.KeyStoreException: AndroidKeyStore not found
at java.security.KeyStore.getInstance
👉 Если посмотреть внимательно на стэк трейс, то там нету класса AndroidKeyStore, а просто вызов java.security.KeyStore.getInstance, который подводит нас к первому препятствию с Robolectric:

🚨 Robolectric не поддерживает shadows для классов из пакета "java."

👉 Java включает в себя встроенных поставщиков, которые реализуют базовый набор служб безопасности, но также позволяют устанавливать настраиваемые поставщики. AndroidKeyStore предоставляется одним из таких поставщиков, что означает, что мы можем добавить нашу реализацию AndroidKeyStore
👉 Для этого мы создадим класс, который расширяет java.security.Provider, используя имя поставщика AndroidKeyStore:
val provider = object : Provider("AndroidKeyStore", 1.0, "") {
init {
put("KeyStore.AndroidKeyStore",
FakeKeyStore::class.java.name)
}
}
Security.addProvider(provider)
👉 Чтобы реализовать наш FakeKeyStore, класс должен расширять абстрактный класс KeyStoreSpi, где SPI означает интерфейс поставщика услуг:
class FakeKeyStore : KeyStoreSpi() {
private val wrapped =
KeyStore.getInstance(KeyStore.getDefaultType())

override fun engineIsKeyEntry(alias: String?) =
wrapped.isKeyEntry(alias)
override fun engineIsCertificateEntry(alias: String?) =
wrapped.isCertificateEntry(alias)
...
override fun engineGetKey(alias: String?, password: CharArray?)=
wrapped.getKey(alias, password)
}
Если мы запустим, то получим опять ошибку 😱:
java.security.NoSuchAlgorithmException: no such algorithm: AES for provider AndroidKeyStore
at javax.crypto.KeyGenerator.getInstance
👉 Как и в случае с KeyStore, нам необходимо убедиться, что наш поставщик также содержит KeyGenerator, предоставив реализацию KeyGeneratorSpi:
class FakeAesKeyGenerator : KeyGeneratorSpi() {
private val wrapped = KeyGenerator.getInstance("AES")
...
override fun engineGenerateKey(): SecretKey =
wrapped.generateKey()
}
👉 Осталось только теперь добавить:
put("KeyGenerator.AES", FakeAesKeyGenerator::class.java.name)
#webinar #android11 #camerax #navigation

Уже через час ребята из Android Academy Global начнут рассказывать самые последние обновления, связанные с #Android11🔥😎 На очереди у нас сегодня следующее:

1️⃣ The Art of Jetpack Navigation Component
2️⃣ CameraX

👉 Присоединяйтесь ➡️ https://www.youtube.com/watch?v=RZ6eM_6QPjM
#news #review

Google Play In-App Review API: integration and experience

TL;DR
В новой версии Google Play 1.8.0 была добавлено API для показа нативного диалога Review. Данный диалог может помочь увеличить количество ревью от ваших пользователей на Google Play.

Автор рассказал в статье как он применил новое API для рейтинга в приложении и после этого его рейтинг сразу бустанулся во много раз.
В библиотеке Google Play версии 1.8.0 появилась долгожданная функция: встроенный диалог review в приложении. Моменты, который стоит учесть:
👉 Встает вопрос того как часто и когда стоит показывать Review диалог?! 🤔 В документации написано следующее:
1️⃣ Запускайте процесс Review в приложении после того, как пользователь ознакомится с вашим приложением или игрой в достаточной степени, чтобы дать полезный отзыв.
2️⃣ Не запрашивайте у пользователя чрезмерный количество раз Review. Такой подход помогает минимизировать разочарование пользователей и ограничить использование API.
3️⃣ Ваше приложение не должно задавать пользователю какие-либо вопросы до или во время представления кнопки или карточки оценки, включая вопросы об их мнении
👉 Имеет смысл просить Review после того, как вы принесете пользу пользователю. В это время у пользователя будет хорошее настроение, и отзыв, скорее всего, будет более положительным
👉 Просить обзор после открытия приложения - плохая идея, потому что это приведет к раздражению пользователей, которые либо пропустят его, либо оставят вам плохой отзыв 😢
👉 В API продуман анти-спам, чтобы не показывать такие окна пользователям, которые уже писали review на ваше приложение
👉 Однако частота отображения диалогового окна не задокументирована для Google Play API. Поэтому автор поинтересовался на StackOverflow и получил ответ от гуглера:
👉 Похоже, что показывая диалоговое окно чаще трех раз в год может раздражать пользователя. Поэтому вам следует добавить больше настраиваемой логики, чтобы избежать чрезмерного спама 🔥
👉 Как же добавить такой диалог в приложение. Сначало надо добавить зависимость:
implementation 'com.google.android.play:core:1.8.0'

👉 Теперь можно интегрировать в код:
lateinit var manager: ReviewManager
var reviewInfo: ReviewInfo? = null

// Call this method asap, for example in onCreate()
private val initReviews() {
manager = ReviewManagerFactory.create(this)
manager.requestReviewFlow().addOnCompleteListener { request ->
if (request.isSuccessful) {
reviewInfo = request.result
} else {
// Log error
}
}
}

// Call this when you want to show the dialog
private fun askForReview() {
if (reviewInfo != null) {
manager.launchReviewFlow(this, reviewInfo).addOnFailureListener {
// Log error and continue with the flow
}.addOnCompleteListener { _ ->
// Log success and continue with the flow
}
}
}

👉 Тестировать можно и нужно данную фичу. Тестирование можно проводить на тестовых треках в Google Play - внутреннее тестирование, альфа или бета:
1️⃣ Опубликуйте там свое приложение и убедитесь, что ваш текущий аккаунт Google еще не проверял приложение
2️⃣ В тестовых треках нет квот, поэтому вы всегда будете видеть диалог
3️⃣ Если вы отправите оценку, она будет добавлена не как обзор, а как отзыв о тестировании

🚨 Данное API помогло увеличить в 5 раз Review приложения, куда автор интегрировал данное API. И большинство из них положительные, поэтому общий рейтинг нашего приложения растет
#feedback #webinar

Спасибо, что присутствовали на онлайн митапе 18.08.2020!

Ребята рассмотрели:
1️⃣ The Art of Jetpack Navigation Component
2️⃣ CameraX

🧾 Мы надеемся, что вы не забыли еще свои чувства с нашей встречи. Будем благодарны, если вы заполните нашу фидбек-форму 🔥
Просим вас оставить ваши фидбеки, и тем самым вы поможете нам стать лучше.😎

Спешим поделиться с вами важными ссылками:
📗 Презентации вы можете найти здесь
📸 Наш YouTube-канал, где вы всегда сможете найти нужную вам трансляцию
📝 Ознакомится с примерами из презентаций можно на нашем github
#news #libraries #projects

The 25 Best Android Libraries and Projects of 2020 — Summer Edition

Автор собрал неплохую подборку библиотек, который могут вам помочь в изучении Android, разработки в целом и просто ознакомиться с популярными подходами. Давайте узнаем какие же библиотеки для ознакомления подготовил нам автор:
1️⃣ Pokedex
Данный проект показывает modern tech stack с MVVM на борту и Repository паттерн. Проект написан на Kotlin, использует Coroutines + Flow для асинхронных задач и стандартные сетевые библиотеки, OkHttp и Retrofit.
Собственно, можно сказать, что никакой разницы между этим и другими подобными проектами нет. Однако есть одно важное отличие. Он использует недавно представленный Dagger Hilt в качестве DI. Он имеет неплохую документацию и выпущен под лицензией Apache 2.0
2️⃣ Kotlin Coroutines — Use Cases on Android
Этот проект поможет вам освоить Coroutines с самого начала. Здесь вы сможете быстро найти и поиграть с различными реализациями Coroutine для Android
3️⃣ AnimatedBottomBar
Это проект, который показывает какие клевые анимации можно сделать с BottomBar виджетом. У библиотеки исчерпывающая документация с множеством примеров использования. Это версия 1.0. + И поддерживает API 16+
4️⃣ Motion Toast
Библиотека предлагает пять типов тостов с возможностью настройки:
👉 Продолжительность
👉 Тема (светлая/темная)
👉 Стили (тосты с движением или цветным движением)
Документация хороша и полна примеров. Использование простое. Он поддерживает API 21+ и выпущен под лицензией MIT
5️⃣ Cycler
Еще одна классная библиотека от Square Engineering. В документации говорится, что он «позволяет легко и кратко декларативно настроить Android RecyclerView». Библиотека находится в версии 0.1.4, поэтому я бы не рекомендовал ее для производственного кода. Но все же это действительно хороший подход для реализации адаптеров и управления RecyclerViews. Документация исчерпывающая. Проект содержит демонстрационное приложение и выпущен под лицензией Apache 2.0
6️⃣ Zoom Recycler Layout
Это классная библиотека анимации масштабирования для элементов RecyclerView, написанных на Kotlin. Фактически, это один класс, который расширяет LinearLayoutManager и переопределяет две функции: scrollVerticalBy() и scrollHorizontallyBy(). Тем не менее, вы можете подсмотреть как это сделано и создать что-то крутое свое. Проект находиться под лицензией Apache 2.0 и поддерживает API 11+
7️⃣ Accompanist
Это группа библиотек, которая содержит некоторые утилиты, которые Chris Banes создал и копировал для проектов, использующих Jetpack Compose. Имеет исчерпывающую документацию и выпущен под лицензией Apache 2.0
8️⃣ JetpackComposeCalculator
Отличный пример того, как можно использовать Accompanist. Автор библиотеки клонировал пользовательский интерфейс калькулятора Android 10 с помощью Jetpack Compose 🔥🔥🔥🔥🔥🔥 Вы можете рассматривать этот проект как способ изучения Jetpack Compose на основе красивых примеров
9️⃣ ComposeClock
На основе Jetpack Compose создал крутые часы
🔟 Compose Academy
Это бесплатный ресурс, который предоставляет фрагменты и практические примеры использования Jetpack Compose. Joe разделил документацию на восемь основных разделов:
👉 Animation
👉 Core
👉 Foundation
👉 Graphics
👉 Layout
👉 Material
👉 Resource
👉 Test

🚨 Еще осталось рассмотреть 15 библиотек, который заслуживают внимания ❤️ Так что не поленитесь прочитать статью и глянуть какие библиотеки мы не рассмотрели в нашем посте 🤗
#news #shrinking #r8

Shrinking Your App with R8

TL;DR
R8 отличный тул для уменьшения размера вашего приложения, что непременно сказывается на установках. Добавив строчку minifyEnabled true можно его включить. Но какие подводные камни можно встретить, можно ознакомится в статье ⬇️

Есть интересная тенденция - чем меньше размер вашего приложения, тем охотнее ее установят пользователи. Т.е. существует прямая корреляция между размером и установками. Поэтому Google усиленно работает над компилятором R8. Автор сделал обзор функций, доступных в R8.

👉 Сначала давайте напомним основные функции R8:
1️⃣ Tree shaking: использование статического анализа кода для поиска и удаления недостижимого кода и неустановленных типов.
2️⃣ Optimization: оптимизация кода по размеру путем удаления мертвого кода, неиспользуемых аргументов
3️⃣ Identifier renaming a.k.a. obfuscation: использование коротких имен и сжатие пространства имен пакетов
4️⃣ Reducing debug information: сжатие информации о номерах строк

👉 Когда вы используете одну из этих сторонних библиотек, часто в вашем приложении используется лишь очень небольшая часть каждой отдельной библиотеки. Без сжатия весь код библиотеки сохраняется в вашем приложении. Поэтому нам и нужен R8 💪
👉 Давайте теперь включим R8 в нашем приложении. Для этого добавим код в build.gradle:
android {
buildTypes {
release {
minifyEnabled true
}
}
}
👉 Если говорить о цифрах, то прошлогоднее приложение Google I/O занимало 18,55 МБ со 150 220 методами и 3 файлами dex до сжатия. После сжатия приложение уменьшилось до 6,45 МБ с помощью 45 831 метода и 1 файла dex. R8 сэкономил 65% в размере dex 😱😱
👉 Давайте рассмотрим просто алгоритм сжатия. Для этого рассмотрим простую программку:
class JavaHelloWorld {
private void unused() {
System.out.println("Unused");
}
private static void greeting() {
System.out.println("Hello, world!");
}
public static void main(String[] args) {
greeting();
}
}
👉 Входной точкой в программу является static void main метод, который мы укажем для сохранения:
-keep class com.example.JavaHelloWorld {
public static void main(java.lang.String[]);
}
👉 Таким образом алгоритм R8 работает следующим образом:
Он отслеживает весь доступный код из хорошо известных точек входа в программу - эти точки входа определяются правилами хранения R8
В примере R8 переходит от основного метода к методу greeting()
Данный метод вызывает среду выполнения, поэтому на этом трассировка останавливается
После завершения трассировки R8 использует оптимизацию, называемую tree shaking, для удаления неиспользуемого кода
Затем R8 переименовывает идентификаторы в более короткие имена, которые занимают меньше места в файлах DEX
Таким образом получаем:
class JavaHelloWorld {
private static void a() {
System.out.println("Hello, world!");
}
public static void main(String[] args) {
a();
}
}
Наконец, применяется оптимизация кода и получаем следующее:
class com.example.JavaHelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
👉 А теперь давайте рассмотрим такой пример:
class Person(val name: String)
fun printJson() {
val gson = Gson()
val person = Person("Søren Gjesse")
println(gson.toJson(person))
}
После сжатия кода и запуска программы мы увидим пустой объект JSON{} 😱😱 Почему так?!
Это связано с тем, что R8 видит поле name в Person, но никто его никогда не читает, поэтому R8 удаляет его
👉 Чтобы пофиксить надо добавить правило в файл proguard-rules.pro:
-keep class com.example.myapplication.Person {
public java.lang.String name;
}
Это правило говорит R8 не трогать поля name в классе Person. После запуска получим JSON {«name»: «Søren Gjesse»}
👉 Нужно не забыть добавить в build.gradle файл:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles 'proguard-rules.pro'
}
}
}
#news #git #practices

7 Git Best Practices to Start Using in Your Next Commit

TL;DR
Набор практик при работе с git от автора. Каждая из них может быть дополнена по желанию

Автор рассмотрел набор практик при работе с git. Так как инструмент git получил очень большое распространение в компаниях, то стоит понимать его возможности. Автор привел 7 собственных практик, которых он придерживается при использовании git:
1️⃣ Не делайте push ваших изменений сразу в master, а стоит создать новую бранч и там делать свои изменения
Тут наверное стоит сказать, что существует и альтернативное мнение: trunk based development. Данный совет применим способствует развитию другой практики под названием Code Review. Какие команды тут могут понадобиться:
👉 Чтобы создать ветку, запустите git branch -b <branch_name>
👉 Команда git branch -a перечисляет все доступные ветки, и вы можете перемещаться между ними с помощью git checkout <branch_name>

2️⃣ Необходимо правильно настроить автора для коммита
Вы должны правильно указать свое имя и адрес электронной почты, чтобы потом можно было понять кто какой коммит сделал. Команда git blame <file_name> может вам рассказать в этом случае следюущее:
👉 Последний коммит, который изменил данный файл
👉 Автор коммита
👉 Отметка времени фиксации
Данная команда вам поможет быстрее понять в чем проблема и разобраться в деталях

3️⃣ Напишите развернутое и содержательное коммит сообщение
Ваше будущее и сотрудники вашей команды будут вам очень благодарны, если вы будете следовать правилу написанию поясняющего сообщения с деталями реализации для коммита. В последствии с помощью команды git log вы можете перемещаться по истории кодовой базы и тем самым видеть что какой коммит сделал.
👉 Сообщения в стиле «Исправление ошибок» или «Рефакторинг аутентификации» никакой информативности не несет и такие логи просто только засоряют историю коммитов.
👉 Хороший пример статьи, где можно почитать мысли о том как писать сообщение git

4️⃣ Стоит делать коммит гранулярным
В разработке есть принцип Single Responsibility. Почему бы его не применить на создание коммитов?! 🤔 Вы должны зафиксировать наименьшее количество строк, которые имеют смысл вместе. Данный подход помогает при исследовании кодовой базы в будущем через git log или git blame

5️⃣ Избегайте переписывания истории master
Один из фокусов, которые выполняет git, - это возможность переписывать историю логов. Вы можете сделать это разными способами, но чаще всего через git rebase -i. С помощью этой команды можно, например, изменить порядок коммитов, удалить коммит, смерджить два или более коммитов или отредактировать
👉 Однако использование git rebase в ветвях, таких как master, может вызвать множество конфликтов для других участников, что тратит много времени и часов на отладку, когда повторная установка выполняется часто
👉 Стоит помнить, что когда вы переписываете историю, вам нужно потом сделать git push --force. Что также может привести к проблемам, поэтому лучше всего такое делать только в своих ветках

6️⃣ Обновляйте свою рабочую ветку часто
Крайне важно всегда обновлять вашу ветку с основной веткой, например dev ветка или может это master. Процесс интеграции необходимо, чтобы работал постоянно. Это поможет предотвратить ошибки, переделки и утомительную работу по разрешению конфликтов. Вы можете легко сделать это, выполнив следующие команды:
👉 git checkout <ветка_вверху>
👉 git pull
👉 git checkout -
👉 git rebase <upstream_branch>

⚠️ git fetch --prune поможет вам обновить ветки, не переходя на нее

7️⃣ Знайте инструмент. Не бойся его использовать
git - мощный инструмент, есть масса команд. Вы можете изучить новые приемы, прочитав документацию git. Также можно воспользоваться командой git help -a
#news #layouts

Writing Performant Layouts

TL;DR
Необходимо помнить о 16 мс при проектировании layout. Есть набор инструментов, которые помогут выяснить, где проблема: Layout Inspector, Lint, Systrace, Android Profiler. Помните также о проблеме double taxation.

В текущих реалиях сложность приложений растет, а соответственно растет и ответственность разработчиков перед пользователями. Ведь вам не хочется читать отзывы и видеть: «это приложение не работает» или «оно слишком медленно работает на моем устройстве» и т.д. Поэтому стоит знать свои инструменты разработчика. В данной статье автор рассмотрел что нужно помнить и какие вещи могут нам помочь при разработке:
👉 Частота обновления большинства устройств Android составляет 60 кадров в секунду, поэтому у нас есть только 16 миллисекунд для рендеринга одного кадра. И первое что приходит на ум - это layouting and measure. Ведь он может быть дорогим: для простых layouts может хватить одного прохода, а для каких-то и больше одного. Помним, что этот процесс даже применяется во всем детям вашего layout

⚠️ Необходимость выполнить более одной итерации layout-and-measure называется double taxation

👉 Давайте кратко рассмотрим некоторые сценарии, которые приводят к double taxation:
1️⃣ LinearLayout с layout_weight требует, чтобы каждый дочерний элемент был измерен дважды
2️⃣ RelativeLayout всегда занимает не менее 2 проходов
Особенно вложенные layouts могут повлиять на перформанс RecyclerView

👉 Как же тогда можно диагностировать, что проблемы существуют и их решить? Для этого есть набор готовых инструментов в арсенале android разработчика:
1️⃣ Layout Inspector - обеспечивает визуальное представление иерархии layout в дереве компонентов, которое можно использовать для определения глубины конкретного layout
2️⃣ Lint - одержит множество полезных правил, которые могут выявить проблемы с layout, а также предоставить полезные предложения по их исправлению
3️⃣ Systrace - помогает увидеть все процессы, запущенные в приложении, в виде графиков
4️⃣ Android Profiler - один из лучших инструментов, который поможет вам собрать много информации о том, что происходит в вашем приложении за кулисами

👉 Стоит подумать о возможности переиспользования layouts.
Использование тега <include> - очень распространенная практика. Также стоит не забывать о тэге <merge>, который используется в определенных случаях тоже. Автор рассмотрел пример использования этих тэгов и когда какой лучше всего использовать.
👉 Выберите более дешевый layout
Иногда double taxation и вложенность становятся побочным эффектом родительского layout, который вы используете. Автор приводит свои мысли какие layout в каком случае стоит рассмотреть в первую очередь:
Сложность → ConstraintLayout
Размещение элементов по вертикали/горизонтали → LinearLayout
Размещение видов друг над другом для создания наложения → FrameLayout
Избегайте использования RelativeLayout

👉 Отложенное создание view
Иногда некоторый layout может быть спрятан от глаз пользователя и только на основании какого-то условия он может быть показан. В этом случае нет смысла создавать данный layout, потому как он может быть показан, а может и нет. Чтобы не тратить время в пустую, лучше в runtime это делать. Для этого существует ViewStub.
ViewStub - это облегченное представление без измерений, которое ничего не рисует и не участвует в layout. Это дешево и полезно :)
Использовать ViewStub просто: вы можете просто заменить тег <include> в layout на ViewStub и указать inflatedId, который будет использоваться для ссылки на созданный layout при загрузке ViewStub
Если вы хотите загрузить layout, указанный в ViewStub, либо сделайте его видимым через setVisibility(View.VISIBLE), либо вызовите inflate()
#news #dalvik #art

Android: Dalvik vs ART

В данной статье автор прошелся по отличиям Dalvik и ART виртуальных машин.
👉 Давайте в начале вспомним, что такое Java VM. JVM - это виртуальная машина, способная выполнять байт-код Java независимо от базовой платформы. Основано на принципе: «Пиши один раз, запускайся где угодно». Компилятор Java преобразует файлы .java в файлы .class (байт-код).
Возможности JVM:
1️⃣ Stack архитектура. Стек используется как структура данных, в которой размещаются и хранятся методы
2️⃣ Запускает только файлы .class
3️⃣ Использует JIT-компилятор

👉 В свою очередь Dalvik или DVM - это виртуальная машина Java, разработанная для Android. Когда пользователь запускает приложение, ядро ​​виртуальной машины Dalvik создает отдельный безопасный процесс в общей памяти, где виртуальная машина непосредственно развертывается как среда для запуска приложения.
Возможности DVM:
1️⃣ Register-based архитектура. Структура данных с размещенными там методами основана на регистрах процессора

⚠️ Из-за отсутствия POP и PUSH инструкции в регистровой VM выполняются быстрее, чем аналогичные в стековой VM

2️⃣ Выполняет байт-код своего формата. Android dexer преобразует файлы .class в формат .dex, оптимизированный для выполнения DVM

👉 Важным шагом в создании APK является преобразование байт-кода Java в байт-код .dex. Первоначально файлы .class были преобразованы в .dex с помощью встроенного компилятора DX
👉 Начиная с Android Studio 3.1 и далее компилятором по умолчанию стал D8, который компилирует быстрее и выводит файлы .dex меньшего размера
👉 Полученный байт-код минимизируется с помощью ProGuard. В результате получаем тот же файл .dex, но меньшего размера
👉 Затем этот файл используется для создания APK
👉 После D8 в 2018 году появился R8, который по сути является тем же D8, но обновленным
👉 При работе с Android Studio 3.4 и Android Gradle 3.4.0 плагином или выше Proguard больше не используется для оптимизации кода во время компиляции. Теперь работает R8, который сам выполняет сжатие, оптимизацию и обфускацию кода
👉 DVM был специально разработан для мобильных устройств и использовался как виртуальная машина для запуска приложений Android вплоть до Android 4.4 Kitkat
👉 Начиная с Android 4.4 Kitkat версии ART была представлена как среда выполнения, а в Android 5.0 ART полностью заменил Dalvik.

‼️ Основное видимое различие между ART и DVM заключается в том, что ART использует компиляцию AOT, а DVM - JIT-компиляцию

👉 Теперь подрезюмируем все что мы знаем на сегодня о DVM:
1️⃣ Использует JIT-компиляцию: всякий раз, когда вы запускаете приложение, часть кода, необходимая для выполнения приложения, компилируется. Остальной код компилируется динамически. Это замедляет запуск и работу приложения, но сокращает время установки 😢
2️⃣ Ускоряет запуск устройства, поскольку кеш приложения создается во время выполнения 💪
3️⃣ Приложениям DVM требуется меньше памяти, чем работающим на ART
4️⃣ Снижает производительность батареи за счет увеличения нагрузки на процессор

👉 А что мы знаем о ART:
1️⃣ Использует компиляцию AOT, т.e. компилирует весь код во время установки приложения. Это ускоряет запуск и работу приложений, но требует больше времени на установку 🔥
2️⃣ Замедляет запуск устройства, поскольку кеш создается при первой загрузке
3️⃣ Из-за подхода к компиляции AOT требуется больше памяти по сравнению с приложениями DVM
4️⃣ Увеличивает производительность батареи за счет снижения нагрузки на CPU из-за отсутствия компиляции при запуске приложений
5️⃣ Улучшенная сборка мусора

👉 Начиная с Android 7, среда выполнения Android включает JIT-компилятор с профилированием кода. Компилятор JIT дополняет компилятор AOT, улучшает производительность во время выполнения, экономит место на диске и ускоряет обновление приложений и системы. Данный подход дает свои плоды:
1️⃣ Более эффективная компиляция
2️⃣ Сохранение ОЗУ и постоянной памяти
3️⃣ Резкое увеличение скорости установки и первой загрузки после обновления системы