Хакер - Android: борьба с оверлеями и контракты Kotlin

Хакер - Android: борьба с оверлеями и контракты Kotlin

hacker_frei

https://t.me/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 — фун­кции‑рас­ширения на все слу­чаи жиз­ни.

  1. По­луче­ние цве­тов и ико­нок:
  2. fun Context.getCompatColor(@ColorRes colorId: Int) = ResourcesCompat.getColor(resources, colorId, null)
  3. fun Context.getCompatDrawable(@DrawableRes drawableId: Int) = AppCompatResources.getDrawable(this, drawableId)!!
  4. Про­вер­ка сра­зу нес­коль­ких рас­ширений:
  5. fun Context.hasPermissions(vararg permissions: String) = permissions.all { permission ->
  6. ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
  7. }
  8. Ко­пиро­вание в буфер обме­на:
  9. fun Context.copyToClipboard(content: String) {
  10. val clipboardManager = ContextCompat.getSystemService(this, ClipboardManager::class.java)!!
  11. val clip = ClipData.newPlainText("clipboard", content)
  12. clipboardManager.setPrimaryClip(clip)
  13. }
  14. Кон­верта­ция вре­мени в фор­мат ISO:
  15. val isoFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
  16. 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

Report Page