Хакер - Android: борьба с оверлеями и контракты Kotlin
hacker_frei
Евгений Зобнин
Содержание статьи
- Почитать
- Борьба с оверлеями
- Разработчику
- Чем отличаются compileSdkVersion и targetSdkVersion
- Используем Ktor вместо OkHttp
- Ограничение видимости функций-расширений
- Очередная подборка функций-расширений
- Kotlin и контракты
- Библиотеки
Сегодня в выпуске: как Android 12 борется с оверлеями, чем отличаются compileSdkVersion и targetSdkVersion, чем заменить OkHttp в мультиплатформенных проектах, как ограничить видимость функций‑расширений, что такое контракты Kotlin. А также: подборка функций‑расширений на все случаи жизни и библиотек для разработчиков.
ПОЧИТАТЬ
Борьба с оверлеями
Untrusted Touch Events in Android — статья о новой функции Android 12, направленной на борьбу с оверлеями, которые перекрывают весь экран или его часть.
Проблема оверлеев (окон, которые приложения могут показывать поверх своего или любых других окон) в том, что они позволяют перекрыть окно другого приложения и передавать ему все нажатия, показывая на экране совершенно другую информацию. В итоге злоумышленник может создать оверлей, который будет призывать нажать безобидную кнопку, а в итоге нажатие будет передано находящемуся позади него окну, которое может активировать опасную функцию.
В разных версиях Android Google реализовала все новые методы защиты от оверлеев, включая невозможность контактировать с системными диалогами при наличии оверлеев, отзыв разрешения на показ оверлеев при первой возможности и так далее. В Android 12 появится еще один вид защиты: невозможность использовать оверлеи, которые пропускают нажатия. Другими словами, если приложение показывает непрозрачный оверлей, который передает нажатия находящемуся за ним окну (тип окна: TYPE_APPLICATION_OVERLAY с флагом FLAG_NOT_TOUCHABLE), то такое окно будет заблокировано.
В списке исключений:
- полностью прозрачные оверлеи;
- невидимые оверлеи (GONE и INVISIBLE);
- доверенные оверлеи (окна сервисов Accessibility, клавиатур и ассистентов);
- оверлеи, демонстрируемые поверх окна собственного приложения.
РАЗРАБОТЧИКУ
Чем отличаются compileSdkVersion и targetSdkVersion
CompileSdkVersion and targetSdkVersion — what is the difference? — статья об отличиях двух свойств Gradle, которые часто приводят к вопросам и недопониманию.
Действительно, как разработчики мы обычно обновляем значения compileSdkVersion и targetSdkVersion одновременно. Для нас такое обновление означает, что приложение теперь может использовать новые API, появившиеся в новой версии Android, и на приложения теперь накладываются новые ограничения, которые в этой версии Android появились.
Но зачем тогда существует два свойства, если даже IDE подсказывает, что при обновлении значения одного следует обновить и значение другого? Начнем с compileSdkVersion. Задача этого свойства в том, чтобы указать, какая версия SDK будет использоваться при компиляции приложения. Если сильно упростить, то она нужна для того, чтобы приложение смогло найти новые API и вызвать их.
Задача свойства targetSdkVersion другая. С его помощью разработчик как бы говорит «я протестировал свое приложение на этой версии Android, и оно готово к особенностям работы именно этой версии Android». Это важное свойство потому, что с развитием Android меняется не только API, но и поведение ОС в отношении приложений. Android может вести себя по‑разному в зависимости от того, для какой версии собрано приложение.
Например, в Android 12 изменился способ отображения уведомлений. Если в предыдущих версиях приложения могли использовать всю область уведомления, то теперь им доступен только ограниченный прямоугольник с отступами по краям. Для приложений, собранных с targetSdkVersion 30 или ниже (то есть для предыдущих версий Android), система будет включать режим совместимости, позволяя съедать всю область уведомления. Но для приложений с targetSdkVersion 31 будет доступна только часть области уведомления.
Вместе с новой версией ОС Google выпускает документ, в котором подробно расписывает, какие аспекты поведения ОС изменятся в зависимости или вне зависимости от значения targetSdkVersion. По‑хорошему программист сначала должен ознакомиться с этим списком, исправить приложение так, чтобы оно учитывало изменения, затем изменить оба свойства на значение новой версии ОС.
При этом никто не запрещает изменять compileSdkVersion и targetSdkVersion раздельно, но практического смысла в этом мало, так как Google постоянно повышает минимальную версию targetSdkVersion для принимаемых в Google Play приложений.
Используем Ktor вместо OkHttp
Kotlin Ktor Network Fetching on Android — статья об использовании библиотеки сетевых запросов Ktor для создания мультиплатформенного приложения вместо библиотеки OkHttp.
Ktor — это библиотека для разработки клиентских и серверных сетевых приложений, изначально спроектированная для работы в среде Kotlin вне зависимости от того, на какой платформе работает приложение: JVM, Android, iOS, браузер или десктоп.
Для начала Ktor следует подключить к проекту:
implementation "io.ktor:ktor-client-core:1.6.0"
implementation "io.ktor:ktor-client-cio:1.6.0"
Далее в коде создаем объект класса HttpClient (CIO — это движок корутин для JVM):
private val ktorHttpClient = HttpClient(CIO)
Затем можно инициировать сетевой запрос и получить ответ:
val response = ktorHttpClient.get("https://www.example.com")
Более сложный запрос может выглядеть так:
val response: HttpResponse = ktorHttpClient.submitForm(
url = "https://en.wikipedia.org/w/api.php",
formParameters = Parameters.build {
append("action", "query")
append("format", "json")
append("list", "search")
append("srsearch", queryString)
},
encodeInQuery = true
)
Теперь можно прочитать результат:
if (response.status == HttpStatusCode.OK) {
val raw = response.readText()
val result = Gson().fromJson(raw, Model.Result::class.java)
return Result.NetworkResult(
result.query.searchinfo.totalhits.toString()
)
}
В целом все очень похоже на OkHttp.
Ограничение видимости функций-расширений
Limit the Availability of Kotlin Extension Functions by using Generics and an Empty Interface — статья о том, как ограничить действие функции‑расширения только определенными классами.
Представим, что у нас есть класс Vehicle, олицетворяющий транспортное средство, а подклассом этого класса может быть как автомобиль, так и космический корабль. Теперь мы добавляем к классу Vehicle функцию‑расширение «проехать про бульвару»:
fun Vehicle.cruiseTheBoulevard(): String
Разумеется, такая функция не может иметь отношение к космическому кораблю. Мы могли бы решить эту проблему, добавив функцию‑расширение классу Automobile, который наследуется от класса Vehicle, но что, если в будущем мы создадим еще и класс Bicycle? Тогда функцию‑расширение придется добавлять и для него.
Решить эту проблему можно с помощью пустого интерфейса:
interface LandVehicle
fun <T> T.cruiseTheBoulevard(): String where T: Vehicle, T: LandVehicle {
return "cruising the boulevard with my ${getMakeAndModel()}"
}
Такая функция‑расширение будет доступна только в классе Vehicle, который реализует интерфейс LandVehicle (хоть он и пуст).
Очередная подборка функций-расширений
Useful Kotlin Extensions for Android — функции‑расширения на все случаи жизни.
- Получение цветов и иконок:
fun Context.getCompatColor(@ColorRes colorId: Int) = ResourcesCompat.getColor(resources, colorId, null)fun Context.getCompatDrawable(@DrawableRes drawableId: Int) = AppCompatResources.getDrawable(this, drawableId)!!- Проверка сразу нескольких расширений:
fun Context.hasPermissions(vararg permissions: String) = permissions.all { permission ->ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED}- Копирование в буфер обмена:
fun Context.copyToClipboard(content: String) {val clipboardManager = ContextCompat.getSystemService(this, ClipboardManager::class.java)!!val clip = ClipData.newPlainText("clipboard", content)clipboardManager.setPrimaryClip(clip)}- Конвертация времени в формат ISO:
val isoFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")fun Date.toISOFormat() : String = isoFormatter.format(this)
Kotlin и контракты
Using Kotlin’s Contract APIs for Smarter Helper Functions — статья, рассказывающая, как сделать код приложения чище, используя Contract API языка Kotlin.
Представим, что у нас есть следующая функция:
val Any?.isNull: Boolean get() = this == null
Она позволяет нам писать такой код:
if (name.isNull) { ... }
Вместо такого:
if (name == null) { ... }
Однако есть небольшая проблема. Следующий код вызовет ошибку компилятора и предупреждение среды разработки о том, что в третьей строке происходит обращение к nullable-переменной:
val name: String? = null
if (name.isNull) return
println("name is ${name.length} characters long")
Так происходит потому, что анализатор кода в среде разработки и компиляторе не в состоянии определить, что в третьей строке name гарантированно не может быть null.
К счастью, мы можем это исправить, используя контракты:
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract@OptIn(ExperimentalContracts::class)
fun Any?.isNull(): Boolean {
contract {
returns(false) implies (this@isNull != null)
}
return this == null
}
От предыдущей реализации данная функция‑расширение отличается появившимся блоком contract. Он как раз и дает анализатору кода подсказку, что если возвращаемое этой функцией значение равно false, то текущий объект не равен null.
Это только один пример использования контрактов. В библиотеке Kotlin есть множество других примеров, включая функцию requireNotNull. Использовать ее можно так:
fun main() {
val name: String? = null
requireNotNull(name)
println("name is ${name.length} characters long")
}
А код функции выглядит так:
public inline fun <T : Any> requireNotNull(value: T?): T {
contract {
returns() implies (value != null)
}
return requireNotNull(value) { "Required value was null." }
}
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei