Хакер - Android: идентификация по обоям и security-новшества Android 12
hacker_frei
Евгений Зобнин
Содержание статьи
- Почитать
- Security-новшества Android 12
- Идентификация Android-устройств по обоям
- Разработчику
- Как избежать создания объектов
- Jetpack Compose FAQ
- Jetpack Compose и ViewModel
- Вопросы на собеседовании
Сегодня в выпуске: security-новшества Android 12, идентификация Android-устройств по обоям, советы, как избежать чрезмерного создания объектов, FAQ по Jetpack Compose и самые популярные вопросы на собеседовании. А также: смерть ViewModel и подборка библиотек для разработчиков.
ПОЧИТАТЬ
Security-новшества Android 12
Android 12 Release Notes — официальный документ команды разработчиков Google о новшествах платформы Android 12. Мы сосредоточимся исключительно на новых функциях безопасности.
Ограничения на показ оверлеев. С новой версии Android больше не предоставляет разрешение на показ оверлеев (окон поверх экрана) автоматически для предустановленных приложений. Исключение сделано только для системных приложений, располагающихся в каталоге /system. ОС также налагает ограничения на оверлеи. Теперь приложения не могут использовать непрозрачные оверлеи, которые пропускают через себя нажатия (раньше с их помощью пользователя вынуждали нажать определенные элементы интерфейса, перекрыв их безобидным окном). Кроме того, приложения теперь могут попросить систему не показывать оверлеи поверх своих окон с помощью разрешения HIDE_OVERLAY_WINDOWS.
Возможность отключить 2G. Android 12 позволяет отключать поддержку сотовых сетей стандарта 2G в настройках смартфона, чтобы защититься от атак, использующих понижение до 2G.
Улучшения Wi-Fi. Android 12 поддерживает WPA3 Hash-to-Element (H2E), WPA2/WPA3-Enterprise transition mode и Transition Disable indication.
Разрешения на использование Bluetooth. Android 12 включает несколько новых разрешений для работы с Bluetooth:
BLUETOOTH_SCAN— возможность поиска новых устройств и подключения к ним;BLUETOOTH_ADVERTISE— разрешение на использование Bluetooth advertising;BLUETOOTH_CONNECT— возможность подключения к привязанным устройствам.
Эти разрешения заменяют ранее применявшиеся разрешения BLUETOOTH и BLUETOOTH_ADMIN, но только для приложений, собранных под Android 12. Старые приложения продолжат работать как прежде.
Индикаторы камеры и микрофона. Как и iOS, начиная с 12-й версии Android будет показывать индикаторы использования микрофона и камеры в правом верхнем углу экрана.
Идентификация Android-устройств по обоям
How Android Wallpaper Images Can Threaten Your Privacy — занимательная статья о том, как идентифицировать любое устройство с Android по установленным на экране обоям.
Небольшое предисловие. Еще в Android 2.0 появился класс WallpaperManager, который можно использовать для управления установленными на экране обоями. Помимо прочего, класс включает метод getDrawable(), который позволяет получить текущие обои в виде изображения. По факту это была уязвимость, которую исправили только в Android 8.1, заставив приложения использовать разрешение READ_EXTERNAL_STORAGE, чтобы получить обои.
В той же Android 8.1 у класса WallpaperManager появился другой метод — getWallpaperColors(), позволяющий извлечь три «главных» цвета из обоев (именно его использует новая система теминга Android 12). Внутри эта функция использует метод k-средних, интересная особенность которого в том, что фактически он создает уникальный «цветовой отпечаток» обоев. В итоге по такому отпечатку можно точно идентифицировать устройство.
Для демонстрации этой техники автор создал приложение и выложил его исходники на GitHub.
РАЗРАБОТЧИКУ
Как избежать создания объектов
Effective Kotlin Item 47: Avoid unnecessary object creation — статья о том, как сделать приложение чуточку быстрее, избавившись от ненужного создания дополнительных объектов.
Основная идея: объекты — дорогое удовольствие. Они занимают память, а на их создание уходит хоть и совсем незначительное по современным меркам, но время. Поэтому даже виртуальная машина Java старается минимизировать создание дополнительных объектов. Например, можно было бы подумать, что две следующие строки должны быть разными объектами, но это не так (оператор === сравнивает ссылки на объект):
val str1 = "Lorem ipsum dolor sit amet"
val str2 = "Lorem ipsum dolor sit amet"
print(str1 == str2) // True
print(str1 === str2) // True
Виртуальная машина, видя два одинаковых объекта типа String, объединяет их в один объект. То же самое происходит с типами Int и Long, но только для чисел от –128 до 127:
val i1: Int? = 1
val i2: Int? = 1
print(i1 == i2) // True
print(i1 === i2) // True
Для хранения каждого объекта используется заголовок из 12 байт, который на 64-битных системах выравнен по 8 байтам. Так что в целом заголовок занимает 16 байт, плюс сама ссылка на объект. Это не так много, но, когда имеешь дело с большим количеством одинаковых небольших объектов, это играет свою роль. Например, Array<Int> будет занимать в пять раз больше места, чем IntArray, просто потому, что в первом случае каждое число обернуто в объект.
На создание объектов также уходит время. Оно совсем небольшое при создании одиночных объектов, но может иметь серьезные негативные последствия, когда объектов много. Например:
class A
private val a = A()
// Benchmark result: 2.698 ns/op
fun accessA(blackhole: Blackhole) {
blackhole.consume(a)
}
// Benchmark result: 3.814 ns/op
fun createA(blackhole: Blackhole) {
blackhole.consume(A())
}
// Benchmark result: 3828.540 ns/op
fun createListAccessA(blackhole: Blackhole) {
blackhole.consume(List(1000) { a })
}
// Benchmark result: 5322.857 ns/op
fun createListCreateA(blackhole: Blackhole) {
blackhole.consume(List(1000) { A() })
}
Есть несколько способов избежать создания объектов. Один из них — использовать синглтоны. Например, в следующей реализации связного списка объект класса Empty создается при каждом создании списка, хотя он всегда один и тот же:
sealed class LinkedList<T>
class Node<T>(
val head: T,
val tail: LinkedList<T>
) : LinkedList<T>()
class Empty<T> : LinkedList<T>()
// Usage
val list: LinkedList<Int> =
Node(1, Node(2, Node(3, Empty())))
val list2: LinkedList<String> =
Node("A", Node("B", Empty()))
Просто заменим его синглтоном:
object Empty : LinkedList<Nothing>()
Еще один способ — использовать пул объектов. Так делает, например, библиотека поддержки корутин. Вместо создания потока для каждой фоновой задачи она поддерживает пул (по сути, массив) потоков и запускает код на уже подготовленном потоке. Простейшая реализация пула может выглядеть примерно так:
private val connections: MutableMap<String, Connection> =
mutableMapOf<String, Connection>()
fun getConnection(host: String) =
connections.getOrPut(host) { createConnection(host) }
Объекты можно кешировать. Например, если какая‑либо функция при вызове создает объекты, то эти объекты можно где‑то сохранить, чтобы использовать при следующем вызове функции. Да, такой кеш будет занимать память, и поэтому придется выбирать между скоростью и потреблением памяти.
Еще один хороший подход — вынести тяжелые объекты «за скобки». Взгляни на следующие две реализации функции, которая вычисляет количество максимальных элементов в коллекции:
fun <T : Comparable<T>> Iterable<T>.countMax(): Int =
count { it == this.max() }
fun <T : Comparable<T>> Iterable<T>.countMax(): Int {
val max = this.max()
return count { it == max }
}
Вторая реализация будет намного быстрее, потому что она находит максимальный элемент только один раз.
Ну и наверное, самый простой способ — откладывать создание тяжелых объектов. То есть вместо такого кода:
val a = A()
писать такой:
val a by lazy { A() }
Во втором случае объект будет создан только при первом обращении к a. А этого может не произойти вовсе.
Jetpack Compose FAQ
7 things you should know before using Jetpack Compose - небольшой FAQ по Jetpack Compose для тех, кто раздумывает, стоит ли использовать новый UI-фреймворк.
- Jetpack Compose уже стабилен? Да, по словам Google, версия 1.0.0 уже полностью готова для продакшена.
- Можно ли использовать Jetpack Compose с Java? Нет, Compose завязан на многие возможности Kotlin, такие как suspend-функции и плагины компилятора.
- Какие минимальные требования? Android 5.0 и Android Studio Arctic Fox (2020.3.1).
- Как Compose влияет на производительность и время сборки? Тесты показали, что Compose способен сделать приложение быстрее, а размер APK — меньше, но только если использовать исключительно Compose и не примешивать традиционные элементы интерфейса; в последнем случае время сборки и размер APK могут незначительно возрасти.
- Можно ли использовать Compose в существующем проекте? Да, Compose может использоваться параллельно и даже совместно с традиционной системой UI Android; более того, миграцию на Compose рекомендуется выполнять постепенно, а не сразу для всего приложения.
- Можно ли использовать Compose совместно с другими популярными библиотеками? Да, Compose — часть набора библиотек Jetpack и отлично с ними взаимодействует, также его можно использовать совместно с Glide, Coil, Dagger/Hilt, Flow, Retrofit, Ktor, Lottie и многими другими библиотеками.
- Можно ли использовать Compose для мультиплатформенной разработки? В настоящий момент находятся в разработке браузерная и десктопная версии Compose, почти готова версия для умных часов, существует даже версия Compose для разработки консольных приложений.
Jetpack Compose и ViewModel
Compose UI and the death of androidx.lifecycle.ViewModel — заметка о том, почему разработчики Android не рекомендуют использовать androidx.lifecycle.ViewModel совместно с Jetpack Compose и почему появление Compose фактически означает смерть ViewModel, даже если вы используете паттерн MVVM.
Ответ на этот вопрос довольно простой: androidx.lifecycle.ViewModel был создан для того, чтобы привязать ViewModel к жизненному циклу активности или фрагмента. В приложениях, написанных с использованием Jetpack Compose, рекомендуется использовать только одну активность и не использовать фрагменты вообще. Другими словами, проблема, решением которой был androidx.lifecycle.ViewModel, просто перестала существовать и поэтому он стал не нужен.
В приложениях на Jetpack Compose, ViewModel должна быть обычным классом, жизненный цикл которого будет равен жизненному циклу всего приложения. Если же необходимо привязать ViewModel к жизненному циклу активности, то достаточно инициализировать его в методе onCreate() активности.
Вопросы на собеседовании
Kotlin Interview Cheat Sheet и Interview Questions for Android Developer — две статьи о частых вопросах на собеседованиях на должность разработчика приложений для Android. Пройдем мимо совсем тривиальных вопросов вроде разницы между val и var и рассмотрим наиболее интересные.
- В чем разница между методами setValue() и postValue() в MutableLiveData? Первый используется из основного потока, второй — из фонового.
- Как проверить, что lateinit-свойство было инициализировано? С помощью метода
isInitialized(). - Какова разница между object и companion object? Первый инициализируется при первом доступе, второй — во время загрузки класса, к которому он привязан.
- Разница между операторами == и ===? Первый используется для проверки значений объектов, второй — ссылок.
- В чем преимущество оператора when в Kotlin перед switch в Java? When намного мощнее, его можно использовать как выражение, а внутри выполнять сложные сравнения.
- В чем разница между основным и вторичным конструктором класса в Kotlin? Первичный конструктор объявляется в самом объявлении класса, сразу после имени класса; он не может содержать кода инициализации (его следует выносить в блок
init). Для объявления вторичного конструктора используется блокconstructor, в котором можно не только инициализировать поля, но и выполнять код. - Можно ли использовать несколько блоков init? Да, их можно использовать, чтобы инициализировать различные по назначению и смыслу компоненты класса.
- Что такое suspend-функция? Это функция, исполнение которой может быть остановлено и возобновлено позже. Такие функции обычно используются для последовательного выполнения асинхронного кода.
- Основная разница между onPause() и onClose()?
onPauseвызывается, даже когда на экране появляется диалог, например диалог подтверждения разрешения. МетодonCloseбудет вызван, только когда текущая активность сменится другой полноэкранной активностью. - Чем отличается AndroidViewModel от ViewModel? Первая включает в себя контекст приложения.
- Зачем нужна Jetpack Paging Library? Эта библиотека позволяет загружать и отображать данные небольшими порциями.
- Зачем нужен Jetpack Navigation Component? Он значительно упрощает управление навигацией внутри приложения.
- В каком потоке работает сервис? В основном потоке приложения — UI-потоке. Но при желании поток можно изменить.
- В чем разница между сервисом и Intent Service? Классический сервис предназначен для выполнения долгих фоновых операций, Intent Service — для небольших задач, которые можно отдать на исполнение и забыть.
- Зачем использовать ProGuard? ProGuard (в настоящее время он заменен на внутреннюю реализацию от Google) позволяет уменьшить размер APK, удалить из него неиспользуемые классы и затрудняет реверс‑инжиниринг.
- Что такое Coroutine Dispatcher? Он определяет, в каком потоке будет выполняться корутина.
- Что такое CoroutineScope? Он определяет жизненный цикл корутины. Корутины, принадлежащие одному Scope, будут завершены, когда закончится жизненный цикл CoroutineScope.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei