Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Gradle

Оптимизатор кода R8
Класс BuildConfig
Меняем номер версии библиотек в одном месте
Узнать debug.keystore: MD5 и SHA1

Gradle — система автоматической сборки, построенная на принципах Apache Ant и Apache Maven. В Eclipse использовалась система Ant, но большинство разработчиков даже не замечало её работы. В основном возможности системы использовались в конторах для автоматизации различных задач. В Android Studio такой номер не пройдёт. Gradle сопровождает вас во время разработки постоянно. Поначалу, если вы перешли с Eclipse, Gradle сильно раздражает своими действиями. Но позже вы оцените её удобство и может даже полюбите её.

Gradle не является изобретением для Android Studio, система была разработана раньше и использовалась в приложениях для Java, Scala и других языках.

Система сборки Gradle очень мощная и сложная, чтобы о ней рассказать в двух словах. Есть целые книги о ней. Сами команды в Gradle представляют собой обычный текст с использованием синтаксиса Groove для конфигурации. Но нам не нужно знать всё. Познакомимся поближе с системой и научимся пользоваться ей.

Создайте новый проект или откройте любой существующий проект из Android Studio и посмотрите на структуру проекта.

Файлы Gradle выделены в отдельную папку Gradle Script. Раскройте её. В основном вас должен интересовать файл build.gradle.kts, который относится к модулю. Рядом с этим файлом в скобках будет написано Module: app. Двойным щелчком откройте его, вы увидите, что файл является текстовым.

Также есть файл build.gradle.kts, который относится к проекту. Но с ним работают реже. Так находятся настройки для репозиториев и самого Gradle.

Вернёмся к файлу модуля, вы увидите много интересной информации. Например, вы там можете увидеть настройки, которые раньше вы могли видеть в манифесте - номера версий, номера SDK и так далее. Забегая вперёд, скажу, что здесь можно добавить всего одну волшебную строчку и нужная библиотека сама скачается из интернета и установится в проекте. Красота!

Однако вернёмся в корневую папку. Кроме файлов build.gradle.kts мы можем заметить файлы gradle.properties, settings.gradle.kts и другие. Трогать их не нужно.

Подключение библиотеки происходит одной строчкой.


implementation("com.airbnb.android:lottie-compose:6.4.0") // старый способ
implementation(libs.coil.compose)                         // новый способ

Чуть позже появилась новая технология BOM (Bill of Materials), которая позволяет определить набор библиотек. В файле libs.versions.toml есть строка:


composeBom = "2024.04.01"

Узнать, какие версии библиотек входят в набор, можно узнать через сайт https://developer.android.com/develop/ui/compose/bom/bom-mapping.

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


testImplementation 'junit:junit:4.12' // новый вариант для Android Studio 3.0

Далее включаете синхронизацию и через несколько секунд в папке появляется нужная библиотека, готовая к использованию. Сама библиотека скачивается с специального хранилища-репозитория JCenter. Данный репозиторий используется по умолчанию и прописан в buil.gradle проекта.


repositories {
    jcenter()
}

Можно указать другой репозиторий, например, Maven Central.


repositories {
    mavenCentral()
}

Для поиска через Maven-репозиторий используйте The Central Repository Search Engine.

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


dependencies {
    compile files("libs/library1.jar",
        "libs/library2.jar")
}

Сам файл нужно скопировать в папку <moduleName>/libs.

При любом изменении файла недостаточно его сохранить. Нужно также произвести синхронизацию. Наверху обычно появляется жёлтая полоска с ссылкой Sync Now.

Задаём имя APK при компиляции

Можно задать собственное имя при компиляции проекта. Например, так.


defaultConfig {
    ....
    project.ext.set("archivesBaseName", "MyName-" + defaultConfig.versionName);
}

Получим имя MyName-1.0.12-release.apk

Оптимизатор кода R8

Оптимизатор кода R8 имеет следующие возможности: урезание байт-кода, сжатие, обфускация, оптимизация, удаление «синтаксического сахара», преобразование в DEX. Оптимизатор может производить все операции за один шаг, что даёт сильное улучшение производительности. R8 был введён в Android Gradle plugin 3.3.0. Вам нужно только включить его.


