Русский
Русский
English
Статистика
Реклама

Kotlin Multiplatform. Работаем с многопоточностью на практике. Ч.1

Доброго всем времени суток! С вами я, Анна Жаркова, ведущий мобильный разработчик компании Usetech
Я давно занимаюсь не только нативной разработкой (как iOS, так и Android), но и кросс-платформенной. В свое время я очень плотно писала на Xamarin (iOS, Android, так и Forms). Так как я интересуюсь различными технологиями шаринга кода, то не прошла и мимо Kotlin Multiplatform (KMM). И сегодня мы с вами поговорим об этом SDK, и как с ним работать на практике.
В сети хватает базовых примеров приложений на KMM, поэтому мы рассмотрим что-то, более приближенное к нашим ежедневным разработческим задачам, а именно, как реализовать многопоточное приложение на Kotlin Multiplatform.



Для начала немного вводной информации. Если вы уже знакомы с Kotlin Multiplatform, то листайте ниже до примера.

Основная идея KMM, как и других кросс-платформенных технологий оптимизация разработки путем написания кода один раз и последующего его использования на разных платформах.

Согласно концепции JetBrains, Kotlin Multiplatform не является фреймворком. Это именно SDK, который позволяет создавать модули с общим кодом, подключаемые к нативным приложениям.


Для взаимодействия с платформами используются специфические для платформы версии Kotlin: Kotlin/JVM, Kotlin/JS, Kotlin/Native. Данные версии включают расширения языка Kotlin, а также специфичные для конкретной платформы библиотеки и инструменты. Написанный на Kotlin модуль компилируется в JVM байткод для Android и LLVM байткод для iOS.


Модуль (Shared, Common) содержит переиспользуемую бизнес-логику. Платформенные модули iOS/Android, к которым подключен Shared/Common, либо используют написанную логику напрямую, либо имплементируют свою реализацию в зависимости от особенностей платформы.

Общая бизнес-логика может включать в себя:
  • сервисы для работы с сетью;
  • сервисы для работы с БД;
  • модели данных.


Также в нее могут входить архитектурные компоненты приложения, напрямую не включающие UI, но с ним взаимодействующие:
  • ViewModel;
  • Presenter;
  • Интеракторы и т.п.

Концепцию Kotlin Multiplatform можно сравнить с реализацией Xamarin Native. Однако, здесь нет модулей или функционала, реализующих UI. Эта логическая нагрузка ложится на подключенные нативные проекты.

Теперь рассмотрим подход на практике.
Если вы еще не работали с KMM, то потребуется установить и настроить инструменты. Раньше это было довольно хлопотно, но сейчас достаточно установить Android Studio (версии от 4.1) и плагин Kotlin Multiplatform Mobile . Выбираем шаблон KMM Application при создании проекта, и все отработает автоматически.


Мультиплатформенные проекты Kotlin обычно делятся на несколько модулей:
  • модуль переиспользуемой бизнес-логики (Shared, commonMain и т.п);
  • модуль для IOS приложения (iOSMain, iOSTest);
  • модуль для Android приложения (androidMain, androidTest).

В них располагается наша бизнес-логика. Всю используемую в проекте бизнес-логику можно разделить на:
  • переиспользуемую (общую);
  • платформенную реализацию.

Переиспользуемая логика располагается в проекте commonMain в каталоге kotlin и разделяется на package. Декларации функций, классов и объектов, обязательных к переопределению, помечаются модификатором expect:

Реализации должны иметь модификатор actual.
В качестве примера работы с многопоточностью рассмотрим небольшое приложение, обращающееся к стороннему API по сети:

Я выбрала www.themoviedb.org. Полный код примера будет по ссылке внизу статьи.

В общей Common части расположим общую бизнес-логику:

А именно наш сетевой сервис. Это логично.
В модулях iOS/Android приложений оставим только UI компоненты для отображения списка и адаптеры. iOS часть будет написана на Swift, Android на Kotlin.

Начнем с бизнес-логики. Т.к весь функционал будет в модуле common, то мы будем использовать в качестве библиотек решения для Kotlin Multiplatform:

Ktor библиотека для работы с сетью и сериализации.

