Меня зовут Андрей Данилов, под Android начал разрабатывать в далеком 2012 году. Успел поработать примерно везде — в крошечном стартапе, маленькой продуктовой компании, аутсорсах и крупных компаниях, таких как Авито, Ситимобил, Яндекс. Успел выступить примерно на десятке митапов и конференций. Сейчас член Программного комитета конференции Apps conf X. В данный момент руковожу парой команд в Т-Банке.

Развитие Android разработки глазами разработчика

Довольно много времени прошло с 2007 года, когда Android стал доступен для разработчиков. С тех пор прошло почти 18 лет. За это время изменилось примерно все: железо стало мощнее, интернет быстрее, IDE умнее, а проекты сложнее. Предлагаю оглянуться назад и посмотреть, что же именно изменилось с тех пор для разработчиков. 

Ранний Android 2007 - 2014

Система сборки Ant

Первое, что бросилось бы в глаза современному разработчику — это отсутствие Gradle для сборки. Вместо нее использовался Ant. В те времена вообще можно было подумать, что для Android используется XML driven development. Вся верстка в XML, скрипт сборки в XML, строки и ресурсы в XML и даже сохраненные настройки (SharedPreferences) под капотом все тот же XML. Часть использования XML, конечно, дожила и до наших дней, но от части все же удалось избавиться, спасибо и на этом.

Использование Ant выглядело примерно так — в проекте был build.xml, и все задачи сборки (в Ant они называются targets) нужно было прописывать в нем:

<?xml version="1.0" encoding="UTF-8"?>
<project name="HelloWorld" default="help">
    <!-- Определяем свойства проекта -->
    <property name="sdk.dir" location="/path/to/android/sdk" />
    <property name="build.target" value="android-19" />

    <!-- Настраиваем путь к исходным файлам -->
    <path id="source.path">
        <fileset dir="${src.dir}">
            <include name="**/*.java" />
        </fileset>
    </path>

    <!-- Путь к ресурсам -->
    <path id="resource.path">
        <fileset dir="${res.dir}">
            <include name="**/*" />
        </fileset>
    </path>

    <!-- Включаем библиотеки и внешние JAR файлы -->
    <path id="lib.path">
        <pathelement path="${sdk.dir}/platforms/${build.target}/android.jar" />
     </path>

     <!-- Задача для компиляции исходного кода -->
     <target name="compile">
         <javac srcdir="${src.dir}" destdir="${bin.dir}" includeantruntime="false">
             <classpath>
                 <path refid="lib.path" />
             </classpath>
         </javac>
     </target>

     <!-- Задача для создания ресурсов -->
     <target name="create-resources">
         <aapt executable="${sdk.dir}/build-tools/${build.target}/aapt.exe"
               ignoreAssets="true" verbose="true">
             <res path="${res.dir}" />
         </aapt>
     </target>

     <!-- Задача для упаковки APK файла -->
     <target name="package-apk">
         <jar jarfile="${out.dir}/${ant.project.name}.apk" basedir="${bin.dir}"/>
         <zipalign executable="${sdk.dir}/build-tools/${build.target}/zipalign.exe" 
           verbose="true" outfile="${out.dir}/${ant.project.name}-aligned.apk" 
           inputfile="${out.dir}/${ant.project.name}.apk"/>
     </target>
</project>

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

IDE - Eclipse and NetBeans

Конечно, к современной среде разработки пришли не сразу. До Android Studio, сделанной на базе Intellij IDEA, вся разработка под Android (так же, как и под Java, к слову) велась либо в Eclipse, либо в NetBeans. Обе IDE вполне успешно могут работать с Android проектами до сих пор, конечно же, при условии разработки на Java — Kotlin-плагины уже не поддерживаются. Разница между ними была примерно как между Coca Cola и Pepsi — чисто вкусовщина. NetBeans предоставлял чуть более дружелюбный и не перегруженный интерфейс, Eclipse имел большее количество плагинов. Из минусов этих IDE — отсутствие хороших инструментов вроде умного рефакторинга, анализатора кода, а также прямой поддержки Google.

Fun Fact

Первые версии Intellij IDEA и Eclipse появились в 2001 году. Чтобы для Android разработки обойти по популярности Eclipse, Intellij IDEA понадобилось 7 лет, причем не без помощи поддержки Google. И 15 лет, чтобы обойти Eclipse по популярности в Java.

RoboGuice

RoboGuice был, по сути, первым по популярности фрэймворком для Dependency Injection до Dagger и даже местами соответствовал JSR-330. Работал с View, ресурсами и кастомными классами. Но выглядел он максимально странно:

@ContentView(R.layout.main)
class RoboWay extends RoboActivity { 
    @InjectView(R.id.name)             TextView name; 
    @InjectView(R.id.thumbnail)        ImageView thumbnail; 
    @InjectResource(R.drawable.icon)   Drawable icon; 
    @InjectResource(R.string.app_name) String myName; 
    @Inject                            LocationManager loc; 

    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        name.setText( "Hello, " + myName ); 
    } 
} 

Да, какие-то строчки кода это экономило, но только за счет кодогенерации. Получалось, что для маленьких проектов он добавлял экономию на спичках — экономию 1-2 строки для каждой сущности, что совсем незначительно. Зато для крупных проектов RoboGuice добавлял минуты холодной сборки. Страшно подумать, что было бы сейчас в случае с проектами, состоящими из многих сотен экранов. В общем, уже к 2013 году проект по факту перестал поддерживаться, а вскоре и использоваться.

ActionBarSherlock

В древние времена, когда 4 андроида еще не существовало, сделать приличный Action Bar было довольно тяжко. В общем-то, ActionBarSherlock решал эту проблему и при этом использовал нативный ActionBar в 4 андроиде, так что в какой-то момент использовался почти повсеместно.

AsyncTask

AsyncTask был частью стандартного SDK, но поистине легендарной частью. Основная проблема была в том, что из-за обилия неопытных разработчиков, засилья именно Async Task во всевозможных курсах и гайдах по андроид разработке, а также отсутствия хороших архитектурных практик по разделению слоев (привет Clean Architecture) сами Async Tasks создавались, как и все остальное — прямо в коде Activity. Это приводило к созданию анонимных внутренних классов с неявной ссылкой на Activity и дальнейшим утечкам памяти или даже падениям приложения. Например, в случае переворота экрана. Выглядело это так:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         textView.setText(“Progress ” + progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

Стоило перевернуть экран — textView переставал существовать, но сам Async Task продолжал работу и при попытке поменять текст сразу клал все приложение на лопатки.

Масштабы безумия были столь массовыми, что на долгое время этот кейс внесли в вопросы на собеседованиях Android разработчиков. К счастью, с 30 API уже Deprecated.

EventBus

Из-за отсутствия адекватных, сравнительно простых и удобных механизмов обновления UI появилась библиотека EventBus. Концепция была простой, как пробка:

Надо было просто зарегистрировать прием события:

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {
      // Do something
}

Зарегистрировать прием сообщений:

@Override
public void onStart() {
      super.onStart();
      EventBus.getDefault().register(this);
}

И в любом месте дергать его отправку:

EventBus.getDefault().post(new MessageEvent());

Удобно? Да вполне. На тот момент даже показалось глотком свежего воздуха. Но позже начались проблемы. По мере роста проектов довольно скоро оказалось, что количество таких событий может исчисляться десятками и сотнями. Они могут прилетать из разных частей приложения и приводить к багам, которые так просто не вычислить. Тестировать такое тоже было не очень-то удобно. Как итог, использование этой библиотеки довольно быстро было прекращено, а в комьюнити стало автоматически ассоциироваться с лапшой в коде.

Архитектура MVC

Если не брать в расчёт проекты с God Object Activity, которые отвечали примерно за все вообще, то основной архитектурой был MVC. По разбивке на слои выглядел он так:

Model — логика, данные и т.д.

View — XML с версткой

Controller — Activity / Fragment 

Очевидно, при этом подходе Activity было перегружено ответственностью и при этом из-за жесткой привязки к Android фрэймворку усложняло тестирование unit тестами.

Эра бурных перемен 2014 - 2018

Android Studio

Android Studio появилась в 2014 году и используется активно до сих пор. Да, первые версии были настолько кривыми и косыми, что после нескольких дней использования мне пришлось пересесть на Eclipse. Тем не менее спустя полгода после релиза проблемы были починены, а позиция основной IDE для Android разработки стала нерушимой.

Gradle

Первая версия Gradle появилась еще в 2009. Основным языком выбрали Groovy, так как он, с одной стороны, привязан к JVM, с другой стороны — с динамической типизацией, что удобно для скриптов и довольно понятно Java-разработчикам. Это было особенно важно, учитывая, что для рядового разработчика с Ant и Maven работать было сложнее. Помимо этого, Groovy на тот момент был весьма модным языком, так что классический hype driven development имел место быть.

Для Android плагин вышел в 2013, но быстро приобрел популярность уже в 2014 году, после чего про Ant практически полностью забыли.

Material Design

Уже мало кто помнит, но до Material Design был HOLO, который был сосредоточен на минимализме и выглядел примерно так:

В те времена очень часто можно было услышать что-то типа «вот Apple заботится о разработчиках и пользователях, сделали нормальный дизайн дефолтных компонентов, а на Android все выглядит в стиле сайтов 90х». И в какой-то степени это было правдой. До Material Design стили были максимально простыми и некрасивыми. Разумеется, в этом были и технические причины — темные цвета способствовали чуть более долгой работе аккумулятора, а отсутствие наворотов положительно сказывалось на скорости рендеринга элементов. Material Design появился, когда телефоны уже худо-бедно стали справляться с большей нагрузкой. Хотя первое время некоторые элементы Material Design, например, тени, могли провоцировать сокращение фрэймов. Выглядел Material Design тогда так:

RxJava

Асинхронных запросов год к году меньше не становилось. Дошло до того, что в некоторых компаниях умудрились столкнуться с ограничением GCD в 64 треда на iOS. Учитывая, что дефолтные механизмы, такие как AsyncTasks, Threads, Loaders были довольно ущербными в итоге пришли концепции реактивного программирования и ее Java имплементации — RxJava.

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

public static <A, B> Observable<CombinedResult<A, B>> combineObservablesParallel(Observable<A> observable1, Observable<B> observable2) {
	return observable1.flatMap(new Function<A, ObservableSource<CombinedResult<A,B>>>() {
    	@Override
    	public ObservableSource<CombinedResult<A,B>> apply(A a) throws Exception {
        	return Observable.just(a).zipWith(observable2, new BiFunction<A, B, CombinedResult<A, B>>() {
            	@Override
            	public CombinedResult<A, B> apply(A a, B b) throws Exception {
                	return new CombinedResult<>(a,b);
            	}
        	});
    	}
	});
}

Этот кусок со Stackoverflow просто объединял результат 2 observables в 1. Да, сейчас он бы точно выглядел иначе, но тогда это было так.

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

Так или иначе, эпоха RxJava прошла, и основная причина этому — излишняя для популярной библиотеки сложность.

Volley

Библиотека Volley, на мой взгляд, была вечным, всегда вторым номером после Retrofit. Да, это было точно лучше, чем HttpUrlConnection, но до Retrofit не дотягивала. Основных причин две: проблемы с производительностью тяжелых запросов и не самый удачный API. В нем не было ни поддержки работы с RxJava и корутинами, ни удобства написания сложных запросов, а в какой-то момент большинство запросов стали именно такими.

Retrofit

Основным инструментом для работы с сетевыми запросами как был, так и остается Retrofit. Появился он еще в далеком 2013 году, но популярность приобрел спустя несколько лет и стал стандартом индустрии. Причины популярности — OkHTTP под капотом, который также стал стандартом. Благодаря ему Retrofit успешно справлялся с сетевыми запросами любого объема, а также в целом был удобный и простой API с парсингом сразу в POJO, поддержкой RxJava и корутин.

Архитектуры MVP / MVVM

После роста размера приложений стало очевидно, что MVC крайне печально справляется с задачей построения расширяемой архитектуры. Так что с 2015 комьюнити стало искать новые варианты. Сначала был Model View Presenter с Mosby, а потом с Moxy. Доклады про архитектуру заполонили все конференции, и вплоть до 2018 года включительно можно было прийти и в 100500 раз послушать, как кто-то переехал на новую архитектуру в %company_name%.

В 2017 году на Google IO появились Google Architecture Components с LiveData и ViewModel. Проблем и возни с ними было значительно меньше, чем с MVP. И все радостно переехали на Model View ViewModel.

Dagger 2

Dagger 2 вышел еще в далеком 2015 году и довольно быстро завоевал популярность, став стандартом на многие годы. Да, порог входа был довольно высоким. Да, кодогенерация проекта отжирала все больше времени сборки. Но все это с лихвой компенсировалось проверкой корректности графа во время компиляции и хорошим уровнем экспертизы в комьюнити. Периодически появлялись альтернативы — сначала Toothpick, а с приходом Kotlin еще Koin и Kodein, но чем-то настолько же массовым, они так и не стали.

Kotlin

Kotlin, вообще говоря, появился раньше, в 2011, но полноценно использовать под Android его стали с 2015-2016. Google IO 2017 года только закрепил его статус, сделав третьим официально поддерживаемым языком на Android (после Java и С++). А в 2019 году Google даже объявили Kotlin-first подход. Причины всем понятны — Kotlin удобнее, короче, гораздо быстрее развивается, тогда как Java погрязла в поддержке обратной совместимости легаси проектов и довольно консервативном комьюнити.

По большей части Android разработчики массово перешли на Kotlin с 2017. Однако в «кровавом энтерпрайзе» процесс был медленнее — сначала оценивали риски, потом вырабатывали процесс обучения сотрудников. Кое-где даже надо было сдать экзамен, чтобы разрешили писать на Kotlin. Так или иначе, к настоящему времени абсолютное большинство Android проектов использует именно этот язык.

Современность 2018 - 2025

Coroutines + Flow

Сами по себе корутины появились еще в 2017, но съезд с RxJava был затруднен из-за отсутствия всех необходимых операторов. Однако чуть позже, с появлением Flow, все эти проблемы сошли на нет.

Плюсы миграции были всем очевидны.

Во-первых, с корутинами и Flow было легче работать, был меньше порог входа.

Во-вторых, из-за kotlin-first подхода в Google многие системные вызовы стали из коробки поддерживать корутины. Да и сами корутины, по сути, были частью языка.

Примерно с 2020 началась массовая миграция с RxJava на Coroutines и с тех пор корутины стали нерушимым стандартом.

Gradle Kotlin DSL

До 2018 года в Gradle безраздельно властвовал Groovy. Да, можно было какие-то части написать на Java, например, плагины, но это было скорее редкостью и исключением из правил.

Я думаю, поддержка Kotlin DSL в Gradle — это самый недооцененный пункт из всех, что здесь есть. Посудите сами, если раньше разработчик, залезая в build.gradle, наблюдал там Groovy, а также полное отсутствие мотивации лишний раз копаться в премудростях незнакомого для себя языка, то сейчас там Kotlin — тот же самый язык, на котором пишется код. Это важно, так как разработка не становится проще, а ситуация, в которой работа типичного Android разработчика с Gradle строилась по принципу карго-культа (что нагуглил по теме, то и скопировал, не задумываясь, как оно вообще работает), наконец-то сходит на нет.

Jetpack Compose

Анонсированный буквально за месяц до SwiftUI (Совпадение? Не думаю!) в мае 2019 года Jetpack Compose доехал до состояния production ready только в 2021 году. Однако простота внедрения и разработки, а также поддержка древних версий Android сделали свое дело, и внедрение пошло довольно быстро, в первую очередь за счет новых проектов в стартапах и аутсорсе, а также прогрессивных крупных компаний.

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

Архитектура MVI

Однонаправленная архитектура Model View Intent по факту появилась еще в 2016, но массового применения не нашла. Она была довольно сложной в применении, с довольно узкими кейсами эффективного использования. В основном использование ограничивалось сложными экранами вроде чатов, где довольно много обновления UI, а также много источников данных, хотя некоторые компании вроде Badoo строили на MVI всё приложение целиком.

С выходом Jetpack Compose архитектура MVI приобрела чуть больше смысла. Хотя бы потому, что сущность State уже в любом случае существовала в явном виде. В целом из-за все более сложных экранов и все большего количества состояний на них архитектура в данный момент имеет все больше смысла.

Итоги

Сейчас Android разработка стабилизировалась. Это уже далеко не молодая область, в которой часто меняются подходы к разработке, часто выходят новые фреймворки и библиотеки, меняющие всё. Сами посудите — с 2014 по 2018 год изменений было кратно больше, чем за предыдущие 7 лет. В какой-то мере можно сказать, что мы пришли к стагнации — имеющиеся технологии как-то развиваются, но ничего принципиально нового не происходит. Чтоб не пропустить новые тренды и не утонуть в рутине покраски кнопок, приходите на AppsConf 2025. Вместе посмотрим, все ли настолько плохо как кажется, и сможет ли AI что-то изменить в текущей парадигме.

Комментарии (5)


  1. dsoastro
    10.02.2025 18:39

    Самое неприятное изменение - это Гугл, закручивающий гайки и постоянно что-то меняющий. Желание программировать на его Андройде пропало окончательно


    1. FirsofMaxim
      10.02.2025 18:39

      А вечные alpha/beta в jetpack либах? Или стала например stable и через несколько релизов - deprecated.


  1. debug45
    10.02.2025 18:39

    Кажется, на представленном скриншоте Material Design не тех времён, а как раз более-менее современных


    1. RiaruEnjo
      10.02.2025 18:39

      А как выглядел тех времён?


      1. debug45
        10.02.2025 18:39

        Что-то вроде такого. Красивее, чем сейчас, как по мне.