android {
  buildTypes {
    release {
      minifyEnabled true
    }
  }
}

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


#отключение R8 только для Android Library модулей
android.enableR8.libraries = false

#отключение R8 для всех модулей android.enableR8 = false

Сжимаем итоговый APK

В Gradle 1.4 появилась возможность сжать итоговый файл, убрав неиспользуемые ресурсы, в том числе из библиотек, например, Google Play Services.


buildTypes {
    release {
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }

Во время сборки приложения вы можете увидеть строку:


:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
...

Другой способ убрать неиспользуемые ресурсы конфигурации. Например, вам не нужные локализованные ресурсы для всех языков, которые входят в библиотеку Google Play Services или Android Support Library и др. Оставим только нужные языки. Возможно, вы также не хотите поддерживать mdpi или tvdpi-разрешения в своём приложении. Мы можем установить языки и разрешения, которые используются в приложении, остальные будут исключены, что позволит уменьшить вес приложения.


// build.gradle 

android {
    defaultConfig {
        resConfigs "en", "ru"
        resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
    }
}

Можно перенести ключи из манифеста.


<meta-data
    android:name="com.facebook.sdk.ApplicationId"
    android:value="123456789012345" />

Чтобы их не светить, например, если выкладывать код на Гитхабе, то сделаем так.


defaultConfig {
    resValue "string", "facebook_app_id", '123456789012345'
}

И в манифесте переделаем код.


<meta-data
    android:name="com.facebook.sdk.ApplicationId"
    android:value="@string/facebook_app_id" />

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


defaultConfig {
    manifestPlaceholders = [fabric_io_id: "abcde1234567890abcdef1234567890abcdef123"]
}

В манифесте пишем.


<meta-data
    android:name="io.fabric.ApiKey"
    android:value="${fabric_io_id}" />

Класс BuildConfig

В статье LogCat упоминался способ быстрого отключения журналирования.

Суть в следующем. Когда вы создаёте новые переменные в блоках defaultConfig или buildTypes (ветки debug и release), то Gradle создаёт специальный класс BuildConfig, и вы можете получить доступ к этим переменным.

Например, добавим переменную в defaultConfig


defaultConfig {
    ... тут какие-то другие записи
    buildConfigField "String", "YOUR_TOKEN", '"ABRAKADABRA"'
}

На языке Java это равносильно String YOUR_TOKEN = "ABRAKADABRA";

Теперь мы можем обратиться к созданной строке.


String token = BuildConfig.YOUR_TOKEN;
// Что-то делаем со своей строкой

С секцией buildType ситуация интереснее. У секции есть два блока debug и release. Можно создать переменные с разными значениями, которые будут использоваться в зависимости от ситуации. Например, у вас есть собственное API для сервера. Для тестирования вы используете один адрес, а для финальной версии - другой адрес. Тогда вы просто указываете разные адреса в разных ветках. Переменные могут быть не только строковыми.


buildTypes {
    debug {
        buildConfigField "String", "API_URL", '"http://developer.alexanderklimov.ru/api/debug/"'
        buildConfigField "boolean", "REPORT_CRASHES", "true"
    }

    release {
        ... тут какие-то другие записи
        buildConfigField "String", "API_URL", '"http://developer.alexanderklimov.ru/api/release/"'
        buildConfigField "boolean", "REPORT_CRASHES", "false"
    }
}

Создаём код для перехода на веб-страницу.


Uri addressUri = Uri.parse(BuildConfig.API_URL);
Intent openLinkIntent = new Intent(Intent.ACTION_VIEW, addressUri);
startActivity(openLinkIntent);

Теперь вам не нужно переписывать каждый раз код. Загружаться будет страница по нужному адресу автоматически.

Разделяем отладочную и финальную версию

По такому же принципу можно организовать запуск новой версии программы, не затрагивая программу, которую вы установили с Google Play. Допустим вы на своём телефоне установили своё собственное приложение через Google Play. Теперь вам нужно написать новую версию и проверить на своём телефоне. Из-за конфликта имён новое тестируемое приложение перепишет финальную версию или вообще не установится. Поможет следующий трюк.


buildTypes {
    debug {
        applicationIdSuffix ".debug"
        versionNameSuffix "-debug"
        resValue "string", "app_name", "AboutCat (debug)"
    }

    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        resValue "string", "app_name", "AboutCat"
    }
}

