Нам бы хотелось поделиться с вами опытом, который мы, в Futurice, получили, разрабатывая Android-приложения. Надеемся, эти советы уберегут вас от создания собственных велосипедов. Если вы интересуетесь iOS или Windows Phone разработкой, обратите внимание на соответствующие документы на нашем сайте.

Коротко о главном


  • Используйте систему сборки Gradle и стандартную структуру проекта
  • Храните пароли и другие важные данные в файле gradle.properties
  • Не пишите свой собственный HTTP-клиент, лучше используйте библиотеки Volley или OkHttp
  • Используйте библиотеку Jackson для парсинга данных в формате JSON
  • Избегайте использовать Guava и вообще экономьте — старайтесь не превысить ограничение в 65k методов
  • Используйте фрагменты для отображение пользовательского интерфейса
  • Используйте activity только для управления фрагментами
  • XML-разметка — это код, пишите его аккуратно
  • Используйте стили, чтобы не дублировать атрибуты в XML-разметке
  • Лучше несколько файлов со стилями, чем один здоровенный, который будет сложно читать
  • Старайтесь избегать дублей в colors.xml и сделать его покороче — в основном для хранения цветовой палитры
  • То же касается dimens.xml, определите там только основные константы
  • Не делайте слишком глубокую иерархию элементов ViewGroup
  • Избегайте отработки на клиентской стороне при использовании WebView, будьте внимательны к возможным утечкам
  • Используйте Robolectric для unit-тестирования, а Robotium для тестирования UI
  • Используйте Genymotion в качестве эмулятора
  • Всегда используйте ProGuard или DexGuard

Android SDK


Поместите ваш Android SDK в домашнюю директорию или другое место, не связанное с приложением. Некоторые IDE ставятся вместе с SDK, и могут устанавливать его в свою директорию. Это может помешать при обновлении (или переустановке) IDE, или когда вы перейдёте на другую IDE. Избегайте установки SDK в другую системную директорию, поскольку это может потребовать административных привилегий, если ваша IDE запускается с правами пользователя, а не администратора.

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


Выбором по умолчанию должен быть Gradle. Ant гораздо скромнее по возможностям, и к тому же его инструкции менее компактны. С помощью Gradle вы легко сможете:

  • Создавать различные варианты и сборки вашего приложения
  • Создавать простые задачи в виде скрипта
  • Управлять зависимостями и автоматически загружать их
  • Настраивать хранилище ключей
  • И многие другие полезные вещи

Также отметим, что Gradle plugin для Android активно развивается Google как новый стандарт систем сборки.

Структура проекта


Есть два распространённых варианта: старая Ant & Eclipse ADT структура проекта — либо новая Gradle & Android Studio. Лучше выбрать второй вариант. Если ваш проект использует старую структуру, рекомендуем её портировать.

Старая структура
old-structure
+- assets
+- libs
+- res
+- src
¦  L- com/futurice/project
+- AndroidManifest.xml
+- build.gradle
+- project.properties
L- proguard-rules.pro


Новая структура
new-structure
+- library-foobar
+- app
¦  +- libs
¦  +- src
¦  ¦  +- androidTest
¦  ¦  ¦  L- java
¦  ¦  ¦     L- com/futurice/project
¦  ¦  L- main
¦  ¦     +- java
¦  ¦     ¦  L- com/futurice/project
¦  ¦     +- res
¦  ¦     L- AndroidManifest.xml
¦  +- build.gradle
¦  L- proguard-rules.pro
+- build.gradle
L- settings.gradle


Основное отличие заключается в том, что новая структура явным образом разделяет 'наборы ресурсов' (main, androidTest), это одна из концепций Gradle. Вы можете, к примеру, добавить папки 'paid' и 'free' в вашу папку 'src', и они будут содержать исходный код для платной и бесплатной версий вашего приложения.

Наличие папки приложения app на верхнем уровне иерархии помогает отделить его от библиотек (например, library-foobar), которые приложение использует. Файл settings.gradle в таком случае хранит список этих библиотечных проектов, на которые может ссылаться app/build.gradle.

Конфигурация Gradle


Общая структура. Следуйте инструкциям Google для Gradle for Android.

Маленькие задачи сборки. В отличие от других скриптовых языков (shell, Python, Perl, и т.д.), вы можете создавать задачи сборки в Gradle. Подробности смотрите в документации Gradle.

Пароли. В файле вашего приложения build.gradle вам нужно определить параметры подписи (signingConfigs) для релизной сборки. Следует избегать такой ошибки:

Не делайте так. Эта информация появится в системе контроля версий.

signingConfigs {
    release {
        storeFile file("myapp.keystore")
        storePassword "password123"
        keyAlias "thekey"
        keyPassword "password789"
    }
}

Правильнее будет создать файл gradle.properties, который не будет добавлен под управление системы контроля версий:

KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789

Эти данные автоматически импортируются в gradle, и вы сможете использовать их в build.gradle следующим образом:

