Хакер - Android: безопасность Android 12 и принцип работы корутин
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_MUTABLE,PendingIntent.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 выразительнее с помощью функций‑расширений.
- Функции для показа и скрытия элементов интерфейса:
fun View.show(){this.visibility = View.VISIBLE}fun View.hide() {this.visibility = View.INVISIBLE}fun View.remove(){this.visibility = View.GONE}- Функции валидации строк:
fun String?.valid(): Boolean = this != null && !this.equals("null", true) && this.trim().isNotEmpty()fun String.isValidEmail(): Boolean = this.isNotEmpty() && Patterns.EMAIL_ADDRESS.matcher(this).matches()fun String.formatPhoneNumber(context: Context, region: String): String? {val phoneNumberKit = PhoneNumberUtil.createInstance(context)val number = phoneNumberKit.parse(this, region)if (!phoneNumberKit.isValidNumber(number))return nullreturn phoneNumberKit.format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)- Функции для работы с бандлами:
inline fun <reified T: Any> Activity.getValue(lable: String, defaultvalue: T? = null) = lazy{val value = intent?.extras?.get(lable)if (value is T) value else defaultvalue}inline fun <reified T: Any> Activity.getValueNonNull(lable: String, defaultvalue: T? = null) = lazy{val value = intent?.extras?.get(lable)requireNotNull((if (value is T) value else defaultvalue)){lable}}inline fun <reified T: Any> Fragment.getValue(lable: String, defaultvalue: T? = null) = lazy {val value = arguments?.get(lable)if (value is T) value else defaultvalue}inline fun <reified T: Any> Fragment.getValueNonNull(lable: String, defaultvalue: T? = null) = lazy {val value = arguments?.get(lable)requireNotNull(if (value is T) value else defaultvalue) { lable }}- Функции для извлечения ресурсов:
fun Int.asColor() = ContextCompat.getColor(ApplicationCalss.instance, this)fun Int.asDrawable() = ContextCompat.getDrawable(MavrikApplication.instance, this)- Показ диалогов и сообщений:
fun Context.showAlertDialog(positiveButtonLable: String = getString(R.string.okay), title: String = getString(R.string.app_name), message: String, actionOnPositveButton: () -> Unit) {val builder = AlertDialog.Builder(this).setTitle(title).setMessage(message).setCancelable(false).setPositiveButton(positiveButtonLable) { dialog, id ->dialog.cancel()actionOnPositveButton()}val alert = builder.create()alert?.show()}fun Context.showShotToast(message: String){Toast.makeText(this, message, Toast.LENGTH_SHORT).show()}fun Context.showLongToast(message: String){Toast.makeText(this, message, Toast.LENGTH_LONG).show()}fun View.showShotSnackbar(message: String){Snackbar.make(this, message, Snackbar.LENGTH_SHORT).show()}fun View.showLongSnackbar(message: String){Snackbar.make(this, message, Snackbar.LENGTH_LONG).show()}fun View.snackBarWithAction(message: String, actionlable: String, block: () -> Unit){Snackbar.make(this, message, Snackbar.LENGTH_LONG).setAction(actionlable) {block()}}
Вредные функции-расширения
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