В специальных переменных applicationIdSuffix и versionNameSuffix мы задаём суффиксы, чтобы избежать конфликта. А в переменной resValue указываем название программы для отладочной и финальных версий, чтобы на устройстве можно было их найти. Не забудьте при этом удалить строковый ресурс app_name в res/values/strings.xml, иначе получите ошибку при компиляции. Теперь можете спокойно запускать приложение с новым кодом, не боясь повредить своё любимое приложение.

Прячем секретную информацию

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


signingConfigs {
    release {
        storeFile     "${System.env.YOUR_APP_PRIVATE_KEY}"
        keyAlias      "${System.env.YOUR_APP_ALIAS}"
        storePassword "${System.env.YOUR_APP_STORE_PW}"
        keyPassword   "${System.env.YOUR_APP_PW}"
    }
}

Автогенерация версии кода

Нашёл совет, сам не применял. Не обязательно вручную менять версию приложения в атрибутах versionCode и versionName, можно сделать через переменные, а они сами подставятся в нужное место. На любителя.


def versionMajor = 1
def versionMinor = 0
def versionPatch = 0

android {
    defaultConfig {
        versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch
        versionName "${versionMajor}.${versionMinor}.${versionPatch}"
    }
}

settings.gradle

Файл settings.gradle обычно состоит из одной строчки.


include ':app'

Это означает, что у вас используется один проект для работы. Если вы будете подключать другие проекты, то здесь появятся новые строки.

gradle.properties (Project Properties)

Несколько советов по настройке файла gradle.properties.

Режим параллельного выполнения

В этом файле можно найти закомментированную строку # org.gradle.parallel=true. Если модули вашего проекта не используют друг друга как зависимости, создавая перекрёстные ссылки, можно включать режим параллельного выполнения, что ускорит скорость сборки до ~30%.


org.gradle.parallel=true # включаем режим параллельного выполнения

Gradle-демон

Включение на компьютере демона Gradle даст значительный прирост в скорости сборки.


org.gradle.daemon=true # включаем демон

Режим конфигурации при необходимости

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


org.gradle.configureondemand=true

Меняем номер версии библиотек в одном месте

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


dependencies {
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
    compile 'com.android.support:percent:23.4.0'
    compile 'com.android.support:cardview-v7:23.4.0'
    compile 'com.android.support:gridlayout-v7:23.4.0'

    //play services
    compile 'com.google.android.gms:play-services-location:9.2.1'
    compile 'com.google.android.gms:play-services-gcm:9.2.1'
}

Можно быстро поменять у всех номера через переменную. Для этого используется секция ext, в которой указывается переменная и номер версии. Затем в секции dependencies номер версии заменяется на имя переменной


ext { 
    supportLibraryVersion = '24.0.0'; 
    playServicesVersion = '9.2.1'
} 

dependencies { 
    compile "com.android.support:appcompat-v7:$supportLibraryVersion"
    compile "com.android.support:design:$supportLibraryVersion"
    compile "com.android.support:percent:$supportLibraryVersion"
    compile "com.android.support:cardview-v7:$supportLibraryVersion"
    compile "com.android.support:gridlayout-v7:$supportLibraryVersion"
    
    //play services
    compile "com.google.android.gms:play-services-location:$playServicesVersion"
    compile "com.google.android.gms:play-services-gcm:$playServicesVersion"
}

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

Расширенная версия с разными переменными в другом виде.


ext.compileSdkProjectVersion= 24
ext.buildToolsProjectVersion= '24.0.0'
ext.supportLibraryVersion = '24.0.0'
ext.googlePlayVersion = '8.4.0’

android {
    compileSdkVersion compileSdkProjectVersion
    buildToolsVersion buildToolsProjectVersion
    //…
}