signingConfigs {
    release {
        try {
            storeFile file("myapp.keystore")
            storePassword KEYSTORE_PASSWORD
            keyAlias "thekey"
            keyPassword KEY_PASSWORD
        }
        catch (ex) {
            throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")
        }
    }
}

Старайтесь использовать зависимости Maven, а не импортировать jar-файлы. Если вы включаете внешние jar-файлы в ваш проект, они будут законсервированы в той версии, при которой происходил импорт, например 2.1.1. Ручная загрузка jar-файлов и их обновление — достаточно трудоёмкая операция, и Maven может отлично решить эту проблему за нас, включив результат в сборку. Например:

dependencies {
    compile 'com.squareup.okhttp:okhttp:2.2.0'
    compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
}

Избегайте использования динамических зависимостей Maven Избегайте указания динамически формируемых версий, например 2.1.+, так как это может привести к нестабильности сборок, зависящих от неконтролируемых отличий взаимодействия различных версий библиотек. Использование статических номеров версий, например, 2.1.1, поможет создать более стабильные сборки с предсказуемым поведением.

Среда разработки (IDE) и текстовый редактор


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

Самая популярная IDE на данный момент — Android Studio, поскольку она разрабатывается Google, интегрирована с Gradle, использует новую структуру проекта по умолчанию, находится в состоянии стабильной сборки и заточена под Android-разработку.

Вы можете использовать Eclipse ADT, если он вам нравится, но его придётся настраивать, поскольку он по умолчанию работает со старой структурой проекта и системой сборки Ant. Вы можете даже использовать текстовые редакторы Vim, Sublime Text, или Emacs. В этом случае вам придётся использовать Gradle и adb из командной строки. Если вам не удастся подружить Eclipse с Gradle, вам тоже придётся использовать для сборки командную строку. Учитывая то, что ADT plugin недавно был объявлен устаревшим, лучше просто перейти на Android Studio.

Что бы вы не использовали, имейте в виду, что Gradle и новая структура проекта являются официально рекомендованным способом сборки приложений, и не добавляйте ваши редакторо-зависимые конфигурационные файлы в систему контроля версий. Например, не добавляйте файл Ant build.xml. Также не забывайте обновлять build.gradle, когда вы меняете конфигурацию сборки в Ant. В общем, не заставляйте других разработчиков использовать непривычные им инструменты.

Библиотеки


Jackson — библиотека Java для конвертации объектов в JSON и наоборот. Gson — самый распространённый способ решения этой задачи, но по нашим наблюдениям Jackson более производителен, поскольку он поддерживает альтернативные способы обработки JSON: потоковый, модель дерева в оперативной памяти, и традиционную связь форматов JSON-POJO. Имейте в виду, однако, что Jackson по размеру больше чем GSON, так что возможно, вы предпочтёте GSON для того чтобы избежать ограничения в 65k методов. Другие варианты: Json-smart и Boon JSON.

Работа с сетью, кэширование и картинки. Есть пара проверенных опытом решений для производительных запросов к backend-серверам, которые вам стоит рассмотреть перед разработкой вашего собственного клиента. Используйте Volley или Retrofit. Volley к тому же предоставляет средства загрузки и кэширования изображений. Если вы выберете Retrofit, возьмите Picasso для загрузки или кэширования изображений, и OkHttp для эффективных HTTP-запросов. Все эти библиотеки — Retrofit, Picasso и OkHttp разработаны одной компанией, так что они отлично дополняют друг друга. OkHttp также может быть использован с Volley.

RxJava — библиотека для Reactive Programming, другими словами, для обработки асинхронных событий. Это мощная и многообещающая концепция, которая может смутить своей необычностью. Мы рекомендуем хорошо подумать, перед тем как использовать эту библиотеку как фундамент архитектуры всего приложения. Есть проекты, созданные с использованием RxJava, и вы можете обратиться за помощью к однуму из этих людей: Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen. Мы писали несколько статей в наш блог по этому поводу: [1], [2], [3], [4].

Если вы раньше не работали с Rx, начните с использования API. Или вы можете начать с его применения для обработки простых событий пользовательского интерфейса, таких как нажатие или печать в поле поиска. Если вы уверены в ваших навыках использования Rx и хотите использовать его во всей архитектуре, напишите Javadocs касательно самых сложных моментов. Имейте в виду, что программист, не имеющий опыта использования RxJava, проклянёт вас может иметь большие проблемы при поддержке проекта. Постарайтесь помочь ему понять ваш код и Rx.

Retrolambda — это библиотека Java для использования лямбда-выражений в Android и других платформах с JDK ниже 8-ой версии. Она поможет вам сделать ваш код компактным и хорошо читаемым, особенно если вы используете функциональный стиль, например с RxJava. Для её использования, установите JDK8, укажите его в пути к SDK в Android Studio в диалоге описания структуры проекта, и установите переменные окружения JAVA8_HOME и JAVA7_HOME, после чего в корневом build.gradle проекта напишите:

