Хакер - Android: идентификация по обоям и security-новшества Android 12

Хакер - Android: идентификация по обоям и security-новшества Android 12

hacker_frei

https://t.me/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 и рас­смот­рим наибо­лее инте­рес­ные.

  1. В чем раз­ница меж­ду метода­ми setValue() и postValue() в MutableLiveData? Пер­вый исполь­зует­ся из основно­го потока, вто­рой — из фоново­го.
  2. Как про­верить, что lateinit-свой­ство было ини­циали­зиро­вано? С помощью метода isInitialized().
  3. Ка­кова раз­ница меж­ду object и companion object? Пер­вый ини­циали­зиру­ется при пер­вом дос­тупе, вто­рой — во вре­мя заг­рузки клас­са, к которо­му он при­вязан.
  4. Раз­ница меж­ду опе­рато­рами == и ===? Пер­вый исполь­зует­ся для про­вер­ки зна­чений объ­ектов, вто­рой — ссы­лок.
  5. В чем пре­иму­щес­тво опе­рато­ра when в Kotlin перед switch в Java? When нам­ного мощ­нее, его мож­но исполь­зовать как выраже­ние, а внут­ри выпол­нять слож­ные срав­нения.
  6. В чем раз­ница меж­ду основным и вто­рич­ным конс­трук­тором клас­са в Kotlin? Пер­вичный конс­трук­тор объ­явля­ется в самом объ­явле­нии клас­са, сра­зу пос­ле име­ни клас­са; он не может содер­жать кода ини­циали­зации (его сле­дует выносить в блок init). Для объ­явле­ния вто­рич­ного конс­трук­тора исполь­зует­ся блок constructor, в котором мож­но не толь­ко ини­циали­зиро­вать поля, но и выпол­нять код.
  7. Мож­но ли исполь­зовать нес­коль­ко бло­ков init? Да, их мож­но исполь­зовать, что­бы ини­циали­зиро­вать раз­личные по наз­начению и смыс­лу ком­понен­ты клас­са.
  8. Что такое suspend-фун­кция? Это фун­кция, исполне­ние которой может быть оста­нов­лено и возоб­новле­но поз­же. Такие фун­кции обыч­но исполь­зуют­ся для пос­ледова­тель­ного выпол­нения асин­хрон­ного кода.
  9. Ос­новная раз­ница меж­ду onPause() и onClose()? onPause вызыва­ется, даже ког­да на экра­не появ­ляет­ся диалог, нап­ример диалог под­твержде­ния раз­решения. Метод onClose будет выз­ван, толь­ко ког­да текущая активность сме­нит­ся дру­гой пол­ноэк­ранной активностью.
  10. Чем отли­чает­ся AndroidViewModel от ViewModel? Пер­вая вклю­чает в себя кон­текст при­ложе­ния.
  11. За­чем нуж­на Jetpack Paging Library? Эта биб­лиоте­ка поз­воля­ет заг­ружать и отоб­ражать дан­ные неболь­шими пор­циями.
  12. За­чем нужен Jetpack Navigation Component? Он зна­читель­но упро­щает управле­ние навига­цией внут­ри при­ложе­ния.
  13. В каком потоке работа­ет сер­вис? В основном потоке при­ложе­ния — UI-потоке. Но при желании поток мож­но изме­нить.
  14. В чем раз­ница меж­ду сер­висом и Intent Service? Клас­сичес­кий сер­вис пред­назна­чен для выпол­нения дол­гих фоновых опе­раций, Intent Service — для неболь­ших задач, которые мож­но отдать на исполне­ние и забыть.
  15. За­чем исполь­зовать ProGuard? ProGuard (в нас­тоящее вре­мя он заменен на внут­реннюю реали­зацию от Google) поз­воля­ет умень­шить раз­мер APK, уда­лить из него неис­поль­зуемые клас­сы и зат­рудня­ет реверс‑инжи­ниринг.
  16. Что такое Coroutine Dispatcher? Он опре­деля­ет, в каком потоке будет выпол­нять­ся корути­на.
  17. Что такое CoroutineScope? Он опре­деля­ет жиз­ненный цикл корути­ны. Корути­ны, при­над­лежащие одно­му Scope, будут завер­шены, ког­да закон­чится жиз­ненный цикл CoroutineScope.

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei

Report Page