dependencies {
    compile "com.android.support:appcompat-v7:$supportLibraryVersion"
    compile "com.google.android.gms:play-services-base:$googlePlayVersion"
    compile "com.google.android.gms:play-services-maps:$googlePlayVersion"
    compile "com.google.android.gms:play-services-location:$googlePlayVersion"
}

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

Настройки в Android Studio

Рассмотрим настройки, доступные в Android Studio. Закройте текущий проект, чтобы увидеть стартовое окно студии. В правой части нажмите на пункт Configure. В следующем окне выберите пункт Settings, чтобы оказаться в окне настроек студии. В левой части найдите пункт Build, Execution, Deployment, затем подпункт Build Tools, далее подпункт Gradle. По умолчанию, там всё чисто, только указан путь у Service directory path. Это были общие настройки.

Теперь рассмотрим настройки, относящиеся к проекту. Запустите любой проект в Android Studio. Выберите меню File | Settings.... Снова пройдитесь по пунктам Build, Execution, Deployment | Build Tools | Gradle. Вы увидите практически такое же окно с небольшими изменениями. Теперь поле Linked Gradle Projects не будет пустым, а также появятся дополнительные настройки. По умолчанию рекомендуют использовать Use default gradle wrapper.

Gradle Task

На правой стороне Android Studio имеется вертикальная вкладка Gradle, которую можно развернуть. Она содержит список задач (task), которая выполняет Gradle при работе с текущим проектом. Вы можете выделить любую из этих задач и запустить её двойным щелчком. Можно выделить несколько задач.

Узнать debug.keystore: MD5 и SHA1

Иногда требуется узнать значения debug.keystore: MD5 и SHA1. Обычно их получают через командную строку. Но это долго и неудобно, так как нужно помнить все аргументы. Есть способ проще. Открываем вкладку Gradle, нажимаем на кнопку со стрелками Refresh all Gradle Projects. Затем последовательно открываем элементы Tasks | android и запускаем команду signingReport. В нижнем окне Run увидите нужную информацию.


Variant: debug
Config: debug
Store: C:\Users\klimo_000\.android\debug.keystore
Alias: AndroidDebugKey
MD5: BA:6F:23:49:2D:9A:9C:0C:44:75:E0:94:59:07:E0:22
SHA1: 9D:51:F4:B8:4B:15:57:4B:EC:79:67:DC:F4:7C:5B:FB:02:C6:A2:F7
Valid until: Thursday, 1 December 2044

Gradle Console

Когда выполняется какая-то задача Gradle, то ход её выполнения можно увидеть в окне Gradle Console. Открыть её можно через вкладку Gradle Console в нижней правой части студии.

Terminal

Запускать задачи Gradle можно и в окне Terminal.

На панели инструментов имеется значок Sync Project with Gradle Files, которую следует использовать при редактировании файлов Gradle. Как правило, студия также выводит предупреждающее сообщение с ссылкой при изменении файла, которая делает ту же работу.

Добавление зависимостей через интерфейс студии

В статье описывался способ включения библиотеки в проект через редактирование файла build.gradle. Существует альтернативный вариант через настройки студии. Щёлкните правой кнопкой мыши на имени модуля (app) и выберите пункт Open Module Settings (быстрая клавиша F4). В правой части окна находятся вкладки, которые оказывают влияние на файл build.gradle. Например, вкладка Dependencies содержит подключаемые библиотеки.

Чтобы добавить новую зависимость, нажмите на значок с плюсом и выберите нужный вариант, например, Library dependency. Откроется список доступных библиотек из репозитория Maven.

Конфигурация в Android Studio Flamingo

Иногда студия начинает дурить, ругается на несовместимость каких версий библиотек, компиляторов и прочее. Вот и в версии Flamingo проект перестал собираться после какого-то обновления.

Мои настройки, после которого студия собрала проект.


// Project
plugins {
    id 'com.android.application' version '8.0.1' apply false
    id 'com.android.library' version '8.0.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
}

// Module
compileOptions {
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
    jvmTarget = '17'
}

Дополнительное чтение

В примере работы с PDF-файлами в папке assets использована операция запрета на сжатие файлов, которое происходит по умолчанию.

Задачи Gradle - теория для общего развития.

Реклама