Хакер - Android: безопасность Android 12 и принцип работы корутин

Хакер - Android: безопасность Android 12 и принцип работы корутин

hacker_frei

https://t.me/hacker_frei

Евгений Зобнин

Содержание статьи

  • Почитать
  • Security-новшества Android 12
  • Разработчику
  • Полезные функции-расширения
  • Вредные функции-расширения
  • Лучший способ сбора данных из Flow
  • Как работают корутины
  • Библиотеки

Се­год­ня в выпус­ке: security-нов­шес­тва Android 12, полез­ные и вред­ные фун­кции‑рас­ширения, советы по работе с Flow, луч­шее объ­ясне­ние прин­ципа работы корутин. А так­же под­борка биб­лиотек для прог­раммис­тов.

ПОЧИТАТЬ

Security-новшества Android 12

First preview of Android 12 — анонс Android 12, самая инте­рес­ная часть которо­го — новые механиз­мы обес­печения безопас­ности.

  • Ужес­точение пра­вил рас­шарива­ния cookie в WebView. Вслед за Chromium WebView теперь исполь­зует более жес­ткие пра­вила рас­шарива­ния куков с атри­бутом SameSite. В час­тнос­ти, все куки с атри­бутом SameSite=None обя­заны иметь атри­бут Secure и пересы­лать­ся по HTTPS, а ссыл­ки меж­ду HTTP- и HTTPS-вер­сиями сай­та теперь счи­тают­ся cross-site-рек­веста­ми.
  • Ог­раниче­ние дос­тупа к MAC-адре­су. Android 11 огра­ничил дос­туп при­ложе­ний к MAC-адре­су устрой­ства, толь­ко если при­ложе­ние име­ет targetSdkVersion 30, в Android 12 огра­ниче­ние рас­простра­няет­ся на все при­ложе­ния.
  • Не­экспор­тиру­емые ком­понен­ты по умол­чанию. Для при­ложе­ний, соб­ранных для Android 12 (targetSdkVersion 31), все ком­понен­ты (активнос­ти, про­вай­деры, сер­висы) теперь авто­мати­чес­ки помеча­ются как неэк­спор­тиру­емые. Поведе­ние мож­но изме­нить с помощью атри­бута android:exported=true. Этот атри­бут обя­зате­лен для всех интент‑филь­тров при­ложе­ния (ина­че при­ложе­ние прос­то не уста­новит­ся на Android 12).
  • Бе­зопас­ность PendingIntent. При­ложе­ния, собира­емые с targetSdkVersion 31, теперь обя­заны помечать все PendingIntent как изме­няемый или неиз­меня­емый (PendingIntent.FLAG_MUTABLEPendingIntent.FLAG_IMMUTABLE). PendingIntent исполь­зует­ся в Android, что­бы поз­волить сис­теме или сто­рон­ним при­ложе­ниям передать интент от име­ни дру­гого при­ложе­ния.
  • Борь­ба с овер­леями. Android 12 зап­реща­ет нажимать эле­мен­ты интерфей­са сквозь неп­розрач­ные овер­леи (окна, показы­ваемые поверх всех при­ложе­ний) за нес­коль­кими исклю­чени­ями: окна ассистен­тов, помощ­ников для людей с огра­ничен­ными воз­можнос­тями и экранных кла­виатур.
  • Зап­рет на запуск foreground-сер­висов в фоне. При­ложе­ния, соб­ранные с targetSdkVersion 31, не смо­гут запус­кать foreground-сер­висы в фоне.
  • Зап­рет на зак­рытие сис­темных диало­гов. Интент ACTION_CLOSE_SYSTEM_DIALOGS объ­явлен уста­рев­шим и боль­ше не работа­ет.

РАЗРАБОТЧИКУ

Полезные функции-расширения

