Применяем Kotlin Coroutines в боевом Android-проекте

by lazy may be very useful when implementing read-only properties that perform lazy-initialization in Kotlin.

Null или не null?

Null safety — одна из ключевых особенностей Kotlin. Язык гарантирует, что программист не сможет по ошибке вызвать методы объекта, имеющего значение null, или передать этот объект в качестве аргумента другим методам. На практике это означает, что система типов Kotlin разделена на две ветви, в одной из которых существуют nullable-типы (со знаком вопроса на конце), а в другой — типы, у которых может быть только имеющее смысл значение.

Ты можешь написать такой код:

var string: String? = null

Но не такой:

var string: String = null

Не nullable-тип String просто не может содержать значение null.

Язык имеет ряд операторов для удобной и надежной работы с nullable-типами:

// Присвоить переменной length значение одноименного свойства string1 либо null val length = string1?.length // Выполнить код в блоке, только если string1 не null string1?.let { (string1) } // Заверить компилятор, что в данный момент значение string1 не может быть null string1!!.length // Объявить переменную не nullable-типа и заверить компилятор, // что она будет проинициализирована позже, до первого использования lateinit var recyclerView: RecyclerView

В целом удобная и понятная система. Но есть оператор, который может сделать код еще более компактным и красивым. Оператор Elvis вычисляет выражение слева и, если его результат null, вычисляет выражение справа. Он особенно удобен в двух случаях. Во-первых, его можно использовать, чтобы присвоить переменной дефолтное значение:

val name: String = ?: «unknown»

Во-вторых, чтобы вернуть управление из функции, если значение определенной переменной равно null:

fun addPerson(person: Person) { val name = ?: return }

Но есть и подводные камни. Допустим, у нас есть следующий код:

data?.let { updateData(data) } ?: run { showLoadingSpinner() }

Может показаться, что этот код делает то же самое, что и такой код:

if (data != null) { updateData(data) } else { showLoadingSpinner() }

Но это не так. Последний пример кода полностью бинарный: либо первый блок, либо второй. А вот в предыдущем фрагменте кода могут быть выполнены оба блока! Это возможно, если функция updateData(data) сама вернет null. Тогда все выражение слева вернет null и будет выполнен код справа от оператора Elvis. Обойти эту проблему можно, заменив let на apply.

Еще одна вещь, которую следует помнить о null safety, — это автоматическое выведение типов. Компилятор (и плагин среды разработки) Kotlin достаточно умен, чтобы понять тип переменной даже в самых сложных случаях. Но иногда он дает сбои.

Такое может быть при параллельной обработке данных. Возьмем следующий пример:

class Player(var tune: Tune? = null) { fun play() { if (tune != null) { () } } }

Среда разработки сообщит, что не выведет тип tune, потому что это изменяемое свойство. Компилятор не может быть уверен, что между проверкой tune на null и вызовом метода play() другой поток не сделает tune = null.

Читайте также:  Com android systemui: что это, как удалить?

Чтобы это исправить, достаточно сделать так:

class Player(var tune: Tune? = null) { fun play() { tune?.play() } }

Или так:

class Player(var tune: Tune? = null) { fun play() { tune?.let { val success = () } } }

Сбои могут возникать и при взаимодействии с кодом на Java. В Java понятия null safety нет, поэтому компилятор не может знать тип переменой наверняка. Kotlin решает эту проблему двумя способами:

  1. Компилятор Kotlin поддерживает практически все разновидности nullable-аннотаций, поэтому, если код аннотирован с помощью @NotNull и ему подобных аннотаций, компилятор будет считать, что аннотированная переменная не может быть null.
  2. Для взаимодействия с Java (и другими языками для JVM) в Kotlin есть специальные типы с восклицательным знаком на конце (например, String!, Integer!). Это так называемые platform types, и при работе с ними Kotlin использует ту же логику, что и Java. Однако ее можно изменить, если указать тип напрямую:

    val docBuilderFactory: DocumentBuilderFactory = ()

Далее DocumentBuilderFactory будет считаться не nullable-переменной.

Типы Unit, Nothing, Any в Kotlin

Система типов Kotlin несколько отличается от системы типов Java и может вызвать у незнающего человека много вопросов. Наиболее проблемными обычно оказываются типы Unit и Nothing.

  • Unit — эквивалент типа void в Java. Другими словами, он нужен для того, чтобы показать, что функция ничего не возвращает. Unit наследуется от типа Any, а при работе с Java-кодом автоматически транслируется в void.

  • Nothing — субкласс любого класса (именно так), не позволяющий создать объект своего типа (конструктор приватный). Используется для представления результата исполнения функции, которая никогда не завершается (например, потому что она выбрасывает исключение). Пример:

    public inline fun TODO(): Nothing = throw NotImplementedError() fun determineWinner(): Player = TODO()

  • Any — родитель всех остальных классов. Аналог Object в Java.

Создание Activity на Kotlin

В Android Studio нажмите правой кнопкой мыши на имени вашего пакета и выберите New > Kotlin File.

Создание Activity на Kotlin

В диалоговом окне, введите имя новой Activity и выберите Class из выпадающего списка. Я назвал свой класс MainActivity.

После того, как класс был создан, вы увидите предупреждение о том, чтобы вы настроили модуль приложения для поддержки Kotlin.

Создание Activity на Kotlin

В алерте нажмте на ссылку и во всплывающем окне нажмите OK для выбора значений по умолчанию.

Для настройки поддержки Kotlin в вашем проекте, плагин Kotlin сделает некоторые изменения в файле . Примените изменения настроек нажатием на кнопку Sync Now сверху.

Создание Activity на Kotlin

На этом шаге настройка проекта завершена. Вернитесь к вашему Kotlin-классу для начала кодинга.

Схема Lifecycle

А сейчас посмотрите на схему состояний, через которое проходит активити в процессе работы, и событий, которые при этом происходят. Схема с официального сайта

В прямоугольниках указаны состояния (states) активити. А события (events), которые происходят при смене состояний, отмечены стрелками. Важно понимать, что не события управляют состояниями активити, скорее наоборот. Состояния активити изменяются системой Андроид, а события происходят в процессе изменения.

Когда активити запускается системой, оно инициализируется (INITIALIZED) и происходит событие ON_CREATE. При этом активити переходит к состоянию “создано” (CREATED).В этот момент должен инициализироваться пользовательский интерфейс, поскольку активити готовится отобразиться пользователю. Далее происходит событие ON_START и активити переходит к состоянию “запущено” (STARTED). Следующее событие ON_RESUME. Активити переходит в состояние — RESUMED (возобновлено) — выходит на передний план, получает фокус и отображается пользователю. Если активити в процессе работы теряет фокус и частично перекрывается, например, диалоговым окном или другим активити, то переходит обратно в состояние STARTED. При этом происходит событие ON_PAUSE. В этом состоянии активити приостанавливается, но может быть все еще видимым на экране, например, в многооконном режиме. Если же активити полностью перекрыто, то система его останавливает и переводит в состояние CREATED. Выполняется событие ON_STOP. Активити пока не уничтожается системой и может в любой момент возобновить работу. Но поскольку оно не видно пользователю, то в этом состоянии целесообразно отключать некоторые функции, например, воспроизведение анимации. Если пользователь закрыл активити или система испытывает недостаток памяти, или изменилась конфигурация устройства (произошел поворот), активити уничтожается системой. При этом происходит событие ON_DESTROY. В этот момент необходимо освобождать ресурсы, используемые активити.

Плагины для настройки lazy loading

Собрали несколько плагинов и решений для популярных движков.

Настроить ленивую загрузку: скрипт на JavaScript

Скрипт для отложенной загрузки, работает на чистом JavaScript и не зависит от сторонних библиотек.

Возможности:

  1. Запускает ленивую загрузку фоновых и добавочных изображений и картинок в прокрутке.
  2. Работает со всеми элементами с атрибутом src, видео HTML5, фреймы.
  3. Поддерживает srcset и элемент picture.
  4. Загружает фотографии асинхронно с учетом разрешения и размера экрана.
  5. Поддерживается устаревшими браузерами.
  6. Весит меньше 1,4 КБ.
  7. Использует CDN для размещения.

Реализация стандартная. Разметка:

<img class=»b-lazy» src=»» data-src=»» alt=»test image»>

Страница скрипта на GitHub

Размыть изображение: Craig Buckler

Перед загрузкой в высоком качестве можно настроить разное отображение для картинки — сжатие, размытие, шаблонная заглушка. Плагин Craig Buckler позволяет настроить размытое отображение перед загрузкой.

Возможности:

  1. Поддерживает любой тип изображений, включая CSS фоновые изображения, атрибуты srcset и sizes.
  2. Быстро загружается и работает, занимает 1350 байт JavaScript, 440 байт CSS.
  3. Не зависит от фреймворков и библиотек.
  4. Поддерживает экраны с матрицами Retina.
  5. Применяет прогрессивное улучшение для устаревших браузеров.
  6. Делает до трех попыток, если изображение по какой-то причине не загрузилось.

Страница плагина на GitHub

Проще установить специальные плагины для CMS, которые запустят отложенную загрузку. Некоторые вообще не требуют настройки.

WordPress

Запуск WordPress 5.4 планируется в конце марта 2020 года, а пока можно справляться с помощью плагинов. Мы собрали самые лучшие плагины отложенной загрузки для WordPress.

Лучший плагин для lazy load WordPress: исследование

Хостинг Kinsta провел исследование популярных плагинов для lazy loading, чтобы выяснить, какие работают эффективнее.

Тест скорости без плагинов для ленивой загрузки

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

3 лучших плагина WordPress для отложенной загрузки

  • BJ Lazy Load — дополнительно позволяет настроить отложенную загрузку: указать класс HTML для исключений из асинхронной загрузки, который вы сможете применять к элементам страницы, указать, в какой момент загружать картинку или фрейм, добавить URL-адрес изображения для заглушки на месте картинки до ее загрузки в хорошем качестве.
  • Lazy Load XT — кроме ленивой загрузки, плагин можно использовать для минимизации файлов JS и CSS, загрузки библиотек JavaScript и CSS с помощью Cloudflare CDN, перемещения скриптов в нижний колонтитул сайта.
  • a3 Lazy Load — бесплатный плагин на WP, совместим с плагинами WooCommerce, AMP, WPTouch, WP Super Cache, W3 Total Cache и CDN. Позволяет настроить типы объектов для асинхронной загрузки и выбрать миниатюры, виджеты, граватары и другие, настроить загрузки видео и iframe. Переведен на русский.

Плагин a3 Lazy Load смог довести размер изображений до 150 КБ. У BJ Lazy Load и Lazy Load XT получились схожие результаты — размер страницы 2,0 МБ. Сравнение результатов внедрения плагинов показало, что a3 Lazy Load справился лучше.

Сравнение плагинов для lazy load

Исследование проводили в 2016 году, материал обновили в 2019, но в подборке остались плагины, которые не обновлялись по три месяца. Не смотря на это, они могут корректно работать, но вы можете подобрать другое решение, которое будет работать на вашем сайте.

Еще популярные плагины:

Другие плагины lazy load для WP

1-C Битрикс

Для этого движка удалось найти два плагина:

OpenCart

Плагины для настройки lazy loading

Несколько расширений ленивой загрузки для OpenCard:

  • Lazy Load Images — бесплатный плагин, настраивает ленивую загрузку по прокрутке, работает на jQuery.
  • Lazyload Images — плагин для асинхронной загрузки с возможностью настроить визуальные эффекты для появляющихся изображений. Поддерживает Opencart версии 1.5.X + 2.X + 3.0.X + 3.1.X. Цена 20 долларов.
  • Lazy Load Images — плагин для ленивой загрузки изображений, каруселей, слайд-шоу, работает на JavaScript. Работает с версиями OpenCart v3.x. Цена 20 долларов.

Другие расширения lazy load для OpenCart

Joomla!

Для Joomla! нашлась пара плагинов:

Drupal

Для Drupal есть несколько модулей асинхронной загрузки.

  • Lazy-load — модуль Drupal для отложенной загрузки встроенных изображений и фреймов, работает на библиотеке lazysizes.
  • Blazy — плагин обеспечивает интеграцию с bLazy и Intersection Observer API для асинхронной загрузки изображений и фреймов.
  • Bamboo Twig — модуль на Drupal 8 для повышения производительности, в том числе в нем можно включить отложенную загрузку.
  • Image Lazyloader — модуль для ленивой загрузки картинок, подходит для версий Drupal и выше.
  • Lazy Pane — плагин загружает картинки по требованию со стороны клиента из кэша, через AJAX.

Другие модули в списке для Drupal

Выводы

Естественно, в одной статье невозможно раскрыть все аспекты нового инструмента в асинхронном программировании. Тем, кто заинтересовался корутинами Kotlin, можно посоветовать изучить и протестировать, к примеру, комбинирование корутин, каналы, реализацию Actor model и другие возможности.

Несмотря на то, что в рамках примера показана довольно банальная задача — скачать данные с сервера, что делается почти в каждом приложении, он иллюстрирует принцип работы с корутинами. Вместо скачивания может быть что угодно. Пример показывает, как с помощью корутин удобно обернуть любую операцию.

Как мы видим, задачи асинхронного программирования под Android проще реализовать с помощью корутин: быстрее разработка, выше читаемость кода. Риски, конечно, тоже есть: для изучения корутин нужно некоторое время, а их возможности иногда могут не закрыть все требования задачи. Перед использованием в своем проекте рекомендуется внимательно ознакомиться с областью их применения.

Официальный сайт разработчиков Kotlin:

Анонс релиза Kotlin 1.3.0 с Coroutines: -1-3/