dependencies {
    classpath 'me.tatarka:gradle-retrolambda:2.4.1'
}

и в файле build.gradle каждого модуля добавьте

apply plugin: 'retrolambda'

android {
    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

retrolambda {
    jdk System.getenv("JAVA8_HOME")
    oldJdk System.getenv("JAVA7_HOME")
    javaVersion JavaVersion.VERSION_1_7
}

Android Studio начнёт поддерживать синтаксис лямбда-выражений. Если вы раньше их не использовали, можете начать с осознания утверждений:

  • Любой интерфейс с одним методом совместим с лямбда-выражениями и может быть упрощён в написании
  • Если вам непонятно, как описать параметры, напишите обычный внутренний класс и позвольте Android Studio транслировать его в лямбда-выражение.

Помните о ограничении dex-файла на количество методов, и избегайте использования большого количества библиотек. Приложения Android, при упаковке в dex-файл, имеют жёсткое ограничение в 65536 ссылочных методов [1] [2] [3]. При превышении лимита, вы получите фатальную ошибку компиляции. Так что советуем использовать минимально возможное количество библиотек, и обратите внимание на утилиту для подсчёта количества методов в dex-файле. Она поможет определить, какой набор библиотек можно использовать, не превышая лимит. Будьте особенно осторожны при использовании библиотеки Guava, которая содержит более 13k методов.

Activity и фрагменты


В сообществе Android-разработчиков (как и в Futurice) нет единого мнения по вопросу, как лучше всего построить архитектуру Android-приложения в плане использования фрагментов и activity. Square даже выпустила библиотеку для построения архитектуры в основном с помощью view, минимизировав таким образом необходимость фрагментов, но этот способ до сих пор не стал общепринятым.

Исходя из истории развития Android API, вы можете рассматривать фрагменты, как часть пользовательского интерфейса экрана. Другими словами, фрагменты обычно относятся к UI. Activity обычно рассматриваются как контроллеры, они особенно важны с точки зрения их жизненного цикла и для управлением состояниями. Однако, может быть и по-другому: activity могут исполнять функции, связанные с UI (переход состояний между экранами), а фрагменты могут быть использованы только как контроллеры. Мы бы советовали принимать взвешенное решение, имея в виду, что архитектура, базирующаяся на использовании только фрагментов, или только activity, или только view, может иметь ряд недостатков. Вот пара советов, на что стоит обратить внимание, но отнеситесь к ним критично:

  • Избегайте интенсивного использования вложенных фрагментов (nested fragments), из-за возможности появления ошибок типа «матрёшка». Используйте вложенные фрагменты, только если это имеет смысл (например, фрагменты в прокручиваемом горизонтально ViewPager внутри фрагмента-экрана) или если вы хорошо понимаете, что делаете.
  • Не помещайте слишком много кода в activity. Если это возможно, используйте их как лёгкие контейнеры, существующие в вашем приложении в основном для управления жизненным циклом и других важных функций интерфейса Android API. Activity с одним фрагментом лучше, чем просто activity — выносите код, относящийся к пользовательскому интерфейсу во фрагмент. Это сделает возможным его повторное использование в случае, если вам потребуется поместить его в разметку с табами, или на экран планшета с несколькими фрагментами. Избегайте создание activity без связанных фрагментов, кроме случаев, когда вы делаете это специально.
  • Не стоит злоупотреблять API уровня Android, например, слепо полагаясь на механизм Intent для внутренней работы приложения. Вы можете повлиять на операционную систему Android или другие приложения, вызвав ошибки или зависания. Например, известно, что если ваше приложение использует механизм Intent для внутренней коммуникации между пакетами приложения, вы можете вызвать зависание в несколько секунд, если приложение было открыто сразу после загрузки операционной системы.

Архитектура пакетов Java


Архитектура Java для Android-приложений напоминает шаблон Model-View-Controller. В Android, фрагменты и activity представляют классы Conroller'а. С другой стороны, они являются частью пользовательского интерфейса, так что они также являются частью View.

Поэтому сложно отнести фрагменты (или activity) однозначно к Controller либо View. Лучше поместить их в собственный пакет fragments. Activity в этом случае можно оставить в пакете верхнего уровня. Если у вас больше двух-трёх activity, можно вынести их также в отдельный пакет.

С другой стороны, архитектура может выглядеть как обычный MVC, с пакетом models, содержащим объекты POJOs, генерируемые с помощью парсера JSON из ответов API, и пакетом views, содержащим авторские View, оповещения, классы View связанные с action bar, виджеты, и т.д. Адаптеры — это связывающее звено, находящееся между data и views. Учитывая то, что они обычно используют View, экспортируемые через метод getView(), вы можете включить адаптеры как дочерний пакет adapters во views.

Некоторые классы controller используются по всему приложению и работают напрямую с операционной системой Android. Их можно поместить в пакет managers. Различные классы для обработки данных, такие как «DateUtils», можно хранить в пакете utils. Классы, отвечающие за взаимодействие с backend, находятся в пакете network.

Все вышеперечисленные пакеты, в порядке от backend до пользовательского интерфейса:

com.futurice.project
+- network
+- models
+- managers
+- utils
+- fragments
L- views
   +- adapters
   +- actionbar
   +- widgets
   L- notifications

Ресурсы


Именование. Следуйте конвенции об использовании типа объекта в качестве префикса имени файла, как в type_foo_bar.xml. Примеры: fragment_contact_details.xml, view_primary_button.xml, activity_main.xml.

Структура XML разметки. Если вы не уверены, как форматировать XML разметки, следующие советы могут помочь.

  • Один атрибут на строку, с отступом в 4 пробела
  • android:id всегда находится на первом месте
  • android:layout_**** атрибуты в начале
  • атрибут style на последнем месте
  • Закрывающий тэг /> находится на своей строчке, для облегчения упорядочения и добавления атрибутов.
  • Вместо написания вручную android:text, вы можете использовать Визуальный редактор атрибутов для Android Studio.

Пример
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="@string/name"
        style="@style/FancyText"
        />

    <include layout="@layout/reusable_part" />

</LinearLayout>


Как показывает опыт, атрибут android:layout_**** должен быть определён в XML разметки, а остальные атрибуты android:**** должны быть определены в XML стилей. У этого правила есть исключения, но в целом оно работает хорошо. Смысл в том, чтобы хранить только атрибуты разметки (позиция, поля, размер) и атрибуты контента в файле разметки, а детали отображения визуальных компонентов (цвета, отступы, шрифты) должны быть в файлах стилей.

Исключения:
  • android:id конечно должен быть в файле разметки
  • android:orientation для объекта LinearLayout обычно имеет смысл в файле разметки
  • android:text должен быть в файле разметки, потому что он описывает контент
  • Иногда имеет смысл определить в общем стиле android:layout_width и android:layout_height, но по умолчанию эти атрибуты должны находиться в файле разметки

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

<style name="ContentText">
    <item name="android:textSize">@dimen/font_normal</item>
    <item name="android:textColor">@color/basic_black</item>
</style>

Применимо к TextView:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/price"
    style="@style/ContentText"
    />

Вам наверняка придётся сделать то же самое для кнопок, но не останавливайтесь на этом. Развивайте эти концепцию и выносите все группы повторяющихся атрибутов android:****, относящихся к определённым видам визуальных компонент, в стили.

Разделяйте большой файл со стилями на несколько более маленьких. Нет необходимости хранить все стили в одном файле styles.xml. Android SDK по умолчанию поддерживает различные имена файлов, так что нет никаких проблем с именованием файлов со стилями — они всего лишь должны содержать XML-тэг «style». Так что вы можете создать файлы styles.xml, styles_home.xml, styles_item_details.xml, styles_forms.xml. В отличие от имён директорий, которые важны для системы сборки, имена файлов в res/values могут быть произвольными.

colors.xml это цветовая палитра. Не помещайте в colors.xml ничего, кроме связи названия цвета с его RGBA значением. Не используйте его для определения значений RGBA для разных типов кнопок.

Так неправильно
<resources>
    <color name="button_foreground">#FFFFFF</color>
    <color name="button_background">#2A91BD</color>
    <color name="comment_background_inactive">#5F5F5F</color>
    <color name="comment_background_active">#939393</color>
    <color name="comment_foreground">#FFFFFF</color>
    <color name="comment_foreground_important">#FF9D2F</color>
    ...
    <color name="comment_shadow">#323232</color>


При таком подходе очень просто создать дублирующие значений RGBA, да и цвета изменять гораздо сложнее. Кроме того, эти цвета относятся к определённому контенту, «button» или «comment», и должны быть описаны в стиле кнопки, а не в colors.xml.

А вот так - правильно
<resources>

    <!-- grayscale -->
    <color name="white"     >#FFFFFF</color>
    <color name="gray_light">#DBDBDB</color>
    <color name="gray"      >#939393</color>
    <color name="gray_dark" >#5F5F5F</color>
    <color name="black"     >#323232</color>

    <!-- basic colors -->
    <color name="green">#27D34D</color>
    <color name="blue">#2A91BD</color>
    <color name="orange">#FF9D2F</color>
    <color name="red">#FF432F</color>

</resources>


Цветовую палитру определяет дизайнер приложения. Цвета не обязательно называть «green», «blue», и т.д. Названия вроде «brand_primary», «brand_secondary», «brand_negative» тоже вполне приемлемы. Такое форматирование цветов делает простым их изменение или рефакторинг, а также позволяет легко понять, сколько цветов используется. Для создания красивого пользовательского интерфейса, важно по возможности уменьшить количество используемых цветов.

Оформите dimens.xml как colors.xml. По той же причине, стоит так же определить «палитру» типичных размеров объектов и шрифтов.

Пример хорошей структуры dimens.xml
<resources>

    <!-- font sizes -->
    <dimen name="font_larger">22sp</dimen>
    <dimen name="font_large">18sp</dimen>
    <dimen name="font_normal">15sp</dimen>
    <dimen name="font_small">12sp</dimen>

    <!-- typical spacing between two views -->
    <dimen name="spacing_huge">40dp</dimen>
    <dimen name="spacing_large">24dp</dimen>
    <dimen name="spacing_normal">14dp</dimen>
    <dimen name="spacing_small">10dp</dimen>
    <dimen name="spacing_tiny">4dp</dimen>

    <!-- typical sizes of views -->
    <dimen name="button_height_tall">60dp</dimen>
    <dimen name="button_height_normal">40dp</dimen>
    <dimen name="button_height_short">32dp</dimen>

</resources>


Рекомендуем не писать числовые значения в повторяющихся атрибутах разметки (полях и отступах), а использовать константы вида spacing_**** (примерно так, как вы обычно делаете для локализиции строковых значений).
Это сделает разметку понятнее и позволит проще её изменять.

strings.xml

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

Плохой пример

<string name="network_error">Network error</string>
<string name="call_failed">Call failed</string>
<string name="map_failed">Map loading failed</string>

Хороший пример

<string name="error.message.network">Network error</string>
<string name="error.message.call">Call failed</string>
<string name="error.message.map">Map loading failed</string>

Не пишите строковые значения строчными буквами. Вы можете использовать обычные преобразования текста (в том числе, преобразование первой буквы в прописную). Если потребуется написать всю строку строчными буквами — используйте атрибут textAllCaps объекта TextView.

Плохой пример

<string name="error.message.call">CALL FAILED</string>

Хороший пример

<string name="error.message.call">Call failed</string>

Избегайте глубокой иерархии view. Иногда у вас будет соблазн добавить ещё один LinearLayout, для решения вашей задачи описания view.

В результате может получится примерно следующее
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <RelativeLayout
        ...
        >

        <LinearLayout
            ...
            >

            <LinearLayout
                ...
                >

                <LinearLayout
                    ...
                    >
                </LinearLayout>

            </LinearLayout>

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>


Даже если вы не видите явно выросшую вложенность в файле разметки, она может возникнуть когда вы включаете (в Java) view в другие views.

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

Так что постарайтесь сделать вашу иерархию view как можно более плоской: посмотрите как использовать RelativeLayout, как оптимизировать вашу разметку и использовать тэг <merge>.

Будьте внимательны при использовании WebView. Когда вам нужно показать web-страницу, например новостную статью, избегайте исполнения кода на клиентской стороне для формирования HTML, лучше попросите backend-программистов предоставить «чистый» HTML. WebView также могут вызывать утечку памяти при сохранении ссылки на Activity, к которой привязаны вместа ApplicationContext. Избегайте использования WebView для создания простого текста или кнопки, лучше используйте объекты TextView или Button.

Фреймворки для тестирования


Тестовый фреймворк Android SDK ещё находится в недоделанном состоянии (это они про Espresso 2.1 ?! — прим. пер.), особенно это касается тестов пользовательского интерфейса. Android Gradle по умолчанию содержит задачу connectedAndroidTest, которая запускает созданные вами тесты JUnit, используя расширение JUnit с утилитами Android. Это обозначает, что вам придётся запускать тесты на устройстве или эмуляторе. Используйте официальные инструкции [1] [2] для тестирования.

Используйте Robolectric только для unit-тестов, не для UI. Этот фреймворк предоставляет возможность для запуска тестов без устройства, для увеличения скорости их выполнения, и идеально подходит для unit-тестов моделей данных и view. Однако, пользовательский интерфейс Robolectric тестирует не полностью и неточно. У вас будут проблемы при тестировании элементов пользовательского интерфейса, относящихся к анимациям, диалогам, и т.д., и процесс тестирования будет проходить «с закрытыми глазами» (не видя экрана).

Robotium делает тестирование пользовательского интерфейса простым. Вы можете запускать UI-тесты без Robotium, но он очень полезен за счёт утилит для анализа view и контроля экрана. Сценарии тестирования будут выглядеть совсем просто:

solo.sendKey(Solo.MENU);
solo.clickOnText("More"); // searches for the first occurence of "More" and clicks on it
solo.clickOnText("Preferences");
solo.clickOnText("Edit File Extensions");
Assert.assertTrue(solo.searchText("rtf"));

Эмуляторы


Если вы занимаетесь Android-разработкой профессионально, купите лицензию на эмулятор Genymotion. Он работает быстрее, чем обычный AVD-эмулятор. Эмулятор Genymotion позволяет записать ролик, демонстрирующий работу вашего приложения, эмулирует различное качество сетевого соединения, сигналы GPS и многое другое. Он идеален для запуска тестов. У вас появится доступ к многим (хотя не ко всем) образам устройств с ОС Android, так что стоимость лицензии Genymotion гораздо дешевле, чем покупка множества устройств.

Подводные камни: Genymotion не позволяет использовать в приложении такие сервисы Google, как Google Play Store или Maps. И если вам понадобится протестировать функции API Samsung, вам придётся купить реальное устройство.

Конфигурация Proguard


ProGuard обычно используется в проектах Android для сжатия и обфускации кода.

Условия использования ProGuard зависят от настроек вашего проекта. Обычно вы настраиваете gradle использовать ProGuard для сборки релизной версии.

buildTypes {
    debug {
        minifyEnabled false
    }
    release {
        signingConfig signingConfigs.release
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

Чтобы указать, какой именно участок кода нуждается в обработке, вы должны отметить одну или несколько точек входа. Обычно это классы с основными методами, апплеты, мидлеты, активности, и т.д. Конфигурация по умолчанию, которую использует Android framework, находится по адресу SDK_HOME/tools/proguard/proguard-android.txt. Вы можете определить свои собственные правила для конфигурации ProGuard, поместив их в файл my-project/app/proguard-rules.pro, и они дополнят конфигурацию по умолчанию.

Самая распространённая проблема, связанная с ProGuard — падение приложения при запуске с ошибками ClassNotFoundException или NoSuchFieldException, даже если команда сборки проекта (например, assembleRelease) отработала без ошибок. Это может означать одну из двух вещей:

  • ProGuard удалил класс, enum, метод, поле или аннотацию, посчитав что она не нужна.
  • ProGuard переименовал класс, enum или поле, но оно вызывается с использованием его старого имени, в том числе через механизм Java reflection.

Проверьте файл app/build/outputs/proguard/release/usage.txt на предмет упоминания удалённого объекта. Если он был переименован, его имя есть в файле app/build/outputs/proguard/release/mapping.txt.

Для того, чтобы защитить нужные классы и методу от удаления ProGuard'ом, добавьте в его конфигурацию опцию keep:

-keep class com.futurice.project.MyClass { *; }

Для защиты от переименования используйте опцию keepnames:

-keepnames class com.futurice.project.MyClass { *; }

Другие возможные модификации конфигурации ProGuard вы можете посмотреть в этом примере. Больше примеров конфигурации Proguard здесь.

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

Совет. Сохраняйте файл mapping.txt для каждого релиза. Имея копию файла mapping.txt для каждой сборки, вы можете быть уверены что сможете найти проблему, кода пользователь поймает баг и пришлёт вам обфускированный лог ошибок.

DexGuard. Если вы хотите круто оптимизировать ваш код, и обфускировать его особым образом, попробуйте использовать DexGuard, коммерческий аналог ProGuard. Он может легко разделить Dex-файл на несколько для обхода ограничения в 65k методов.

Благодарности


Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mantyla, Mark Voit, Andre Medeiros, Paul Houghton и другим разработчикам Futurice за то, что они поделились своими знаниями в области Android.

Лицензия


Futurice Oy Creative Commons Attribution 4.0 International (CC BY 4.0)

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


  1. vedenin1980
    23.07.2015 12:27
    +1

    Кат, Карл, кат!


    1. samodum
      23.07.2015 12:34
      -4

      Об этом принято писать в личку


      1. vedenin1980
        23.07.2015 12:55
        +5

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


        1. Error_403_Forbidden
          23.07.2015 21:16

          Вау-вау, палегче.
          Мне почему-то всегда про замечания об ошибках то же самое пишут — пиши в личку, чётров грамма-наци, минусуют, сливают карму. А тут практически обратное. В чём секрет, Шерлок?


    1. mairos Автор
      23.07.2015 12:40
      +2

      ох, извиняюсь :-( исправлено


  1. Voffko
    23.07.2015 12:28

    del.


  1. mairos Автор
    23.07.2015 12:36
    +1

    Выражаю благодарность DmitryO за ссылку на эту интересную статью :-)
    Все неточности перевода, непонятности и пожелания прошу писать в личку, ошибки будут оперативно исправлены.
    Вообще, было сложно понять, какие термины нужно перевести, а какие оставить на английском. Я старался делать примерно так, как это принято в предыдущих статьях Хабра по Android, чтобы читателям было понятнее (мне самому не очень понятно, почему «фрагменты» обычно пишут на русском, а «activity» оставляют на английском). Заранее приношу свои извинения любителям русского языка за выражения типа «релизная сборка» и «обфускировать» — увы, это специализированный технический текст и альтернативы, как правило, были не лучше. А если оставлять все эти термины на английском, как мы обычно делаем в профессиональным диалогах с коллегами, тут пол-текста будет латиницей :-)


    1. DmitryO
      23.07.2015 19:20

      Всегда пожалуйста )

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


  1. Suvitruf
    23.07.2015 13:10

    Используйте систему сборки Gradle и стандартную структуру проекта
    Почему, например, не Maven?
    Не пишите свой собственный HTTP-клиент, лучше используйте библиотеки Volley или OkHttp
    А Aquery можно?
    Используйте библиотеку Jackson для парсинга данных в формате JSON
    Не GSON?
    Не делайте слишком глубокую иерархию элементов ViewGroup
    Слишком много это сколько? Стоило бы пруфы привести. Например этот от автора хорошей книги по Android.
    Есть два распространённых варианта: старая Ant & Eclipse ADT структура проекта — либо новая Gradle & Android Studio.
    Maven & Eclipse?
    Всегда используйте ProGuard или DexGuard
    Зачем? Ужимать код? Это мелочи по сравнению с ресурсами. Обфускации? Не всегда требуется. Я не говорю, что не надо использовать ProGuard, но не понимаю, почему автор так категорично навязывает его использование.


    1. mairos Автор
      23.07.2015 14:24
      +3

      не могу быть уверен, почему так написали авторы статьи, могу лишь озвучить своё мнение :-)

      1. По нашим ощущениям, Gradle действительно функциональнее Maven и позволяет писать более компактный код. Я без проблем приведу примеры вещей, которые может Gradle и не может Maven, но не наоборот. Впрочем, статьи по этому поводу пишутся уже минимум лет 5

      2. Aquery, конечно, можно :-)

      3. По мнению авторов, Jackson более функционален, это явным образом написано в статье. Не могу уточнить, не использовали

      4. Оценка глубины иерархии — вопрос хороший, и на Хабре обсуждался, насколько помню, но явно за рамками статьи для новичков. Это же относительно короткий набор советов, а не полноценная книга по Android-разработке :-)

      5. Пусть меня поправят знатоки Eclipse, давненько не открывал, вроде под Maven там надо было ставить плагин. Android Studio же дружит с Gradle «из коробки», так что выражение автора имеет некоторый смысл

      6. выражение «Always use ProGuard or DexGuard» мне тоже кажется чересчур сильным


    1. HotIceCream
      23.07.2015 15:38

      Если принять во внимание, что ваш проект в дальнейшем будут поддерживать другие Android разработчики, то Gradle предпочтительнее, чем что либо еще, т.к. в последнее время является чуть ли не стандартом для сборки Android приложений. Даже если вы лучше разбираетесь с Maven, заставлять это других делать не вижу смысла.

      Возможно я не прав, но приложение, после обфускации с помощью ProGuard должно работать быстрее: названия методов становятся короче -> поиск по ним становится быстрее. Использовать DexGuard во всех подрят приложениях не вижу смысла.

      А вообще, вы можете сделать Pull Request в оригинальную статью на github со своими комментариями;)


      1. zagayevskiy
        23.07.2015 18:47
        +3

        Wat? Поиск по названиям методов? В джаве? Вы не правы:)


    1. Valle
      27.07.2015 07:07
      +1

      1. Потому что Gradle единственная официально поддерживаемая гуглом система сборки под андроид. Ну и с эстетической точки зрения gradle красивее.
      2. Неа, Aquery нельзя. Он во всю поддерживает reflection что при включении proguard добавит неимоверное количество головной боли. Ну и сломать код гораздо проще, т.к. ошибки компиляции превращаются в ошибки исполнения. В статье неточность, там должно быть написано Volley И okhttp. OkHttp гарантирует что используется последняя реализация http движка, а volley оборачивает это и добавляет кеширование, мультиплексирование запросов и загрузку картинок в придачу. Правда volley гугл тоже забросил, так что можно посмотреть на альтернативы.
      3. С глубокой иерархией две проблемы — во-первых, это приводит к StackOverflowExceptions на старых девайсах и во-вторых если используются RelativeRayout или любые другие двухпроходные layouts то это будет жутко тормозить.
      4. Эклипс больше не поддерживается. Ну и по сравнению с AndroidStudio он убог.
      5. Потому что рано или поздно но вы придете к проблеме 65k methods, а если решать ее Multidex-ом то получится тормозящее чудовище типа клиента фейсбука. Прогуард кроме выкидывания кода который вы не используете еще дает существенную оптимизацию потребления памяти и скорости выполнения.


  1. eXstaM
    23.07.2015 13:35
    +1

    Не пишите свой собственный HTTP-клиент, лучше используйте библиотеки Volley или OkHttp

    а чем плох Android Asynchronous Http Client loopj.com/android-async-http ?


    1. mairos Автор
      23.07.2015 13:58

      вполне хорош, доводилось использовать
      но вряд ли автор мог перечислить все хорошие библиотеки для http-запросов, тут скорее акцент на том, чтобы начинающие разработчики не писали свои велосипеды, а посмотрели по сторонам
      Volley и OkHttp — вполне себе решения, тоже живут у нас в production


      1. Suvitruf
        23.07.2015 14:02

        Почему тогда акцентирование именно на сетевом стеке? Ведь таким образом можно вообще всё обобщить: «Не пишите свои велосипеды, если есть уже готовые решения».


        1. mairos Автор
          23.07.2015 14:09

          это были конкретные примеры велосипедов, на мой взгляд, вполне удачные
          по крайней мере не раз доводилось видеть (как правило, именно у начинающих разработчиков) объёмные реализации сетевых взаимодействий, по функциональности легко покрываемые тем же OkHttp, или, если брать шире, Retrofit


        1. DmitryO
          23.07.2015 19:30

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


  1. zagayevskiy
    23.07.2015 14:47
    +1

    Не стоит злоупотреблять API уровня Android, например, слепо полагаясь на механизм Intent для внутренней работы приложения. Вы можете повлиять на операционную систему Android или другие приложения, вызвав ошибки или зависания. Например, известно, что если ваше приложение использует механизм Intent для внутренней коммуникации между пакетами приложения, вы можете вызвать зависание в несколько секунд, если приложение было открыто сразу после загрузки операционной системы.
    Не понял, что это значит. Объясните подробнее, плз.


    1. MaximEfimov
      23.07.2015 18:40

      Возможно речь идет о некоторых ограничениях метода. Например, если вы положите в Intent список из миллиона строк, то вероятнее всего, принимающая сторона «не заведется». Если это будет Activity, то вы не увидите даже запуска метода onCreate(), как и хоть какого-нибудь сообщения в лог – такую ошибку становится непросто поймать.
      Или, если вы напишете свой Parcelable, который будет не просто раскладываться в POJO, а лезть, например, в кеш, то десериализация такого объекта в неродном процессе также, скорее всего, ни к чему хорошему не приведет.


      1. zagayevskiy
        23.07.2015 18:49

        Аа, ну ССЗБ, в общем:)


  1. artemgapchenko
    23.07.2015 16:52
    +1

    кода пользователь поймает баг и пришлёт вам обфускированный лог ошибок.

    В части наших проектов мы используем Crashlytics, а для ещё одного писали своё собственное решение для автоматического сбора крэшей. Статистика по проблемам, возникающим у пользователей, собирается гораздо быстрее, если не доверять им возможность выбора — слать отчёт об ошибке, или не слать, а отсылать сразу же в фоновом режиме.

    Подводные камни: Genymotion не позволяет использовать в приложении такие сервисы Google, как Google Play Store или Maps.

    Это только если не научить его их использовать. :)


  1. MaximEfimov
    23.07.2015 17:01
    +2

    Я бы еще добавил, что нужно смотреть на правильную организацию потоков и передачу данных между ними. Для этого можно взять широкоизвестный Robospice, или недавно представленный нами Chronos. Очень часто ведь люди пишут все на AsyncTask'ах, когда 2015 год на дворе, а потом приложение неадекватно себя ведет, стоит только несколько раз повернуть экран.


  1. C_arter
    23.07.2015 18:33

    Помните о ограничении dex-файла на количество методов, и избегайте использования большого количества библиотек. Приложения Android, при упаковке в dex-файл, имеют жёсткое ограничение в 65536 ссылочных методов

    Ну если не получается уложиться, то можно использовать multidex.
    developer.android.com/tools/building/multidex.html


    1. sapl
      23.07.2015 18:41

      К сожалению выйти за 65к очень просто даже в средненьком проекте,
      достаточно подключить для авторизации Facebook SDK и получить кучу не нужного кода.


  1. mairos Автор
    23.07.2015 19:12

    меня в этой статье больше всего смутил раздел «Фреймворки для тестирования»
    чем ребят из Futurice не устраивает Espresso, входящий в официальный com.android.support, поддерживающий API начиная с Froyo, совместимый с Junit4 — мне совершенно непонятно :-|
    живёт у нас в production уже пол-года и никаких проблем с тестированием UI не наблюдаем


    1. DmitryO
      23.07.2015 19:33

      Так ребята ж делятся своим опытом. Есть дисклеймер. Вот если бы вы поделились опытом своей команды, там бы стоял Espresso.


      1. mairos Автор
        23.07.2015 19:48

        В смысле, pull request им прислать? (issue по мотивам этой статьи им уже вкатили) :-D или написать «Best Practices» от MyCompany? :-)
        На самом деле, вопрос был в некоторой степени риторический, конечно. Имелось в виду донести информацию, что есть не только Robotium.
        А не устраивает их в Espresso с большой вероятностью то, что переходить на другой фреймворк тестирования — это убиться можно сколько работы, разве что в новых проектах начинать потихоньку.

        Правда чтоли pull request отправить? :-) Один вон уже прислал перевод "to French" :-D


        1. DmitryO
          23.07.2015 21:42

          Смысла особого не вижу в pull request. Они же вполне однозначно написали, что делятся своим личным опытом. И если Futurice не использует Espresso, то зачем им его упоминать?


  1. z0rgoyok
    24.07.2015 00:55

    В Genymotion можно поставить ARM Translation и GApps, будет и Play Store и Play Services.


  1. formatbce
    24.07.2015 17:25

    ...[DexGuard] Он может легко разделить Dex-файл на несколько для обхода ограничения в 65k методов...

    MultiDexApplication отлично справляется.