5 Kotlin Extensions To Make Your Android Code More Expressive — оче­ред­ная статья о том, как сде­лать код на Kotlin вырази­тель­нее с помощью фун­кций‑рас­ширений.

  1. Фун­кции для показа и скры­тия эле­мен­тов интерфей­са:
  2. fun View.show(){
  3. this.visibility = View.VISIBLE
  4. }
  5. fun View.hide() {
  6. this.visibility = View.INVISIBLE
  7. }
  8. fun View.remove(){
  9. this.visibility = View.GONE
  10. }
  11. Фун­кции валида­ции строк:
  12. fun String?.valid(): Boolean = this != null && !this.equals("null", true) && this.trim().isNotEmpty()
  13. fun String.isValidEmail(): Boolean = this.isNotEmpty() && Patterns.EMAIL_ADDRESS.matcher(this).matches()
  14. fun String.formatPhoneNumber(context: Context, region: String): String? {
  15. val phoneNumberKit = PhoneNumberUtil.createInstance(context)
  16. val number = phoneNumberKit.parse(this, region)
  17. if (!phoneNumberKit.isValidNumber(number))
  18. return null
  19. return phoneNumberKit.format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
  20. Фун­кции для работы с бан­дла­ми:
  21. inline fun <reified T: Any> Activity.getValue(lable: String, defaultvalue: T? = null) = lazy{
  22. val value = intent?.extras?.get(lable)
  23. if (value is T) value else defaultvalue
  24. }
  25. inline fun <reified T: Any> Activity.getValueNonNull(lable: String, defaultvalue: T? = null) = lazy{
  26. val value = intent?.extras?.get(lable)
  27. requireNotNull((if (value is T) value else defaultvalue)){lable}
  28. }
  29. inline fun <reified T: Any> Fragment.getValue(lable: String, defaultvalue: T? = null) = lazy {
  30. val value = arguments?.get(lable)
  31. if (value is T) value else defaultvalue
  32. }
  33. inline fun <reified T: Any> Fragment.getValueNonNull(lable: String, defaultvalue: T? = null) = lazy {
  34. val value = arguments?.get(lable)
  35. requireNotNull(if (value is T) value else defaultvalue) { lable }
  36. }
  37. Фун­кции для извле­чения ресур­сов:
  38. fun Int.asColor() = ContextCompat.getColor(ApplicationCalss.instance, this)
  39. fun Int.asDrawable() = ContextCompat.getDrawable(MavrikApplication.instance, this)
  40. По­каз диало­гов и сооб­щений:
  41. fun Context.showAlertDialog(positiveButtonLable: String = getString(R.string.okay), title: String = getString(R.string.app_name), message: String, actionOnPositveButton: () -> Unit) {
  42. val builder = AlertDialog.Builder(this)
  43. .setTitle(title)
  44. .setMessage(message)
  45. .setCancelable(false)
  46. .setPositiveButton(positiveButtonLable) { dialog, id ->
  47. dialog.cancel()
  48. actionOnPositveButton()
  49. }
  50. val alert = builder.create()
  51. alert?.show()
  52. }
  53. fun Context.showShotToast(message: String){
  54. Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
  55. }
  56. fun Context.showLongToast(message: String){
  57. Toast.makeText(this, message, Toast.LENGTH_LONG).show()
  58. }
  59. fun View.showShotSnackbar(message: String){
  60. Snackbar.make(this, message, Snackbar.LENGTH_SHORT).show()
  61. }
  62. fun View.showLongSnackbar(message: String){
  63. Snackbar.make(this, message, Snackbar.LENGTH_LONG).show()
  64. }
  65. fun View.snackBarWithAction(message: String, actionlable: String, block: () -> Unit){
  66. Snackbar.make(this, message, Snackbar.LENGTH_LONG)
  67. .setAction(actionlable) {
  68. block()
  69. }
  70. }

Вредные функции-расширения

Bad Kotlin Extensions — статья о том, как не надо писать фун­кции‑рас­ширения на Kotlin. Боль­шая часть тек­ста осно­вана на стан­дар­тных пра­вилах соз­дания фун­кций: фун­кция не дол­жна делать боль­ше, чем заяв­лено в ее наз­вании; фун­кция дол­жна иметь чет­кое имя, которое на 100% однознач­но отра­жает ее суть, и так далее. Но есть и нес­коль­ко весь­ма инте­рес­ных при­меров:

operator fun Int.not(): Int {

return factorial(this)

}

Эта фун­кция‑рас­ширение поз­воля­ет счи­тать фак­тори­ал с помощью такой записи:

!5

Она весь­ма похожа на запись 5!, которая исполь­зует­ся для рас­чета фак­тори­ала в матема­тике. Одна­ко в дан­ном слу­чае такая фор­ма, конеч­но же, будет сби­вать с тол­ку, так как в язы­ках прог­рамми­рова­ния вос­кли­цатель­ный знак поч­ти всег­да озна­чает отри­цание.

Еще один инте­рес­ный при­мер:

operator File.div(fileName: String): File = File(this, fileName)

Дан­ная фун­кция поз­воля­ет делать так:

val file = File("src") / "main" / "java" / "com"

Выг­лядит клас­сно, но поль­зы тут не так уж и мно­го, а овер­хед от соз­дания четырех объ­ектов высокий.

Лучший способ сбора данных из Flow

The Best Way to Collect a Flow in Kotlin — launchIn — неболь­шая замет­ка о неоче­вид­ных момен­тах Kotlin Flow API и фун­кции launchIn.

Пред­ста­вим, что нам необ­ходимо соб­рать дан­ные из Flow. Спо­соб сде­лать это «в лоб» выг­лядел бы так:

scope.launch {

flow

.onEach { println(it) }

.collect()

}

Од­нако «канонич­ный» спо­соб будет дру­гим:

flow

.onEach { println(it) }

.launchIn(scope)

И это не прос­то син­такси­чес­кий сахар. LaunchIn поз­воля­ет избе­жать весь­ма неоче­вид­ных проб­лем с при­ложе­нием.

К при­меру, ког­да нуж­но соб­рать дан­ные из двух Flow, лег­ко оши­бить­ся и сде­лать это так:

scope.launch {

flow1

.onEach { println(it) }

.collect()

flow2

.onEach { println(it) }

.collect()

}

Ошиб­ка здесь в том, что дан­ные из двух Flow не будут собирать­ся одновре­мен­но. Сна­чала будут получе­ны все дан­ные из flow1, и толь­ко затем нач­нется сбор flow2.

Ис­пра­вить это некано­нич­ным путем мож­но так:

scope.launch {

flow1

.collect { println(it) }

}

scope.launch {

flow2

.collect { println(it) }

}

Од­нако при исполь­зовании launchIn такого не воз­никнет в прин­ципе:

flow1

.onEach { println(it) }

.launchIn(coroutineScope)

flow2

.onEach { println(it) }

.launchIn(coroutineScope)

Как работают корутины

Lets build a coroutine — хорошая статья, объ­ясня­ющая на паль­цах, как работа­ют корути­ны в Kotlin и дру­гих язы­ках.

Раз­работ­чики Kotlin называ­ют корути­ны лег­ковес­ными потока­ми. Одна­ко такое объ­ясне­ние не помога­ет понять их сути и даже меша­ет это­му. На самом же деле корути­ны доволь­но прос­тая, но мало похожая на потоки кон­цепция.

Что­бы разоб­рать­ся с корути­нами (coroutine), надо понять, что такое routine. А это не что иное, как фун­кция. Нап­ример, такая:

fun saveUserTasks(userId: Int) {

val user = loadUser(userId)

println("user loaded")

val tasks = loadTasks(user)

println("tasks loaded")

saveTasks(tasks)

}

Две отли­читель­ные чер­ты фун­кций:

  • они не име­ют сос­тояния и всег­да запус­кают­ся «с чис­того лис­та» (если, конеч­но, не исполь­зуют гло­баль­ные перемен­ные);
  • фун­кция дол­жна завер­шить свое исполне­ние, перед тем как вер­нуть управле­ние выз­вавше­му ее коду.

Ко­рути­на, с дру­гой сто­роны, име­ет сос­тояние и может при­оста­нав­ливать и возоб­новлять свое исполне­ние в опре­делен­ных точ­ках (воз­вра­щая, таким обра­зом, управле­ние еще до завер­шения сво­его исполне­ния).

Ес­ли мы поп­робу­ем вруч­ную пре­обра­зовать при­веден­ную выше фун­кцию в корути­ну, то получим неч­то вро­де это­го:

class State(

var label: Int = 0,

var result: Any? = null

)

fun saveUserTasks(userId: Int, state: State) {

when (state.label) {

0 -> {

val user = loadUser(userId)

println("user loaded")

state.result = user

// Точка остановки исполнения

}

1 -> {

// Точка возобновления исполнения

val user = state.result as User

val tasks = loadTasks(user)

println("tasks loaded")

state.result = tasks

// Точка остановки исполнения

}

2 -> {

// Точка возобновления исполнения

val tasks = state.result as List<Task>

saveTasks(tasks)

}

}

}

Те­перь мы можем запус­тить нашу доморо­щен­ную «корути­ну» на выпол­нение с помощью такого кода:

fun main() {

val state = State()

saveUserTasks(7, state)

saveUserTasks(7, state)

saveUserTasks(7, state)

}

Ре­зуль­тат будет тот же, что и в слу­чае при­веден­ной в начале клас­сичес­кой фун­кции. Но теперь у нас появи­лась воз­можность запус­кать и при­оста­нав­ливать исполне­ние фун­кции в нес­коль­ких точ­ках. Если мы добавим сюда еще нес­коль­ко подоб­ных корутин, то смо­жем выпол­нять их фраг­менты пооче­ред­но, соз­дав иллю­зию одновре­мен­ного исполне­ния.

Имен­но так работа­ют корути­ны в Kotlin. Он прев­раща­ет фун­кции с модифи­като­ром suspend в объ­ект клас­са Continuation, который внут­ри пред­став­ляет собой при­мер­но такую же машину сос­тояний, которую мы изоб­рели чуть выше. Точ­ки оста­нов­ки при этом появ­ляют­ся в мес­тах вызова дру­гих suspend-фун­кций.

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

Report Page