[Видео] Как мы провели очередной Android Paranoid

Android почти исполнилось десять лет.

Мы решили отметить это праздничным чаепитием со всеми, кто пришел в питерский офис Яндекса на второй митап Android Paranoid. Сказано — сделано. К нашему сожалению, маршмеллоу, шоколадное печенье и желейные бобы закончились еще 28 марта.

jckvgnqrzkkxtve9lk-p_dl9riq.png

Вместо них — доклады, записанные на видео, и короткая выжимка полезной информации для Android-разработчиков. Под катом о том,

  • что происходит после нажатия на иконку приложения;
  • как перевести приложение на Kotlin и уместиться в 300 строк кода;
  • как менялись инструменты фоновой работы в Android;
  • как быстро получить анимации в RecyclerView.


Про анимации в RecyclerView


Данил Терновых из Яндекс.Денег рассказал о том, как быстро и без затрат получить анимации в RecyclerView.

Для тех, кто хочет попробовать их в работе — демо на GitHub. Подробности реализации — в видео.


«Что происходит после тапа на иконку приложения,


И даже чуть раньше?», — рассказал Владимир Теблоев из Сбербанк-Технологий. Рекомендуем посмотреть видео если вы думали, что вся жизнь в Android ограничивается вьюхами и активити, и ничего не знали про работу ядра, загрузчика, далвик, и все время задавались вопросом — зачем андроиду зигота? Для заинтересовавшихся — выжимка в трех эпизодах.

Эпизод 1 — Уровни системы и Zygote

Давным-давно инженеры молодой мобильной ОС спроектировали четыре уровня работы системы:

  • ядро с драйверами и Binder;
  • корневые библиотеки, библиотеки ОС и Dalvik;
  • Application Framework, неизменяемые компоненты системы — контент-провайдеры, активити-менеджеры и т.д.;
  • Пользовательские приложения.


bulzduhcrovyrzzpk5ferrpev2s.png

Чуть менее давно Dalvik превратился в Android Runtime, но сути процесса это не изменило — после тапа на иконку Launcher получает сигнал, передает его в менеджер активностей, тот передается в Zygote, а она создает новое приложение.

Zygote — демон, который запускается при старте системы и инициализирует первичную виртуальную машину. Zygote позволяет создавать процессы для любых приложений в Android, клонируя себя и корневые библиотеки, которые необходимы для запуска всех приложений. Так экономятся время и память, потому что в первичном экземпляре Zygote уже инициализированы все нужные библиотеки. Останется только использовать Copy-on-Write и изменить ProcessID.

e6i2tea-fyrh9o2ijbmo7d2mvwi.png
Слева — первичный экземпляр, в середине — Zygote, отвечающая за компоненты Android, справа — наше приложение.

Эпизод 2 — Жизненный цикл и взаимодействие процессов

В Android существуют пять типов процессов и три приоритета, которые им назначаются.

Критический приоритет назначается активным процессам — тем, с которыми пользователь взаимодействует прямо сейчас. Это может быть открытая активити или музыкальный плеер в UI.

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

Процессы низкого приоритета относятся к уже остановленным активити. Android сортирует такие процессы в порядке запуска и хранит в памяти, чтобы завершать их в порядке от старых к новым. Последняя категория — «зомби-процессы» — в них может быть инициализирован какой-то поток, но все компоненты жизненного цикла уже уничтожены.

Основной способ взаимодействия процессов — IPC через Binder. Это драйвер, через который работают все корневые структуры Android. Модель взаимодействий — на схеме ниже.

vthlfcv_lzevudd2opuukm2dah8.png

Предположим, что активити в процессе А должна получить данные из другого процесса. Метод Foo () обращается к Binder, который, в свою очередь, сериализует и упаковывает входные данные и передает целевому процессу для обработки. Затем нужные данные десериализуются, процесс Б что-то с ними делает и выполняет операции в обратном порядке.

Отдельно Владимир рассказал обо всех этапах создания активностей в Android. Все детали — в видео.


«Пользователь хочет 60 FPS,


Для этого и нужна фоновая работа».

Владимир Иванов из EPAM уже семь лет пишет под Android и iOS и успел похоронить Windows Phone. Владимир рассказал об эволюции инструментов для выполнения задач вне главного потока на Android. Речь о цепочке — AsyncTask, Loaders, Executors, EventBus, RxJava, Coroutines в Kotlin.

В докладе очень много примеров, здесь — малая часть.

Итерация 1 — AsyncTask

Допустим, мы пишем приложение, которое показывает прогноз погоды. Последовательность действий примерно следующая:

  1. Определяем метод DoInBackground ();
  2. С помощью http-клиента делаем запрос на сервер;
  3. Получаем и парсим ответ;
  4. Показываем пользователю.


На последнем этапе возникает сложность — мы не можем просто взять и вернуть ответ с фона на UI-поток. Если UI одновременно использует другие потоки, приходится продумывать костыли и сложные блокировки. Чтобы этого не делать, разработчики интерфейсов рекомендуют обновляться только с UI-потока.

Соответственно, нужен способ выполнить что-то на UI. В AsyncTask для этого используется метод onPostExecute, его и используем.

public class LoadWeatherForecastTask
extends AsyncTask < String,
Integer,
Forecast > {
	public void onPostExecute(Forecast forecast) {
		mTemperatureView.setText(forecast.getTemp());
	}
}


У этого подхода одна большая проблема и несколько минусов — AsyncTask умерли, за исключением production-проектов, которым больше пяти лет.

А минусы такие:

1) Много кода для сетевых запросов;
2) AsyncTask не знают ничего про жизненный цикл активностей и потенциально ведут к утечкам памяти;
3) При смене конфигурации на лету (например, экран перевернулся) нужно перевыполнить запрос.

Итерация 2 — Loaders

С Android 3.0 пришли Loaders — команда Android придумала их, чтобы решить проблемы AsyncTask.

class WeatherForecastLoader(context: Context) : AsyncTaskLoader < WeatherForecast > (context) {
	override fun loadInBackground() : WeatherForecast {
		try {
			Thread.sleep(5000)
		} catch(e: InterruptedException) {
			return WeatherForecast("", 0F, "")
		}
		return WeatherForecast("Saint-Petersburg", 20F, "Sunny")
	}
}


В частности, речь о повторном использовании результата при смене конфигурации. Проблема решается так:

1) LoaderManager, привязанный к активности, хранит ссылки на несколько Loader в специальной структуре;
2) Активность сохраняет все LoaderManager внутри NonConfigurationInstances;
3) При создании новой активности система передает в нее данные из NonConfigurationInstances;
4) Активность восстанавливает LoaderManager со всеми Loader.

Минусы:
1) Все еще много кода;
2) Интерфейсы все еще сложные, а классы все еще абстрактные;
3) Loaders — платформенный API Android, а значит, их нельзя переиспользовать на чистой Java.

Итерация 3 — EventBus и ThreadPoolExecutors

С появлением ThreadPoolExecutors передача данных с фона на UI стала выглядеть так:

1) Заводим класс Background, а в нем — переменную Service;
2) Инициализируем этот класс в ScheduledThreadPoolExecutor с нужным нам размером;
3) Пишем вспомогательные методы, которые делают класс runnable или callable.

public class Background {
	private val mService = ScheduledThreadPoolExecutor(5)
	fun execute(runnable: Runnable) : Future < *>{
		return mService.submit(runnable)
	}
	fun < T > submit(runnable: Callable < T > ) : Future < T > {
		return mService.submit(runnable)
	}
}


Кроме выполнения на фоне, все еще нужно возвращать результат на UI. Для этого пишем handler и метод, который что-то постит на UI-треде.

public class Background {…private lateinit
	var mUiHandler: Handler
	public fun postOnUiThread(runnable: Runnable) {
		mUiHandler.post(runnable)
	}
}


Не весь UI должен знать, что какой-то конкретный метод выполнился. Чтобы разделить ответственность, придумали EventBus. Это способ передачи событий из фонового потока на UI, при котором на общую шину подключены несколько слушателей, которые и обрабатывают эти события.

dwcc1jfwhuzzrg0rry-okgwyhum.png

Есть несколько готовых реализаций EventBus. Некоторые из них — Google Guava, Otto и GreenBot Eventbus.

Из минусов:

  1. Источник данных о событии ничего не знает о том, как оно должно обрабатываться;
  2. По опыту докладчика, через некоторое время код с EventBus становится невозможно поддерживать.


Итерация четвертая — RxJava, или «Хватит это терпеть!»

Кто-то должен был придумать удобный инструмент для фоновой работы. В итоге у нас есть RxJava — большой фреймворк для работы с потоками событий.

Предположим, мы пишем код, который должен авторизовываться на GitHub. Нужно завести по методу на каждую нужную операцию (в нашем случае — логин и получение списка репозиториев).

interface ApiClientRx {
	fun login(auth: Authorization) 
	    : Single < GithubUser > 
	fun getRepositories(reposUrl: String, auth: Authorization) 
	    : Single < List < GithubRepository >>
}


Результатом выполнения будет Single — поток из нуля или одного события. Итог работы — интерфейс из двух методов, которые возвращают все, что нужно пользователю.

Минусы:

  1. Крутая кривая обучения, учить долго и сложно;
  2. Много операторов, разницу между которыми сложно понять;
  3. На простой код из двух запросов и двух операторов создается около 20 объектов, что ведет к избыточному использованию памяти;
  4. Нерелевантные stacktrace, из 30 строк только одна может относиться к вашему коду.


Плюсы:

  1. RxJava — стандарт де-факто. Владимир провел опрос в твиттере и выяснил, что 65% разработчиков в новых проектах будут использовать RxJava;
  2. Мощный API;
  3. RxJava — фреймворк с открытым исходным кодом, у которого есть большое сообщество;
  4. Код на RxJava легко покрывается юнит-тестами.


Итерация пятая — Coroutines

Coroutines — библиотека для фоновой работы с поддержкой внутри языка Kotlin.

Ее плюсы:

1. Не блокирующий подход — основной поток выполняется во время фоновой работы и встраивает в себя результаты по мере выполнения;
2. Асинхронный код в синхронном стиле

private fun attemptLogin() {
	launch(UI) {
		val auth = BasicAuthorization(login, pass) try {
			showProgress(true) val userInfo = login(auth).await() val repoUrl = userInfo.repos_url val list = getRepositories(repoUrl, auth).await() showRepositories(this@LoginActivity, list.map {
				it - >it.full_name
			})
		} catch(e: RuntimeException) {
			Toast.makeText(this@LoginActivity, e.message, LENGTH_LONG).show()
		} finally {
			showProgress(false)
		}
	}
}


3) Средства языка вместо операторов;
4) Просто изучать — кривая обучения почти не кривая;
5) После небольшого обдумывания юнит-тесты становятся почти такими же, как для синхронного кода.

Минусы:

1) Недавно вышли из статуса экспериментальных;
2) Это не часть языка, а библиотека;
3) Не для всего есть адаптеры;
4) Coroutines — не замена RxJava. Они не сильно помогут в сложных случаях со сложным потоком событий.

Остальное про Coroutines (включая примеры) лучше послушать в докладе:


Как уместить код в 300 строк, программируя на Kotlin


Год назад, на Google IO 2017 анонсировали то, что Kotlin стал официальным языком Android. Доклад Юрия Чечеткина из Альфа-Банка о том, как начать переезжать на новый язык, сократить классы до 300 строк и не сойти с ума.

Доклад — практический ликбез по тому, как писать компактно и красиво. Он ориентирован на продвинутую аудиторию, которая знает основные особенности Kotlin.

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

Основные проблемы с миграцией на Kotlin:

  1. Legacy-код на Java. Большие классы на несколько тысяч строк кода очень сложны для конвертации средствами среды разработки;
  2. Зависимости — Lombok, Stream API и т.д.;
  3. Завышенные требования к коду внутри команды. Проводятся регулярные автоматические проверки кода на ограничение в 300 строк и code review;
  4. Kotlin — новый язык, и сложно сформулировать требования, пока нет единых конвенций;
  5. Kotlin компилируется дольше;
  6. Синтаксический сахар — «большая сила, но большая ответственность».


wpa1cm8zy4f-rivgzyme6xv0_gi.png

Выводы:

  • Спустя год использования Kotlin стало меньше кода — он стал чище, стало удобнее делать code review;
  • Нет старых методов Java, нет лишних зависимостей, только стандартные возможности языка;
  • Меньше костылей и багов;
  • Больше возможностей — некоторые вещи, реализуемые на Kotlin, невозможно написать на Java.


Остальное — в видео.


Следите за мероприятиями и буднями команды Я.Денег в ВК, фейсбуке и инстаграме.
Все конференции и митапы Яндекса — на Я.Встречах.

© Habrahabr.ru