В build.gradle (:app) пропишем следующие зависимости:
val ktorVersion = "1.4.0"val serializationVersion = "1.0.0-RC" sourceSets {        val commonMain by getting {            dependencies {                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")                implementation("io.ktor:ktor-client-core:$ktorVersion")                implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")                implementation("io.ktor:ktor-client-serialization:$ktorVersion")            }        }        val androidMain by getting {            dependencies {                //...                implementation("io.ktor:ktor-client-android:$ktorVersion")            }        }        val iosMain by getting {            dependencies {                implementation("io.ktor:ktor-client-ios:$ktorVersion")            }        }        ...


Также добавим поддержку сериализации:
plugins {    //...    kotlin("plugin.serialization") version "1.4.10"}


Далее нам надо определить, что делать с многопоточностью, ведь она реализуется по-разному на каждой платформе. На стороне iOS мы используем GCD (Grand Central Dispatch), а на стороне Android JVM Threads и Coroutines. Однако, в Kotlin Multiplatform мы можем сделать общей и работу с многопоточностью.
Для этого мы будет использовать Kotlin Coroutines:
val coroutinesVersion = "1.3.9-native-mt" sourceSets {        val commonMain by getting {            dependencies {                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")               //...            }        }        val androidMain by getting {            dependencies {                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")               //...            }        }        val iosMain by getting {            dependencies {                //...            }        }        ...

Тут стоит сделать пояснение, как с этим работать, потому что далеко не все iOS разработчики знают, что такое Coroutines. Если вкратце, то это блок кода, который можно приостановить, не блокируя поток. У корутины может быть контекст выполнения (CoroutineContext), цикл жизни корутины управляется Job. У корутины есть область действия (CoroutineScope), а поток, в котором она исполняется, задается с помощью CoroutineDispatcher.
Если проводить аналогию с iOS, то это похоже на выполнение блока кода в DispatchQueue, имеющей определенный QoS и привязку к определенному потоку NSThread, либо Operation в OperationQueue, где GlobalScope аналогичен DispatchQueue.global(), а MainScope DispatchQueue.main:
//Androidfun loadMovies() {  GlobalScope.async {    service.makeRequest()   withContext(uiDispatcher) {//...}  }}

//iOSfunc loadMovies() {  DispatchQueue.global().async {    service.makeRequest()  DispatchQueue.main.async{//...}}}

Еще одной ключевой особенностью корутин является использование слова suspend. Данный модификатор не превращает метод в асинхронный сам по себе, это зависит от других деталей реализации, но маркирует, что его можно приостановить без блокировки потока. Также такой метод можно вызывать только в контексте корутины.
Ktor использует механизм корутины для реализации асинхронной работы, поэтому вызов HttpClient делаем в suspended функции:
//Network serviceclass NetworkService {     val httpClient = HttpClient {        install(JsonFeature) {            val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }            serializer = KotlinxSerializer(json)        }    }    suspend inline fun <reified T> loadData(url: String): T? {       return httpClient.get(url)    }}//Movies service suspend fun loadMovies():MoviesList? {        val url = MY_URL        return networkService.loadData<MoviesList>(url)    }

При подключении Kotlin Coroutines мы не указали никакую особую версию для iOS. Это не ошибка. Дело в том, что начиная с версии Kotlin 1.4 Suspended функция Kotlin легко трансформируется в функцию Swift c completion handler блоком:
func getMovies() {    self.networkService?.loadMovies {(movies, error) in       //...    }}

Т.к Ktor уже обеспечивает асинхронность, то в данном случае потребности в использовании DispatchQueue на стороне iOS нет.

На стороне Android используем механизм корутинов, и вызов будет иметь вид:
fun getMovies() {    mainScope.launch {    val movies = this.networkService?.loadMovies()//...    }}}


Такой способ обращения к общей логике мы можем использовать при подходе, когда у нас нет общего архитектурного элемента наших нативных приложений, и в Common проекте мы реализуем только бизнес-логику. Это вполне рабочий подход.

Если же мы хотим сделать максимально расшариваемый между наивными проектами общий код, включить туда архитектурное решение, а взаимодействие с UI через протоколы, то нам потребуется поменять работу и с потоками.

Посмотрим это в следующей части

Исходники примера github.com/anioutkazharkova/movies_kmp
Подробнее о работе корутин вы можете узнать тут

Источник: habr.com
К списку статей
Опубликовано: 19.12.2020 22:21:31
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка мобильных приложений

Kotlin multiplatform

Ios

Многопоточность

Kmm

Android

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru