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

Android — самая массовая мобильная платформа в мире. Разработка ведётся на языке Kotlin в официальной среде Android Studio: от первого «Привет, мир» на эмуляторе до публикации приложения в Google Play. Курс шаг за шагом проведёт от установки инструментов до релиза.

Уровень: начальный 19 разделов 3–4 часа

1. Что такое Android-разработка

Android — операционная система на базе Linux, которая работает на смартфонах, планшетах, часах и телевизорах. Приложения компилируются в установочный файл .apk или современный формат .aab (Android App Bundle) и распространяются через Google Play.

Официальный язык — Kotlin: он лаконичен, безопасен по типам и полностью совместим с Java. Для сборки используется система Gradle, а интерфейс современные приложения строят на Jetpack Compose — декларативном тулките, где экран описывается кодом, а не XML-файлами.

// Простейшая программа на Kotlin
fun main() {
    println("Привет, мир!")
}
Kotlin придумали в JetBrains (та же компания создала PyCharm и IDEA). Синтаксис приятнее Java, а безопасность от нулевых ссылок встроена прямо в язык.

2. Установка Android Studio

Скачай Android Studio с официального сайта developer.android.com/studio. Установщик предложит JDK, SDK и набор инструментов командной строки. После первого запуска мастер доустановит недостающие компоненты — это займёт несколько минут.

# Проверка установки SDK-инструментов
sdkmanager --list
sdkmanager "platform-tools" "platforms;android-35"
sdkmanager "system-images;android-35;google_apis;x86_64"
На Windows убедись, что включена виртуализация в BIOS (Intel VT-x / AMD-V), иначе эмулятор будет работать очень медленно или не запустится вовсе.

Лицензии компонентов принимаются при первом запуске. Если работаешь из терминала, прими их командой sdkmanager --licenses.

3. Первый проект и структура

В Android Studio выбери New Project → Empty Activity. IDE сгенерирует проект, который уже можно запустить. Разберись со структурой — без неё легко потеряться:

ПапкаЧто внутри
app/src/main/javaкод Kotlin: активити и классы
app/src/main/resресурсы: картинки, строки, темы
AndroidManifest.xmlописание приложения и разрешения
build.gradle.ktsнастройки сборки и зависимости
// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.myapp"
    compileSdk = 35
}

Точка входа — активити. Когда система запускает приложение, она вызывает его метод onCreate.

4. Основы Kotlin

Синтаксис Kotlin близок к Java и C#, но проще. Ключевое отличие — безопасность от нулевых ссылок: тип String? означает «строка или null», и компилятор заставит проверить null перед обращением.

val name = "Анна"           // val — неизменяемая переменная
var count = 0               // var — изменяемая

fun greet(name: String): String {
    return "Привет, $name!"
}

var maybe: String? = null   // nullable-тип
val length = maybe?.length ?: 0   // ?. — безопасный вызов, ?: — значение по умолчанию
Оператор ?: называется «элвис» — он похож на человечка с причёской. Если слева null, берётся значение справа.

В интерполяцию строк напрямую подставляются переменные: "Итог: $count". Это удобно при выводе данных в интерфейс.

5. Манифест, Activity и Jetpack Compose

Файл AndroidManifest.xml описывает приложение, его активити и разрешения. Активити с фильтром LAUNCHER — стартовый экран, с которого начинается приложение.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="@string/app_name"
        android:theme="@style/Theme.MyApp">

        <activity android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Современный интерфейс строится на Compose: функция @Composable описывает экран декларативно.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Greeting()
        }
    }
}

@Composable
fun Greeting() {
    Text("Привет, Android!")
}
Атрибут android:exported обязателен с Android 12. Для активити, запускаемой системой, ставь true — иначе сборка упадёт с ошибкой.

6. Вёрстка и списки: Compose и RecyclerView

В Compose списки делает компонент LazyColumn — он создаёт только видимые элементы, поэтому легко тянет тысяч элементов.

val items = listOf("Первый", "Второй", "Третий")

LazyColumn {
    items(items) { item ->
        Text(
            text = item,
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )
    }
}

В старых проектах используют XML-вёрстку и виджет RecyclerView с адаптером. Основные блоки XML-разметки: контейнер LinearLayout (строка или колонка), FrameLayout (наложение) и ConstraintLayout (гибкие связи).

// Пример XML-разметки экрана
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
Начинай новое приложение сразу на Compose — он официально рекомендован Google, а XML-вёрстку изучай только для поддержки легаси-проектов.

7. Интенты и навигация

Интент (Intent) — это сообщение «что нужно сделать». Явный интент открывает конкретный экран внутри приложения, неявный — делегирует действие системе.

// Переход на другой экран
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("post_id", id)
startActivity(intent)

// Открыть внешнюю ссылку
val url = Uri.parse("https://it-uroki.ru")
val browser = Intent(Intent.ACTION_VIEW, url)
startActivity(browser)

Данные передаются через putExtra и читаются в принимающей активити через getStringExtra или getIntExtra.

Не храни в интентах большие объекты — на них есть лимит (~1 МБ). Для списков передавай id, а данные скачивай из базы.
Для сложной навигации подключи библиотеку Navigation Compose: она поддерживает аргументы с типами и safeargs — безопасную передачу параметров.

8. Работа с сетью

Стандартный инструмент — Retrofit от Square: он превращает HTTP-запросы в вызовы функций. С корутинами используются suspend-функции, которые не блокируют поток интерфейса.

data class Post(val id: Int, val title: String)

interface ApiService {
    @GET("posts")
    suspend fun getPosts(): List<Post>
}

val api = Retrofit.Builder()
    .baseUrl("https://jsonplaceholder.typicode.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(ApiService::class.java)

Вызвать API можно из корутины:

lifecycleScope.launch {
    val posts = api.getPosts()
    recyclerAdapter.submitList(posts)
}
Для доступа в интернет манифест должен содержать разрешение <uses-permission android:name="android.permission.INTERNET" />. Без него запросы молча завершаются ошибкой.
Локально приложение можно тестировать только с HTTP на адресе 10.0.2.2 вместо localhost — так эмулятор видит хост-машину. Работа с реальным сайтом по HTTP требует флага usesCleartextTraffic.

9. Локальное хранилище: SQLite и Room

Room — библиотека поверх SQLite с проверкой SQL-запросов на этапе компиляции. Данные описываются сущностями (@Entity), обращения — интерфейсом DAO (@Dao).

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val text: String
)

@Dao
interface NoteDao {
    @Insert
    suspend fun insert(note: Note)

    @Query("SELECT * FROM notes")
    fun getAll(): Flow<List<Note>>
}

Объект базы создаётся один раз:

@Database(entities = [Note::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun noteDao(): NoteDao
}

Тип Flow — реактивный поток: интерфейс автоматически обновится при любом изменении базы без ручных вызовов.

Для простых пар «ключ-значение» не нужна база — используй SharedPreferences или DataStore. Room включают, когда данные структурированы и их нужно фильтровать запросами.

10. Эмулятор и отладка

Настрой виртуальное устройство через Device Manager: выбери образ системы (лучше Google APIs), размер экрана и версию Android. Запуск эмулятора — горячая клавиша Shift + F10 или кнопка Run.

Устройство и комьютер связывает инструмент adb (Android Debug Bridge):

# Полезные команды adb
adb devices                 # список подключённых устройств
adb shell                   # интерактивная консоль
adb install app-release.apk # установить APK вручную
adb logcat -s AndroidRuntime:E  # только ошибки приложения

Логи пишутся функцией Log.d("Метка", "сообщение") и просматриваются в панели Logcat. Точки останова ставятся кликом у номера строки — с ними можно пошагово пройти код.

Профиль с большим прогрессом эмулятора занимает десятки гигабайт. Если диск переполнен, удали старые виртуальные устройства через Device Manager.

11. Публикация в Google Play

Релизная сборка подписывается ключом .jks, который связывает приложение с аккаунтом разработчика. Настрой подпись в build.gradle.kts:

android {
    signingConfigs {
        create("release") {
            storeFile = file("keystore.jks")
            storePassword = "пароль"
            keyAlias = "mykey"
            keyPassword = "пароль"
        }
    }
}

Дальше путь такой: Build → Generate Signed Bundle / APK → выбери .aab → зарегистрируй аккаунт разработчика (разовый платёж ~25$) → загрузи сборку в Google Play Console → заполни описание, скриншоты и опубликуй.

Потеря ключа подписи означает, что приложение нельзя будет обновлять — только пересоздать под новым пакетом. Храни файл .jks в надёжном месте вне проекта (например, в зашифрованном менеджере паролей).
Для внутреннего тестирования до публикации используй трек «Закрытое тестирование»: добавь тестеров по email, и они получат приложение без релиза в общий доступ.

12. ViewModel и состояние экрана

ViewModel переживает поворот экрана и хранит данные, не связанные с представлением. Компонент не теряет состояние при изменении конфигурации — это стандартная практика современного Android.

class CounterViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()

    fun increment() {
        _count.value += 1
    }
}

В Compose-экране получаем экземпляр так:

@Composable
fun CounterScreen(viewModel: CounterViewModel =
    viewModel()) {
    val count by viewModel.count.collectAsState()
    Button(onClick = { viewModel.increment() }) {
        Text("Счёт: $count")
    }
}
Правило инженера: «весь код живет в ViewModel, экран только отображает состояние». Такой подход легко тестируется.

13. Корутины и потоковые данные

Корутины Kotlin — способ писать асинхронный код без коллбэков. Запросы к сети, чтение базы и обновление UI выполняются не блокируя поток интерфейса.

lifecycleScope.launch {
    val data = repository.load()     // suspend-функция
    uiState.value = UiState.Success(data)
}

Основные понятия:

ПонятиеСмысл
CoroutineScopeжизненный цикл корутин (например, lifecycleScope)
Dispatchers.IOпул для сетевых и дисковых операций
suspendфункция может приостановиться без блокировки
withContextсмена контекста внутри корутины
UI обновляется в Main-потоке. Не забудьте вернуться на Dispatchers.Main (или use collectAsState), иначе упадёт CalledFromWrongThreadException.

14. Работа с данными: StateFlow, Flow и база

Flow — холодный поток значений: данные появляются по мере подписки. Комбинируя Flow с Room и Retrofit, получаем реактивные экраны, обновляющиеся сами.

@Query("SELECT * FROM notes ORDER BY id DESC")
fun getAll(): Flow<List<Note>>

// в ViewModel
val notes = dao.getAll().stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(),
    initialValue = emptyList()
)

Сбор во ViewModel и отображение через collectAsState — канонический паттерн: подписка сама закрывается, когда экран исчез.

Для кэширования и работы офлайн подключите библиотеку типа Room + базы. Сетевые ответы сохраняйте в локальную таблицу: так приложение работает без интернета.

15. Тесты и отладка приложения

Юнит-тесты проверяют ViewModel и Dao, UI-тесты — композдаблы и взаимодействие. Тесты делают рефакторинг безопасным.

@Test
fun increment_increases_count() {
    val vm = CounterViewModel()
    vm.increment()
    assertEquals(1, vm.count.value)
}
Отладка без тестов — это ручной прогон. Автоматизируйте ключевые сценарии: успешная публикация обычно подразумевает прохождение базового набора тестов.

16. DI на Hilt: управляем зависимостями

Инжекция зависимостей (DI) делает код тестируемым и гибким: сервисы, база, сеть создаются «извне», а не прямо в классах. Hilt — стандартный DI-фреймворк от Google для Android.

@HiltAndroidApp
class App : Application()

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides @Singleton
    fun provideDb(): AppDatabase = Room.databaseBuilder(...).build()
}
Hilt ускоряет тестирование: вы подменяете модуль на фейковый, и тесты не зависят от сети и баз данных.

17. Push-уведомления (FCM)

Push-уведомления возвращают пользователей в приложение: лента, заказы, сообщения. Стандарт для Android — Firebase Cloud Messaging (FCM): сервер шлёт сообщение, приложение его показывает.

  1. подключите Firebase (google-services.json) и добавьте зависимость FCM;
  2. создайте службу-наследник FirebaseMessagingService;
  3. получите токен устройства: FirebaseMessaging.getInstance().token;
  4. передайте токен на свой сервер — по нему шлёте пуш;
  5. отправляйте сообщения: POST /v1/projects/.../messages:send.
{
  "message": {
    "token": "USER_TOKEN",
    "notification": {
      "title": "Скидка!",
      "body": "Только сегодня −30%"
    }
  }
}
Не спамьте этим инструментом: уведомления без ценности ведут к отключению push и потере доверия. Сегментируйте аудиторию и следите за ретеншеном.

18. Камера и работа с файлами

Фото профиля, сканы документов, сохранение файлов — частое требование в приложениях. Современные API Android построены на Activity Result Contracts и файловых провайдерах.

// Запрос на выбор фото из галереи
val pick = registerForActivityResult(
    ActivityResultContracts.GetContent()) { uri ->
    // uri — доступ только к выбранному файлу
}

// Съёмка с камеры с сохранением в файл
val take = registerForActivityResult(
    ActivityResultContracts.TakePicture()) { success -> ... }
Всегда копируйте выбранные файлы в app-specific каталог сразу: контент-URI живёт, пока владелец не удалит файл из галереи.

19. Производительность и профилирование

Медленное приложение теряет пользователей: зависания (ANR) и тормоза караются низкими оценками. Профилируйте заранее: утечки памяти, лишние перерисовки, тяжёлые операции на главном потоке.

ИнструментЧто показывает
Profiler (Android Studio)CPU, память, сеть, энергия
LeakCanaryутечки по ссылкам на Activity/ViewModel
StrictModeдисковые и сетевые операции на главном потоке
Systraceкак фреймы тратят время
Картинка в полном разрешении в профиле на 48 МП — мгновенные OOM и жалобы в Play Console. Всегда декодируйте с погрешностью до нужного размера.

20. WorkManager и фоновые задачи

WorkManager — современный API для отложенных и периодических фоновых задач: синхронизация данных, загрузка, очистка кэша. Он переживает перезапуск приложения и учитывает ограничения сети и батареи.

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build())
    .build()
WorkManager.getInstance(context).enqueue(request)
Пишите код воркера идемпотентным: система может перезапустить задачу после сбоя, и повторный запуск не должен ломать данные.
Не обращайтесь к UI из воркера: фоновая задача выполняется без экрана, результат передавайте через ViewModel или репозиторий.

21. Безопасность: Keystore и шифрование

Хранить токены и пароли в SharedPreferences опасно: их может прочитать любой, у кого есть устройство. Android Keystore держит ключи в защищённом хранилище и не отдаёт их наружу.

Для сетевых токенов заведите отдельный слой хранения через Keystore: менять логику проще, чем потом «пересобирать» безопасность по всему коду.
Шифрование не спасает от вредоносных приложений с правами root: лучшая защита — не хранить то, что можно не хранить.

22. Аналитика и crash-репорты

Публикация — не финиш. Crashlytics собирает падения, аналитика — поведение пользователей. Без них вы не увидите, что приложение делает на реальных устройствах.

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

23. Мультимодульность и Clean Architecture

Когда приложение разрастается, один модуль становится узким местом: все правят всё и конфликты в Git неизбежны. Разбиение на модули даёт изоляцию и быстрее сборку.

Начните с трёх модулей: app, core, feature — этого достаточно для заметного ускорения сборки.
Не дробите «ради чистоты»: каждый модуль добавляет накладные расходы. Усложняйте архитектуру по мере роста проекта.

Что дальше

Закрепи язык: курс IntelliJ IDEA поможет освоить IDE, а Git — начать версионировать код. Для серверной части приложения пригодится REST API, для аналитики данных — SQL. Смежные мобильные платформы: iOS (Swift), кроссплатформенные Flutter и React Native.

← Вернуться к курсам